///|
/// EoE Embedded Relay (Phase 3) — lightweight synchronous frame relay.
///
/// Design isolation from Phase 2 (async/network):
/// - Phase 2 (eoe_endpoint.mbt): queue-based async, decoupled timing,
/// host application polls at its own pace, needs buffering.
/// - Phase 3 (eoe_relay.mbt): synchronous callback, zero-copy intent,
/// frame processing happens inline during the PDO cycle, no queuing.
///
/// The embedded relay is suitable for bare-metal / RTOS targets where:
/// - No OS network stack (no TAP/TUN, no sockets)
/// - Frames are processed by application logic directly
/// - Deterministic timing: relay happens within the PDO cycle budget
/// - Memory is constrained: no dynamic queue growth
///|
/// Direction of an EoE relay frame.
pub(all) enum EoeRelayDirection {
/// Frame going from host application to an EoE slave.
HostToSlave
/// Frame received from an EoE slave, delivered to host application.
SlaveToHost
} derive(Eq, Debug)
///|
/// Metadata for a relayed EoE frame.
pub(all) struct EoeRelayFrame {
/// Direction of the frame.
direction : EoeRelayDirection
/// EoE station address (source for SlaveToHost, destination for HostToSlave).
station : UInt
/// Raw Ethernet frame bytes.
data : Bytes
} derive(Eq, Debug)
///|
/// Statistics for the embedded relay.
pub(all) struct EoeRelayStats {
/// Total frames relayed host→slave.
mut tx_relayed : Int64
/// Total frames relayed slave→host.
mut rx_relayed : Int64
/// Frames dropped due to handler rejection.
mut dropped : Int64
} derive(Eq, Debug)
///|
pub fn EoeRelayStats::new() -> EoeRelayStats {
{ tx_relayed: 0L, rx_relayed: 0L, dropped: 0L }
}
///|
/// EoE Embedded Relay engine.
///
/// Processes frames synchronously during the PDO cycle:
/// 1. Collects all reassembled RX frames from EoeSwitch
/// 2. Invokes `rx_handler` for each (application processes inline)
/// 3. Invokes `tx_provider` to get frames to send (application provides inline)
/// 4. Sends collected TX frames via EoeSwitch
///
/// The handler/provider are plain functions, not trait objects, keeping
/// the relay free of dynamic dispatch overhead on embedded targets.
pub(all) struct EoeRelay {
/// Relay statistics.
stats : EoeRelayStats
/// Whether the relay is active.
mut active : Bool
} derive(Debug)
///|
pub fn EoeRelay::new() -> EoeRelay {
{ stats: EoeRelayStats::new(), active: true }
}
///|
/// Activate or deactivate the relay.
pub fn EoeRelay::set_active(self : EoeRelay, active : Bool) -> Unit {
self.active = active
}
///|
/// Execute one relay cycle synchronously.
///
/// `rx_handler`: Called for each reassembled Ethernet frame received from
/// an EoE slave. Returns true if the frame was accepted, false to drop.
///
/// `tx_provider`: Called repeatedly to collect frames to send. Returns
/// None when no more frames are available this cycle.
///
/// Returns (rx_count, tx_count) processed this cycle.
pub fn[N : @hal.Nic] EoeRelay::relay_cycle(
self : EoeRelay,
sw : EoeSwitch,
nic : N,
timeout : @hal.Duration,
rx_handler : (EoeRelayFrame) -> Bool,
tx_provider : () -> EoeRelayFrame?,
) -> (Int, Int) {
if !self.active {
return (0, 0)
}
// RX: deliver reassembled frames to application
let received = sw.drain_rx()
let mut rx_count = 0
for f in received {
let relay_frame = EoeRelayFrame::{
direction: SlaveToHost,
station: f.station,
data: f.data,
}
if rx_handler(relay_frame) {
self.stats.rx_relayed = self.stats.rx_relayed + 1L
rx_count = rx_count + 1
} else {
self.stats.dropped = self.stats.dropped + 1L
}
}
// TX: collect frames from application and send
let mut tx_count = 0
while true {
match tx_provider() {
None => break
Some(frame) =>
try {
ignore(sw.send_frame(nic, frame.station, frame.data, timeout))
self.stats.tx_relayed = self.stats.tx_relayed + 1L
tx_count = tx_count + 1
} catch {
_ => self.stats.dropped = self.stats.dropped + 1L
}
}
}
(rx_count, tx_count)
}
///|
/// Get relay statistics as JSON.
pub fn EoeRelayStats::to_json(self : EoeRelayStats) -> Json {
{
"tx_relayed": Json::number(self.tx_relayed.to_double()),
"rx_relayed": Json::number(self.rx_relayed.to_double()),
"dropped": Json::number(self.dropped.to_double()),
}
|> Json::object
}
///|
/// Get full relay status as JSON.
pub fn EoeRelay::to_json(self : EoeRelay) -> Json {
{ "active": Json::boolean(self.active), "stats": self.stats.to_json() }
|> Json::object
}
///|
/// Format relay status as human-readable text.
pub fn EoeRelay::format_text(self : EoeRelay) -> String {
let buf = StringBuilder(size_hint=128)
buf.write_string("EoE Embedded Relay\n")
buf.write_string(" Active: ")
buf.write_string(self.active.to_string())
buf.write_string("\n TX relayed: ")
buf.write_string(self.stats.tx_relayed.to_string())
buf.write_string("\n RX relayed: ")
buf.write_string(self.stats.rx_relayed.to_string())
buf.write_string("\n Dropped: ")
buf.write_string(self.stats.dropped.to_string())
buf.write_string("\n")
buf.to_string()
}