///|
/// FoE transaction layer: complete FoE Download/Upload using
/// mailbox transport, RMSM counter management, and FoE encoding/decoding.
/// Implements ETG.1000.6 §5.8 (File over EtherCAT).
///|
/// Maximum FoE Busy retries before giving up.
let foe_max_busy_retries : Int = 100
///|
/// Receive and validate a FoE response (FP addressing).
/// Discards stale/repeat frames via RMSM counter validation.
fn[N : @hal.Nic] recv_foe_validated_fp(
nic : N,
station : UInt,
mb_config : @mailbox.MailboxConfig,
rmsm : @mailbox.Rmsm,
timeout : @hal.Duration,
) -> @mailbox.FoeResponse raise @hal.EcError {
for attempt = 0; attempt < max_stale_retries + 1; attempt = attempt + 1 {
let response_raw = mailbox_recv_rmsm(nic, station, mb_config, rmsm, timeout)
let (mbh, resp) = @mailbox.decode_foe_response(response_raw)
if rmsm.validate_response(mbh.counter) {
return resp
}
}
raise @hal.EcError::MailboxProtocol("RMSM: too many stale frames received")
}
///|
/// Receive and validate a FoE response (AP addressing).
/// Discards stale/repeat frames via RMSM counter validation.
fn[N : @hal.Nic] recv_foe_validated_ap(
nic : N,
position : UInt,
mb_config : @mailbox.MailboxConfig,
rmsm : @mailbox.Rmsm,
timeout : @hal.Duration,
) -> @mailbox.FoeResponse raise @hal.EcError {
for attempt = 0; attempt < max_stale_retries + 1; attempt = attempt + 1 {
let response_raw = mailbox_recv_ap_rmsm(
nic, position, mb_config, rmsm, timeout,
)
let (mbh, resp) = @mailbox.decode_foe_response(response_raw)
if rmsm.validate_response(mbh.counter) {
return resp
}
}
raise @hal.EcError::MailboxProtocol("RMSM: too many stale frames received")
}
///|
/// Raise an EcError from a FoE error response.
fn foe_raise_error(
code : @mailbox.FoeErrorCode,
text : String,
) -> Unit raise @hal.EcError {
let suffix = if text.length() > 0 { " - " + text } else { "" }
raise @hal.EcError::MailboxProtocol(
"FoE error: " + @debug.to_string(code) + suffix,
)
}
///|
/// Patch the mailbox counter field (byte 5, bits 6:4) in a raw mailbox frame.
fn patch_mailbox_counter(frame : Bytes, new_counter : Byte) -> Bytes {
Bytes::makei(frame.length(), fn(i) {
if i == 5 {
let type_bits = frame[5].to_int() & 0x0F
let counter_bits = (new_counter.to_int() & 0x07) << 4
(type_bits | counter_bits).to_byte()
} else {
frame[i]
}
})
}
///|
/// Handle a FoE response that may be Busy, retrying the given resend closure.
/// Returns the actual response (Data or Ack) when a non-Busy response arrives.
fn[N : @hal.Nic] foe_handle_busy(
nic : N,
station : UInt,
mb_config : @mailbox.MailboxConfig,
rmsm : @mailbox.Rmsm,
last_frame : Bytes,
initial_resp : @mailbox.FoeResponse,
timeout : @hal.Duration,
) -> @mailbox.FoeResponse raise @hal.EcError {
let mut resp = initial_resp
for retry = 0; retry < foe_max_busy_retries; retry = retry + 1 {
guard resp is @mailbox.FoeResponse::FoeBusy else { return resp }
// Begin a new retry transaction so RMSM expects the next counter
let new_cnt = rmsm.begin_retry_transaction()
let patched = patch_mailbox_counter(last_frame, new_cnt)
mailbox_send(nic, station, mb_config, patched, timeout)
resp = recv_foe_validated_fp(nic, station, mb_config, rmsm, timeout)
}
raise @hal.EcError::MailboxProtocol("FoE: too many Busy retries")
}
///|
/// Handle a FoE response that may be Busy (AP addressing).
fn[N : @hal.Nic] foe_handle_busy_ap(
nic : N,
position : UInt,
mb_config : @mailbox.MailboxConfig,
rmsm : @mailbox.Rmsm,
last_frame : Bytes,
initial_resp : @mailbox.FoeResponse,
timeout : @hal.Duration,
) -> @mailbox.FoeResponse raise @hal.EcError {
let mut resp = initial_resp
for retry = 0; retry < foe_max_busy_retries; retry = retry + 1 {
guard resp is @mailbox.FoeResponse::FoeBusy else { return resp }
let new_cnt = rmsm.begin_retry_transaction()
let patched = patch_mailbox_counter(last_frame, new_cnt)
mailbox_send_ap(nic, position, mb_config, patched, timeout)
resp = recv_foe_validated_ap(nic, position, mb_config, rmsm, timeout)
}
raise @hal.EcError::MailboxProtocol("FoE: too many Busy retries")
}
///|
/// FoE Download (write file to slave) using FP addressing.
///
/// Flow: Write(filename) → Ack(0) → Data(1) → Ack(1) → ... → Data(N) → Ack(N)
/// EOF is indicated when the last Data segment is shorter than max_data_per_segment.
/// If the data is an exact multiple of max_data_per_segment, an empty Data segment
/// is sent to signal EOF.
pub fn[N : @hal.Nic] foe_download(
nic : N,
station : UInt,
mb_config : @mailbox.MailboxConfig,
rmsm : @mailbox.Rmsm,
filename : String,
password : UInt,
data : Bytes,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
// Max payload per FoE Data segment: SM out size minus mailbox header (6) minus FoE header (6)
let max_data = mb_config.out_size.reinterpret_as_int() - 12
if max_data <= 0 {
raise @hal.EcError::MailboxProtocol(
"FoE: mailbox too small for FoE transfer",
)
}
// Step 1: Send Write request
let counter0 = rmsm.begin_transaction()
let write_frame = @mailbox.encode_foe_write(counter0, password, filename)
mailbox_send(nic, station, mb_config, write_frame, timeout)
// Expect Ack(0)
let resp0 = recv_foe_validated_fp(nic, station, mb_config, rmsm, timeout)
let resp0 = foe_handle_busy(
nic, station, mb_config, rmsm, write_frame, resp0, timeout,
)
match resp0 {
@mailbox.FoeResponse::FoeAck(packet_number=pn) =>
if pn != 0U {
raise @hal.EcError::MailboxProtocol(
"FoE download: expected Ack(0), got Ack(" + pn.to_string() + ")",
)
}
@mailbox.FoeResponse::FoeError(code=c, text=t) => foe_raise_error(c, t)
_ =>
raise @hal.EcError::MailboxProtocol(
"FoE download: expected Ack, got " + @debug.to_string(resp0),
)
}
// Step 2: Send Data segments
let total = data.length()
let mut offset = 0
let mut packet_number = 1U
for ;; {
let chunk_end = if offset + max_data > total {
total
} else {
offset + max_data
}
let chunk = Bytes::makei(chunk_end - offset, fn(i) { data[offset + i] })
let counter_n = rmsm.begin_transaction()
let data_frame = @mailbox.encode_foe_data(counter_n, packet_number, chunk)
mailbox_send(nic, station, mb_config, data_frame, timeout)
let resp_n = recv_foe_validated_fp(nic, station, mb_config, rmsm, timeout)
let resp_n = foe_handle_busy(
nic, station, mb_config, rmsm, data_frame, resp_n, timeout,
)
match resp_n {
@mailbox.FoeResponse::FoeAck(packet_number=ack_pn) =>
if ack_pn != packet_number {
raise @hal.EcError::MailboxProtocol(
"FoE download: expected Ack(" +
packet_number.to_string() +
"), got Ack(" +
ack_pn.to_string() +
")",
)
}
@mailbox.FoeResponse::FoeError(code=c, text=t) => foe_raise_error(c, t)
_ =>
raise @hal.EcError::MailboxProtocol(
"FoE download: expected Ack, got " + @debug.to_string(resp_n),
)
}
offset = chunk_end
// EOF: last segment was shorter than max_data (or was the trailing empty segment)
if chunk.length() < max_data {
break
}
packet_number = packet_number + 1U
// Exact multiple: send empty Data to signal EOF on next iteration
}
}
///|
/// FoE Upload (read file from slave) using FP addressing.
///
/// Flow: Read(filename) → Data(1) → Ack(1) → Data(2) → Ack(2) → ... → Data(N,short) → Ack(N)
/// Returns the assembled file data.
pub fn[N : @hal.Nic] foe_upload(
nic : N,
station : UInt,
mb_config : @mailbox.MailboxConfig,
rmsm : @mailbox.Rmsm,
filename : String,
password : UInt,
timeout : @hal.Duration,
) -> Bytes raise @hal.EcError {
// Max payload per FoE Data segment from slave: SM in size minus headers
let max_data = mb_config.in_size.reinterpret_as_int() - 12
if max_data <= 0 {
raise @hal.EcError::MailboxProtocol(
"FoE: mailbox too small for FoE transfer",
)
}
// Step 1: Send Read request
let counter0 = rmsm.begin_transaction()
let read_frame = @mailbox.encode_foe_read(counter0, password, filename)
mailbox_send(nic, station, mb_config, read_frame, timeout)
// Step 2: Receive Data segments
let result = Buffer(size_hint=1024)
let mut expected_pn : UInt = 1U
for ;; {
let resp = recv_foe_validated_fp(nic, station, mb_config, rmsm, timeout)
let resp = foe_handle_busy(
nic, station, mb_config, rmsm, read_frame, resp, timeout,
)
match resp {
@mailbox.FoeResponse::FoeData(packet_number=pn, data=segment) => {
if pn != expected_pn {
raise @hal.EcError::MailboxProtocol(
"FoE upload: expected Data(" +
expected_pn.to_string() +
"), got Data(" +
pn.to_string() +
")",
)
}
for i = 0; i < segment.length(); i = i + 1 {
result.write_byte(segment[i])
}
// Send Ack
let counter_ack = rmsm.begin_transaction()
let ack_frame = @mailbox.encode_foe_ack(counter_ack, pn)
mailbox_send(nic, station, mb_config, ack_frame, timeout)
// EOF: segment shorter than max_data
if segment.length() < max_data {
break
}
expected_pn = expected_pn + 1U
}
@mailbox.FoeResponse::FoeError(code=c, text=t) => foe_raise_error(c, t)
_ =>
raise @hal.EcError::MailboxProtocol(
"FoE upload: expected Data, got " + @debug.to_string(resp),
)
}
}
result.to_bytes()
}
///|
/// FoE Download (write file to slave) using AP addressing.
pub fn[N : @hal.Nic] foe_download_ap(
nic : N,
position : UInt,
mb_config : @mailbox.MailboxConfig,
rmsm : @mailbox.Rmsm,
filename : String,
password : UInt,
data : Bytes,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
let max_data = mb_config.out_size.reinterpret_as_int() - 12
if max_data <= 0 {
raise @hal.EcError::MailboxProtocol(
"FoE: mailbox too small for FoE transfer",
)
}
let counter0 = rmsm.begin_transaction()
let write_frame = @mailbox.encode_foe_write(counter0, password, filename)
mailbox_send_ap(nic, position, mb_config, write_frame, timeout)
let resp0 = recv_foe_validated_ap(nic, position, mb_config, rmsm, timeout)
let resp0 = foe_handle_busy_ap(
nic, position, mb_config, rmsm, write_frame, resp0, timeout,
)
match resp0 {
@mailbox.FoeResponse::FoeAck(packet_number=pn) =>
if pn != 0U {
raise @hal.EcError::MailboxProtocol(
"FoE download: expected Ack(0), got Ack(" + pn.to_string() + ")",
)
}
@mailbox.FoeResponse::FoeError(code=c, text=t) => foe_raise_error(c, t)
_ =>
raise @hal.EcError::MailboxProtocol(
"FoE download: expected Ack, got " + @debug.to_string(resp0),
)
}
let total = data.length()
let mut offset = 0
let mut packet_number = 1U
for ;; {
let chunk_end = if offset + max_data > total {
total
} else {
offset + max_data
}
let chunk = Bytes::makei(chunk_end - offset, fn(i) { data[offset + i] })
let counter_n = rmsm.begin_transaction()
let data_frame = @mailbox.encode_foe_data(counter_n, packet_number, chunk)
mailbox_send_ap(nic, position, mb_config, data_frame, timeout)
let resp_n = recv_foe_validated_ap(nic, position, mb_config, rmsm, timeout)
let resp_n = foe_handle_busy_ap(
nic, position, mb_config, rmsm, data_frame, resp_n, timeout,
)
match resp_n {
@mailbox.FoeResponse::FoeAck(packet_number=ack_pn) =>
if ack_pn != packet_number {
raise @hal.EcError::MailboxProtocol(
"FoE download: expected Ack(" +
packet_number.to_string() +
"), got Ack(" +
ack_pn.to_string() +
")",
)
}
@mailbox.FoeResponse::FoeError(code=c, text=t) => foe_raise_error(c, t)
_ =>
raise @hal.EcError::MailboxProtocol(
"FoE download: expected Ack, got " + @debug.to_string(resp_n),
)
}
offset = chunk_end
if chunk.length() < max_data {
break
}
packet_number = packet_number + 1U
}
}
///|
/// FoE Upload (read file from slave) using AP addressing.
/// Returns the assembled file data.
pub fn[N : @hal.Nic] foe_upload_ap(
nic : N,
position : UInt,
mb_config : @mailbox.MailboxConfig,
rmsm : @mailbox.Rmsm,
filename : String,
password : UInt,
timeout : @hal.Duration,
) -> Bytes raise @hal.EcError {
let max_data = mb_config.in_size.reinterpret_as_int() - 12
if max_data <= 0 {
raise @hal.EcError::MailboxProtocol(
"FoE: mailbox too small for FoE transfer",
)
}
let counter0 = rmsm.begin_transaction()
let read_frame = @mailbox.encode_foe_read(counter0, password, filename)
mailbox_send_ap(nic, position, mb_config, read_frame, timeout)
let result = Buffer(size_hint=1024)
let mut expected_pn : UInt = 1U
for ;; {
let resp = recv_foe_validated_ap(nic, position, mb_config, rmsm, timeout)
let resp = foe_handle_busy_ap(
nic, position, mb_config, rmsm, read_frame, resp, timeout,
)
match resp {
@mailbox.FoeResponse::FoeData(packet_number=pn, data=segment) => {
if pn != expected_pn {
raise @hal.EcError::MailboxProtocol(
"FoE upload: expected Data(" +
expected_pn.to_string() +
"), got Data(" +
pn.to_string() +
")",
)
}
for i = 0; i < segment.length(); i = i + 1 {
result.write_byte(segment[i])
}
let counter_ack = rmsm.begin_transaction()
let ack_frame = @mailbox.encode_foe_ack(counter_ack, pn)
mailbox_send_ap(nic, position, mb_config, ack_frame, timeout)
if segment.length() < max_data {
break
}
expected_pn = expected_pn + 1U
}
@mailbox.FoeResponse::FoeError(code=c, text=t) => foe_raise_error(c, t)
_ =>
raise @hal.EcError::MailboxProtocol(
"FoE upload: expected Data, got " + @debug.to_string(resp),
)
}
}
result.to_bytes()
}