///|
/// Errors raised when a gateway transforms a frame.
pub suberror RoutingError {
InvalidOutputIdentifier
InvalidPrefix
PayloadOverflow
} derive(Debug)
///|
/// A deterministic one-way gateway rule.
pub struct RouteRule {
name : String
filter : Filter
output_id : UInt?
output_extended : Bool?
prefix : Array[Byte]
strip_prefix : Int
}
///|
/// A frame accepted and transformed by a route.
pub struct RoutedFrame {
rule_name : String
input_id : UInt
frame : Frame
}
///|
/// A stateless rule-based CAN gateway.
pub struct CanGateway {
routes : Array[RouteRule]
mut forwarded : Int
mut rejected : Int
}
///|
/// Create an empty gateway.
pub fn new_gateway() -> CanGateway {
{ routes: [], forwarded: 0, rejected: 0 }
}
///|
/// Construct a route rule with optional identifier and payload rewrites.
pub fn route_rule(
name : String,
filter : Filter,
output_id? : UInt? = None,
output_extended? : Bool? = None,
prefix? : Array[Byte] = [],
strip_prefix? : Int = 0,
) -> RouteRule raise RoutingError {
if strip_prefix < 0 || strip_prefix > 64 {
raise InvalidPrefix
}
{
name,
filter,
output_id,
output_extended,
prefix: prefix.copy(),
strip_prefix,
}
}
///|
pub fn CanGateway::add_rule(self : CanGateway, rule : RouteRule) -> Unit {
self.routes.push(rule)
}
///|
pub fn CanGateway::rule_count(self : CanGateway) -> Int {
self.routes.length()
}
///|
/// Return route names in insertion order.
pub fn CanGateway::rule_names(self : CanGateway) -> Array[String] {
self.routes.map(rule => rule.name)
}
///|
/// Apply the first matching rule.
pub fn CanGateway::forward(
self : CanGateway,
input : Frame,
) -> RoutedFrame? raise RoutingError {
for rule in self.routes {
if rule.filter.matches(input) {
let original = input.id()
let data = input.data()
if rule.strip_prefix > data.length() {
self.rejected += 1
raise InvalidPrefix
}
let output_data = rule.prefix + data[rule.strip_prefix:].to_owned()
let output_id = match rule.output_id {
Some(id) => id
None => input.id()
}
let extended = match rule.output_extended {
Some(value) => value
None => input.is_extended()
}
let frame = match input.kind() {
Remote =>
remote_frame(output_id, extended~) catch {
_ => {
self.rejected += 1
raise InvalidOutputIdentifier
}
}
Error =>
error_frame(if output_data.is_empty() { 0 } else { output_data[0] })
Data =>
match input.protocol() {
Can20 =>
data_frame(output_id, output_data, extended~) catch {
_ => {
self.rejected += 1
raise PayloadOverflow
}
}
CanFd =>
fd_frame(
output_id,
output_data,
extended~,
bitrate_switch=input.bitrate_switch(),
error_state_indicator=input.error_state_indicator(),
) catch {
_ => {
self.rejected += 1
raise PayloadOverflow
}
}
}
}
self.forwarded += 1
return Some({ rule_name: rule.name, input_id: original, frame })
}
}
self.rejected += 1
None
}
///|
pub fn CanGateway::forwarded(self : CanGateway) -> Int {
self.forwarded
}
///|
pub fn CanGateway::rejected(self : CanGateway) -> Int {
self.rejected
}
///|
pub fn RoutedFrame::rule_name(self : RoutedFrame) -> String {
self.rule_name
}
///|
pub fn RoutedFrame::input_id(self : RoutedFrame) -> UInt {
self.input_id
}
///|
pub fn RoutedFrame::frame(self : RoutedFrame) -> Frame {
self.frame
}
///|
/// Route a batch and keep only successfully forwarded frames.
pub fn route_batch(
gateway : CanGateway,
frames : Array[Frame],
) -> Array[RoutedFrame] raise RoutingError {
let result : Array[RoutedFrame] = []
for frame in frames {
match gateway.forward(frame) {
Some(routed) => result.push(routed)
None => ()
}
}
result
}
///|
/// Create a bidirectional bridge from two gateways.
pub struct GatewayPair {
left_to_right : CanGateway
right_to_left : CanGateway
}
///|
pub fn gateway_pair(
left_to_right : CanGateway,
right_to_left : CanGateway,
) -> GatewayPair {
{ left_to_right, right_to_left }
}
///|
pub fn GatewayPair::left(self : GatewayPair) -> CanGateway {
self.left_to_right
}
///|
pub fn GatewayPair::right(self : GatewayPair) -> CanGateway {
self.right_to_left
}
///|
/// Return a route's stable name.
pub fn RouteRule::name(self : RouteRule) -> String {
self.name
}
///|
/// Return the rule's payload prefix.
pub fn RouteRule::prefix(self : RouteRule) -> Array[Byte] {
self.prefix.copy()
}