///|
/// ISO-TP addressing format for a diagnostic transport channel.
pub enum IsoTpSessionAddressing {
IsoTpNormalAddressing
IsoTpExtendedAddressing(Byte)
IsoTpMixedAddressing(Byte)
}
///|
/// Construct all portable ISO-TP addressing formats.
pub fn isotp_session_addressing_variants() -> Array[IsoTpSessionAddressing] {
[IsoTpNormalAddressing, IsoTpExtendedAddressing(0), IsoTpMixedAddressing(0)]
}
///|
/// Flow-control parameters advertised by a receiver.
pub struct IsoTpFlowControlConfig {
block_size : Int
separation_time_us : UInt64
wait_frame_limit : Int
max_payload : Int
}
///|
/// Create flow-control parameters with safe bounds.
pub fn isotp_flow_control_config(
block_size : Int,
separation_time_us : UInt64,
wait_frame_limit : Int,
max_payload : Int,
) -> IsoTpFlowControlConfig {
{
block_size: if block_size < 0 {
0
} else {
block_size
},
separation_time_us,
wait_frame_limit: if wait_frame_limit < 0 {
0
} else {
wait_frame_limit
},
max_payload: if max_payload < 0 {
0
} else {
max_payload
},
}
}
///|
pub fn IsoTpFlowControlConfig::block_size(self : IsoTpFlowControlConfig) -> Int {
self.block_size
}
///|
pub fn IsoTpFlowControlConfig::separation_time_us(
self : IsoTpFlowControlConfig,
) -> UInt64 {
self.separation_time_us
}
///|
pub fn IsoTpFlowControlConfig::wait_frame_limit(
self : IsoTpFlowControlConfig,
) -> Int {
self.wait_frame_limit
}
///|
pub fn IsoTpFlowControlConfig::max_payload(
self : IsoTpFlowControlConfig,
) -> Int {
self.max_payload
}
///|
/// Return the wire representation of a flow-control frame.
pub fn IsoTpFlowControlConfig::to_payload(
self : IsoTpFlowControlConfig,
) -> Array[Byte] {
[0x30, self.block_size.to_byte(), (self.separation_time_us / 1000).to_byte()]
}
///|
/// State of a long-payload ISO-TP transmitter.
pub enum IsoTpTransmitterState {
IsoTpTxIdle
IsoTpTxWaitingFlowControl
IsoTpTxSending
IsoTpTxComplete
IsoTpTxAborted(String)
}
///|
/// Construct every transmitter state for state-machine tools.
pub fn isotp_transmitter_state_variants() -> Array[IsoTpTransmitterState] {
[
IsoTpTxIdle,
IsoTpTxWaitingFlowControl,
IsoTpTxSending,
IsoTpTxComplete,
IsoTpTxAborted("example"),
]
}
///|
/// A stateful ISO-TP transmitter.
pub struct IsoTpTransmitter {
payload : Array[Byte]
mut offset : Int
mut sequence : Byte
mut block_sent : Int
mut wait_frames : Int
mut state : IsoTpTransmitterState
flow_control : IsoTpFlowControlConfig
mut next_due_us : UInt64
started_us : UInt64
}
///|
/// Create a transmitter for a bounded payload.
pub fn new_isotp_transmitter(
payload : Array[Byte],
flow_control : IsoTpFlowControlConfig,
started_us : UInt64,
) -> IsoTpTransmitter {
let state = if payload.length() <= 7 {
IsoTpTxSending
} else {
IsoTpTxWaitingFlowControl
}
{
payload: payload.copy(),
offset: 0,
sequence: 1,
block_sent: 0,
wait_frames: 0,
state,
flow_control,
next_due_us: started_us,
started_us,
}
}
///|
pub fn IsoTpTransmitter::payload(self : IsoTpTransmitter) -> Array[Byte] {
self.payload.copy()
}
///|
pub fn IsoTpTransmitter::offset(self : IsoTpTransmitter) -> Int {
self.offset
}
///|
pub fn IsoTpTransmitter::remaining(self : IsoTpTransmitter) -> Int {
self.payload.length() - self.offset
}
///|
pub fn IsoTpTransmitter::sequence(self : IsoTpTransmitter) -> Byte {
self.sequence
}
///|
pub fn IsoTpTransmitter::state(
self : IsoTpTransmitter,
) -> IsoTpTransmitterState {
self.state
}
///|
pub fn IsoTpTransmitter::next_due_us(self : IsoTpTransmitter) -> UInt64 {
self.next_due_us
}
///|
pub fn IsoTpTransmitter::started_us(self : IsoTpTransmitter) -> UInt64 {
self.started_us
}
///|
pub fn IsoTpTransmitter::is_complete(self : IsoTpTransmitter) -> Bool {
self.state is IsoTpTxComplete
}
///|
pub fn IsoTpTransmitter::is_aborted(self : IsoTpTransmitter) -> Bool {
self.state is IsoTpTxAborted(_)
}
///|
/// Accept a receiver flow-control payload.
pub fn IsoTpTransmitter::accept_flow_control(
self : IsoTpTransmitter,
payload : Array[Byte],
timestamp_us : UInt64,
) -> Bool {
if payload.length() < 3 || (payload[0].to_int() & 0xF0) != 0x30 {
self.abort("invalid flow control")
false
} else {
let status = payload[0].to_int() & 0x0F
if status == 0x1 {
self.wait_frames += 1
if self.wait_frames > self.flow_control.wait_frame_limit() {
self.abort("flow-control wait limit exceeded")
false
} else {
self.state = IsoTpTxWaitingFlowControl
true
}
} else if status == 0x2 {
self.abort("flow-control overflow")
false
} else {
self.block_sent = 0
self.wait_frames = 0
self.state = IsoTpTxSending
self.next_due_us = timestamp_us
true
}
}
}
///|
/// Produce the next ISO-TP payload when the transmitter is due.
pub fn IsoTpTransmitter::next_payload(
self : IsoTpTransmitter,
timestamp_us : UInt64,
) -> Array[Byte]? {
if self.is_complete() || self.is_aborted() || timestamp_us < self.next_due_us {
None
} else if self.payload.length() <= 7 && self.offset == 0 {
let result : Array[Byte] = [self.payload.length().to_byte()]
result.append(self.payload.copy())
self.offset = self.payload.length()
self.state = IsoTpTxComplete
Some(result)
} else if self.offset == 0 {
let length = self.payload.length()
let result : Array[Byte] = [
0x10 | (length >> 8).to_byte(),
length.to_byte(),
]
let take = if length > 6 { 6 } else { length }
result.append(self.payload[:take].to_owned())
self.offset = take
self.sequence = 1
self.block_sent = 0
self.state = IsoTpTxWaitingFlowControl
Some(result)
} else if self.state is IsoTpTxSending {
let remaining = self.payload.length() - self.offset
let take = if remaining > 7 { 7 } else { remaining }
let result : Array[Byte] = [0x20 | self.sequence.to_int().to_byte()]
result.append(self.payload[self.offset:self.offset + take].to_owned())
self.offset += take
self.sequence = ((self.sequence.to_int() + 1) & 0x0F).to_byte()
self.block_sent += 1
self.next_due_us = timestamp_us + self.flow_control.separation_time_us()
if self.offset >= self.payload.length() {
self.state = IsoTpTxComplete
} else if self.flow_control.block_size() > 0 &&
self.block_sent >= self.flow_control.block_size() {
self.state = IsoTpTxWaitingFlowControl
self.block_sent = 0
}
Some(result)
} else {
None
}
}
///|
/// Abort a transmitter with a stable reason.
pub fn IsoTpTransmitter::abort(
self : IsoTpTransmitter,
reason : String,
) -> Unit {
self.state = IsoTpTxAborted(reason)
}
///|
/// Return an upper bound for the remaining transfer duration.
pub fn IsoTpTransmitter::estimated_finish_us(self : IsoTpTransmitter) -> UInt64 {
if self.is_complete() || self.is_aborted() {
self.next_due_us
} else {
let frames = (self.remaining() + 6) / 7
self.next_due_us +
frames.to_uint64() * self.flow_control.separation_time_us()
}
}
///|
/// A receiver result for an incoming ISO-TP payload.
pub enum IsoTpReceiverResult {
IsoTpRxIgnored
IsoTpRxFlowControl(Array[Byte])
IsoTpRxProgress(Int)
IsoTpRxComplete(Array[Byte])
IsoTpRxError(String)
}
///|
/// Construct representative receiver results.
pub fn isotp_receiver_result_variants() -> Array[IsoTpReceiverResult] {
[
IsoTpRxIgnored,
IsoTpRxFlowControl([0x30, 0, 0]),
IsoTpRxProgress(0),
IsoTpRxComplete([]),
IsoTpRxError("example"),
]
}
///|
/// A stateful ISO-TP receiver.
pub struct IsoTpStreamReceiver {
mut expected_length : Int
mut received : Array[Byte]
mut next_sequence : Byte
mut block_received : Int
mut wait_frames : Int
mut last_timestamp_us : UInt64
mut active : Bool
mut complete : Bool
flow_control : IsoTpFlowControlConfig
timeout_us : UInt64
}
///|
pub fn new_isotp_stream_receiver(
flow_control : IsoTpFlowControlConfig,
timeout_us : UInt64,
) -> IsoTpStreamReceiver {
{
expected_length: 0,
received: [],
next_sequence: 1,
block_received: 0,
wait_frames: 0,
last_timestamp_us: 0,
active: false,
complete: false,
flow_control,
timeout_us,
}
}
///|
pub fn IsoTpStreamReceiver::expected_length(self : IsoTpStreamReceiver) -> Int {
self.expected_length
}
///|
pub fn IsoTpStreamReceiver::received_length(self : IsoTpStreamReceiver) -> Int {
self.received.length()
}
///|
pub fn IsoTpStreamReceiver::remaining(self : IsoTpStreamReceiver) -> Int {
self.expected_length - self.received.length()
}
///|
pub fn IsoTpStreamReceiver::active(self : IsoTpStreamReceiver) -> Bool {
self.active
}
///|
pub fn IsoTpStreamReceiver::complete(self : IsoTpStreamReceiver) -> Bool {
self.complete
}
///|
pub fn IsoTpStreamReceiver::next_sequence(self : IsoTpStreamReceiver) -> Byte {
self.next_sequence
}
///|
pub fn IsoTpStreamReceiver::last_timestamp_us(
self : IsoTpStreamReceiver,
) -> UInt64 {
self.last_timestamp_us
}
///|
pub fn IsoTpStreamReceiver::payload(self : IsoTpStreamReceiver) -> Array[Byte] {
self.received.copy()
}
///|
/// Feed a complete ISO-TP payload received from the bus.
pub fn IsoTpStreamReceiver::feed(
self : IsoTpStreamReceiver,
payload : Array[Byte],
timestamp_us : UInt64,
) -> IsoTpReceiverResult {
if payload.is_empty() {
IsoTpRxError("empty ISO-TP payload")
} else {
let pci = payload[0].to_int()
let kind = pci & 0xF0
self.last_timestamp_us = timestamp_us
if kind == 0x00 {
let length = pci & 0x0F
if length > payload.length() - 1 {
IsoTpRxError("single-frame length exceeds payload")
} else {
self.reset()
self.expected_length = length
self.received = payload[1:1 + length].to_owned()
self.complete = true
IsoTpRxComplete(self.received.copy())
}
} else if kind == 0x10 {
if payload.length() < 2 {
IsoTpRxError("first frame has no length byte")
} else {
let length = ((pci & 0x0F) << 8) | payload[1].to_int()
if length <= 7 || length > self.flow_control.max_payload() {
IsoTpRxError("first-frame length is outside receiver bounds")
} else {
self.reset()
self.expected_length = length
let take = if payload.length() - 2 > length {
length
} else {
payload.length() - 2
}
self.received.append(payload[2:2 + take].to_owned())
self.active = true
self.complete = false
self.next_sequence = 1
self.block_received = 0
IsoTpRxFlowControl(self.flow_control.to_payload())
}
}
} else if kind == 0x20 {
self.feed_consecutive(pci & 0x0F, payload, timestamp_us)
} else if kind == 0x30 {
IsoTpRxIgnored
} else {
IsoTpRxError("unknown ISO-TP PCI type")
}
}
}
///|
fn IsoTpStreamReceiver::feed_consecutive(
self : IsoTpStreamReceiver,
sequence : Int,
payload : Array[Byte],
timestamp_us : UInt64,
) -> IsoTpReceiverResult {
if !self.active {
IsoTpRxError("consecutive frame without first frame")
} else if sequence != self.next_sequence.to_int() {
self.reset()
IsoTpRxError("unexpected consecutive-frame sequence")
} else {
let take = if payload.length() - 1 > self.remaining() {
self.remaining()
} else {
payload.length() - 1
}
self.received.append(payload[1:1 + take].to_owned())
self.last_timestamp_us = timestamp_us
self.next_sequence = ((self.next_sequence.to_int() + 1) & 0x0F).to_byte()
self.block_received += 1
if self.received.length() >= self.expected_length {
self.active = false
self.complete = true
IsoTpRxComplete(self.received[:self.expected_length].to_owned())
} else if self.flow_control.block_size() > 0 &&
self.block_received >= self.flow_control.block_size() {
self.block_received = 0
IsoTpRxFlowControl(self.flow_control.to_payload())
} else {
IsoTpRxProgress(self.received.length())
}
}
}
///|
/// Return whether a receiver transfer has timed out.
pub fn IsoTpStreamReceiver::timed_out(
self : IsoTpStreamReceiver,
timestamp_us : UInt64,
) -> Bool {
self.active && timestamp_us > self.last_timestamp_us + self.timeout_us
}
///|
/// Reset a receiver to accept a new transfer.
pub fn IsoTpStreamReceiver::reset(self : IsoTpStreamReceiver) -> Unit {
self.expected_length = 0
self.received.clear()
self.next_sequence = 1
self.block_received = 0
self.wait_frames = 0
self.active = false
self.complete = false
}
///|
/// Take the completed payload and reset the receiver.
pub fn IsoTpStreamReceiver::take_payload(
self : IsoTpStreamReceiver,
) -> Array[Byte]? {
if self.complete {
let result = self.received[:self.expected_length].to_owned()
self.reset()
Some(result)
} else {
None
}
}
///|
/// A transport exchange combining a transmitter and receiver.
pub struct IsoTpSession {
mut transmitter : IsoTpTransmitter?
receiver : IsoTpStreamReceiver
mut sent_frames : Int
mut received_frames : Int
mut dropped_frames : Int
}
///|
pub fn new_isotp_session(
flow_control : IsoTpFlowControlConfig,
timeout_us : UInt64,
) -> IsoTpSession {
{
transmitter: None,
receiver: new_isotp_stream_receiver(flow_control, timeout_us),
sent_frames: 0,
received_frames: 0,
dropped_frames: 0,
}
}
///|
pub fn IsoTpSession::start_transmit(
self : IsoTpSession,
payload : Array[Byte],
timestamp_us : UInt64,
) -> Unit {
self.transmitter = Some(
new_isotp_transmitter(payload, self.receiver.flow_control, timestamp_us),
)
}
///|
pub fn IsoTpSession::next_transmit(
self : IsoTpSession,
timestamp_us : UInt64,
) -> Array[Byte]? {
match self.transmitter {
Some(transmitter) => {
let result = transmitter.next_payload(timestamp_us)
match result {
Some(_) => self.sent_frames += 1
None => ()
}
result
}
None => None
}
}
///|
pub fn IsoTpSession::accept_flow_control(
self : IsoTpSession,
payload : Array[Byte],
timestamp_us : UInt64,
) -> Bool {
match self.transmitter {
Some(transmitter) => transmitter.accept_flow_control(payload, timestamp_us)
None => false
}
}
///|
pub fn IsoTpSession::receive(
self : IsoTpSession,
payload : Array[Byte],
timestamp_us : UInt64,
) -> IsoTpReceiverResult {
let result = self.receiver.feed(payload, timestamp_us)
self.received_frames += 1
match result {
IsoTpRxError(_) => self.dropped_frames += 1
_ => ()
}
result
}
///|
pub fn IsoTpSession::transmitter(self : IsoTpSession) -> IsoTpTransmitter? {
self.transmitter
}
///|
pub fn IsoTpSession::receiver(self : IsoTpSession) -> IsoTpStreamReceiver {
self.receiver
}
///|
pub fn IsoTpSession::sent_frames(self : IsoTpSession) -> Int {
self.sent_frames
}
///|
pub fn IsoTpSession::received_frames(self : IsoTpSession) -> Int {
self.received_frames
}
///|
pub fn IsoTpSession::dropped_frames(self : IsoTpSession) -> Int {
self.dropped_frames
}
///|
/// An ISO-TP channel identity used by a transport router.
pub struct IsoTpChannel {
source_id : UInt
target_id : UInt
extended : Bool
addressing : IsoTpSessionAddressing
flow_control : IsoTpFlowControlConfig
}
///|
pub fn isotp_channel(
source_id : UInt,
target_id : UInt,
extended? : Bool = false,
addressing? : IsoTpSessionAddressing = IsoTpNormalAddressing,
flow_control? : IsoTpFlowControlConfig = isotp_flow_control_config(
8, 0, 3, 4095,
),
) -> IsoTpChannel {
{ source_id, target_id, extended, addressing, flow_control }
}
///|
pub fn IsoTpChannel::source_id(self : IsoTpChannel) -> UInt {
self.source_id
}
///|
pub fn IsoTpChannel::target_id(self : IsoTpChannel) -> UInt {
self.target_id
}
///|
pub fn IsoTpChannel::extended(self : IsoTpChannel) -> Bool {
self.extended
}
///|
pub fn IsoTpChannel::addressing(self : IsoTpChannel) -> IsoTpSessionAddressing {
self.addressing
}
///|
pub fn IsoTpChannel::flow_control(
self : IsoTpChannel,
) -> IsoTpFlowControlConfig {
self.flow_control
}
///|
/// Return whether a frame identifier belongs to a channel.
pub fn IsoTpChannel::accepts(self : IsoTpChannel, frame : Frame) -> Bool {
frame.id() == self.target_id || frame.id() == self.source_id
}
///|
/// Build a CAN frame carrying an ISO-TP payload.
pub fn IsoTpChannel::frame(
self : IsoTpChannel,
payload : Array[Byte],
transmit : Bool,
) -> Frame raise FrameError {
let id = if transmit { self.source_id } else { self.target_id }
data_frame(id, payload, extended=self.extended)
}
///|
/// A bounded transport router for multiple diagnostic channels.
pub struct IsoTpRouter {
channels : Array[IsoTpChannel]
mut accepted : Int
mut rejected : Int
}
///|
pub fn new_isotp_router() -> IsoTpRouter {
{ channels: [], accepted: 0, rejected: 0 }
}
///|
pub fn IsoTpRouter::add_channel(
self : IsoTpRouter,
channel : IsoTpChannel,
) -> Bool {
if self.find_channel(channel.source_id(), channel.target_id()) is Some(_) {
false
} else {
self.channels.push(channel)
true
}
}
///|
pub fn IsoTpRouter::remove_channel(
self : IsoTpRouter,
source_id : UInt,
target_id : UInt,
) -> Bool {
match self.find_channel_index(source_id, target_id) {
Some(index) => {
ignore(self.channels.remove(index))
true
}
None => false
}
}
///|
pub fn IsoTpRouter::find_channel(
self : IsoTpRouter,
source_id : UInt,
target_id : UInt,
) -> IsoTpChannel? {
match self.find_channel_index(source_id, target_id) {
Some(index) => Some(self.channels[index])
None => None
}
}
///|
pub fn IsoTpRouter::channels(self : IsoTpRouter) -> Array[IsoTpChannel] {
self.channels.copy()
}
///|
pub fn IsoTpRouter::accepted(self : IsoTpRouter) -> Int {
self.accepted
}
///|
pub fn IsoTpRouter::rejected(self : IsoTpRouter) -> Int {
self.rejected
}
///|
pub fn IsoTpRouter::route(self : IsoTpRouter, frame : Frame) -> IsoTpChannel? {
for channel in self.channels {
if channel.accepts(frame) {
self.accepted += 1
return Some(channel)
}
}
self.rejected += 1
None
}
///|
fn IsoTpRouter::find_channel_index(
self : IsoTpRouter,
source_id : UInt,
target_id : UInt,
) -> Int? {
for index, channel in self.channels {
if channel.source_id() == source_id && channel.target_id() == target_id {
return Some(index)
}
}
None
}
///|
/// Return a stable transfer summary for diagnostics.
pub fn isotp_transfer_summary(
transmitter : IsoTpTransmitter,
receiver : IsoTpStreamReceiver,
) -> String {
"tx_offset=\{transmitter.offset()} rx_received=\{receiver.received_length()} rx_expected=\{receiver.expected_length()} tx_complete=\{transmitter.is_complete()} rx_complete=\{receiver.complete()}"
}