///|
/// EoE Endpoint abstraction for async/network integration (Phase 2).
///
/// An EoeEndpoint represents the host-side network interface that bridges
/// Ethernet frames between the EoE Virtual Switch and an external network
/// stack (TAP/TUN device, raw socket, or user-space application).
///
/// Design isolation: This module handles only the host↔switch boundary.
/// The actual EtherCAT-side fragmentation/reassembly remains in eoe_switch.mbt.
///|
/// Trait for bidirectional Ethernet frame exchange at the host boundary.
///
/// Implementors:
/// - QueueEndpoint: in-process async queue (default, no OS dependency)
/// - Future: TapEndpoint (Linux TAP/TUN via C FFI)
/// - Future: WintunEndpoint (Windows Wintun via C FFI)
pub(open) trait EoeEndpoint {
/// Read an Ethernet frame from the host network stack.
/// Returns None if no frame is available (non-blocking).
fn read_frame(Self) -> Bytes?
/// Write an Ethernet frame to the host network stack.
/// Returns true on success.
fn write_frame(Self, Bytes) -> Bool
/// Whether the endpoint is connected and operational.
fn is_up(Self) -> Bool
}
///|
/// Queue-based EoE endpoint for in-process async bridging.
///
/// The host application pushes frames into `tx_pending` (host→slave direction)
/// and reads frames from `rx_pending` (slave→host direction).
/// The bridge cycle transfers frames between these queues and the EoeSwitch.
///
/// This endpoint requires no OS network stack — suitable for user-space
/// applications, unit tests, and embedded systems with application-level
/// networking.
pub(all) struct QueueEndpoint {
/// Frames waiting to be sent to EoE slaves (host→slave).
tx_pending : Array[EoeEndpointFrame]
/// Frames received from EoE slaves, ready for host consumption (slave→host).
rx_pending : Array[EoeEndpointFrame]
/// Maximum queue depth per direction (0 = unlimited).
max_depth : Int
/// Whether this endpoint is active.
mut up : Bool
} derive(Debug)
///|
/// A frame in the endpoint queue, tagged with the destination/source station.
pub(all) struct EoeEndpointFrame {
/// Target or source EoE station address.
station : UInt
/// Raw Ethernet frame bytes.
data : Bytes
} derive(Eq, Debug)
///|
pub fn QueueEndpoint::new(max_depth? : Int = 64) -> QueueEndpoint {
{ tx_pending: [], rx_pending: [], max_depth, up: true }
}
///|
/// Submit a frame for sending to an EoE slave (host→slave direction).
/// Returns false if the queue is full or endpoint is down.
pub fn QueueEndpoint::submit_tx(
self : QueueEndpoint,
station : UInt,
data : Bytes,
) -> Bool {
if !self.up {
return false
}
if self.max_depth > 0 && self.tx_pending.length() >= self.max_depth {
return false
}
self.tx_pending.push(EoeEndpointFrame::{ station, data })
true
}
///|
/// Consume the next received frame from the slave→host queue.
/// Returns None if no frames are available.
pub fn QueueEndpoint::consume_rx(self : QueueEndpoint) -> EoeEndpointFrame? {
if self.rx_pending.length() == 0 {
return None
}
// Remove from front (FIFO)
Some(self.rx_pending.remove(0))
}
///|
/// Number of frames waiting to be sent to slaves.
pub fn QueueEndpoint::tx_depth(self : QueueEndpoint) -> Int {
self.tx_pending.length()
}
///|
/// Number of frames received from slaves, waiting for host consumption.
pub fn QueueEndpoint::rx_depth(self : QueueEndpoint) -> Int {
self.rx_pending.length()
}
///|
/// Set endpoint up/down state.
pub fn QueueEndpoint::set_up(self : QueueEndpoint, up : Bool) -> Unit {
self.up = up
}
///|
/// EoeEndpoint implementation for QueueEndpoint.
pub fn QueueEndpoint::read_frame(self : QueueEndpoint) -> Bytes? {
if self.tx_pending.length() == 0 {
return None
}
let frame = self.tx_pending.remove(0)
Some(frame.data)
}
///|
pub fn QueueEndpoint::write_frame(self : QueueEndpoint, data : Bytes) -> Bool {
if !self.up {
return false
}
if self.max_depth > 0 && self.rx_pending.length() >= self.max_depth {
return false
}
// Station is unknown when writing from switch; use 0 as placeholder.
self.rx_pending.push(EoeEndpointFrame::{ station: 0U, data })
true
}
///|
pub fn QueueEndpoint::is_up(self : QueueEndpoint) -> Bool {
self.up
}
///|
/// Bridge one cycle between EoeSwitch and QueueEndpoint.
///
/// TX direction (host→slave): Drains tx_pending, fragments and sends
/// each frame to its target EoE station via the switch.
///
/// RX direction (slave→host): Drains the switch rx_queue and pushes
/// reassembled frames into rx_pending for host consumption.
///
/// Returns (tx_sent, rx_delivered) counts.
pub fn[N : @hal.Nic] bridge_eoe_cycle(
sw : EoeSwitch,
ep : QueueEndpoint,
nic : N,
timeout : @hal.Duration,
) -> (Int, Int) {
let mut tx_sent = 0
// TX: host → slave
while ep.tx_pending.length() > 0 {
let frame = ep.tx_pending.remove(0)
let _ = sw.send_frame(nic, frame.station, frame.data, timeout) catch {
_ => continue
}
tx_sent = tx_sent + 1
}
// RX: slave → host
let received = sw.drain_rx()
let mut rx_delivered = 0
for f in received {
if ep.up && (ep.max_depth == 0 || ep.rx_pending.length() < ep.max_depth) {
ep.rx_pending.push(EoeEndpointFrame::{ station: f.station, data: f.data })
rx_delivered = rx_delivered + 1
}
}
(tx_sent, rx_delivered)
}
///|
/// Get endpoint status as JSON.
pub fn QueueEndpoint::to_json(self : QueueEndpoint) -> Json {
{
"up": Json::boolean(self.up),
"tx_depth": Json::number(self.tx_pending.length().to_double()),
"rx_depth": Json::number(self.rx_pending.length().to_double()),
"max_depth": Json::number(self.max_depth.to_double()),
}
|> Json::object
}