///|
/// FoE (File over EtherCAT) frame encoding and decoding per ETG.1000.6 §5.8.
///
/// FoE header (6 bytes, follows 6-byte mailbox header):
/// Byte 0: OpCode
/// Byte 1: Reserved (0x00)
/// Bytes 2-5: context-dependent union:
/// - Read/Write: Password (LE u32)
/// - Data/Ack: Packet Number (LE u32)
/// - Error: Error Code (LE u32)
/// Bytes 6+: payload (filename for Read/Write, file data for Data,
/// error text for Error, nothing for Ack/Busy)
///|
let foe_header_len : Int = 6
///|
/// FoE OpCode values.
pub(all) enum FoeOpCode {
/// Host→Slave: request to upload (read) a file.
Read
/// Host→Slave: request to download (write) a file.
Write
/// Data segment (bidirectional).
Data
/// Acknowledge receipt of a data segment (bidirectional).
Ack
/// Error indication (bidirectional).
Error
/// Slave→Host: busy, retry the last request.
Busy
} derive(Eq, Debug)
///|
pub fn FoeOpCode::to_byte(self : FoeOpCode) -> Byte {
match self {
Read => b'\x01'
Write => b'\x02'
Data => b'\x03'
Ack => b'\x04'
Error => b'\x05'
Busy => b'\x06'
}
}
///|
pub fn FoeOpCode::from_byte(b : Byte) -> FoeOpCode raise @hal.EcError {
match b {
b'\x01' => Read
b'\x02' => Write
b'\x03' => Data
b'\x04' => Ack
b'\x05' => Error
b'\x06' => Busy
_ =>
raise @hal.EcError::MailboxProtocol(
"unknown FoE opcode: " + b.to_int().to_string(),
)
}
}
///|
/// FoE error codes per ETG.1000.6.
pub(all) enum FoeErrorCode {
NotDefined
NotFound
AccessDenied
DiskFull
Illegal
PacketNumberWrong
AlreadyExists
NoUser
BootstrapOnly
NotInBootstrap
NoRights
ProgramError
InvalidChecksum
InvalidFirmware
FlashError
Unknown(UInt)
} derive(Eq, Debug)
///|
pub fn FoeErrorCode::from_uint(code : UInt) -> FoeErrorCode {
match code {
0x8000U => NotDefined
0x8001U => NotFound
0x8002U => AccessDenied
0x8003U => DiskFull
0x8004U => Illegal
0x8005U => PacketNumberWrong
0x8006U => AlreadyExists
0x8007U => NoUser
0x8008U => BootstrapOnly
0x8009U => NotInBootstrap
0x800AU => NoRights
0x800BU => ProgramError
0x800CU => InvalidChecksum
0x800DU => InvalidFirmware
0x8011U => FlashError
_ => Unknown(code)
}
}
///|
pub fn FoeErrorCode::to_uint(self : FoeErrorCode) -> UInt {
match self {
NotDefined => 0x8000U
NotFound => 0x8001U
AccessDenied => 0x8002U
DiskFull => 0x8003U
Illegal => 0x8004U
PacketNumberWrong => 0x8005U
AlreadyExists => 0x8006U
NoUser => 0x8007U
BootstrapOnly => 0x8008U
NotInBootstrap => 0x8009U
NoRights => 0x800AU
ProgramError => 0x800BU
InvalidChecksum => 0x800CU
InvalidFirmware => 0x800DU
FlashError => 0x8011U
Unknown(code) => code
}
}
///|
/// Decoded FoE response frame.
pub(all) enum FoeResponse {
/// Data segment with packet number and payload.
FoeData(packet_number~ : UInt, data~ : Bytes)
/// Acknowledgement with packet number.
FoeAck(packet_number~ : UInt)
/// Error with code and optional text.
FoeError(code~ : FoeErrorCode, text~ : String)
/// Busy: slave requests host to re-send.
FoeBusy
} derive(Eq, Debug)
///|
/// Encode a FoE Read request (upload: host wants to read file from slave).
pub fn encode_foe_read(
counter : Byte,
password : UInt,
filename : String,
) -> Bytes {
let name_bytes = filename.to_array().map(fn(c) { c.to_int().to_byte() })
let payload_len = foe_header_len + name_bytes.length()
let buf = Buffer(size_hint=mailbox_header_len + payload_len)
let mbh : MailboxHeader = {
length: payload_len.reinterpret_as_uint(),
address: 0U,
channel: b'\x00',
priority: b'\x00',
mb_type: FoE,
counter,
}
let hdr = mbh.to_bytes()
for i = 0; i < hdr.length(); i = i + 1 {
buf.write_byte(hdr[i])
}
// FoE header: OpCode=Read, Reserved=0
buf.write_byte(FoeOpCode::Read.to_byte())
buf.write_byte(b'\x00')
// Password (LE u32)
write_u32_le(buf, password)
// Filename (ASCII, no null terminator)
for b in name_bytes {
buf.write_byte(b)
}
buf.to_bytes()
}
///|
/// Encode a FoE Write request (download: host wants to write file to slave).
pub fn encode_foe_write(
counter : Byte,
password : UInt,
filename : String,
) -> Bytes {
let name_bytes = filename.to_array().map(fn(c) { c.to_int().to_byte() })
let payload_len = foe_header_len + name_bytes.length()
let buf = Buffer(size_hint=mailbox_header_len + payload_len)
let mbh : MailboxHeader = {
length: payload_len.reinterpret_as_uint(),
address: 0U,
channel: b'\x00',
priority: b'\x00',
mb_type: FoE,
counter,
}
let hdr = mbh.to_bytes()
for i = 0; i < hdr.length(); i = i + 1 {
buf.write_byte(hdr[i])
}
// FoE header: OpCode=Write, Reserved=0
buf.write_byte(FoeOpCode::Write.to_byte())
buf.write_byte(b'\x00')
// Password (LE u32)
write_u32_le(buf, password)
// Filename (ASCII, no null terminator)
for b in name_bytes {
buf.write_byte(b)
}
buf.to_bytes()
}
///|
/// Encode a FoE Data segment (used in both upload and download transfers).
pub fn encode_foe_data(
counter : Byte,
packet_number : UInt,
data : Bytes,
) -> Bytes {
let payload_len = foe_header_len + data.length()
let buf = Buffer(size_hint=mailbox_header_len + payload_len)
let mbh : MailboxHeader = {
length: payload_len.reinterpret_as_uint(),
address: 0U,
channel: b'\x00',
priority: b'\x00',
mb_type: FoE,
counter,
}
let hdr = mbh.to_bytes()
for i = 0; i < hdr.length(); i = i + 1 {
buf.write_byte(hdr[i])
}
buf.write_byte(FoeOpCode::Data.to_byte())
buf.write_byte(b'\x00')
write_u32_le(buf, packet_number)
for i = 0; i < data.length(); i = i + 1 {
buf.write_byte(data[i])
}
buf.to_bytes()
}
///|
/// Encode a FoE Ack frame.
pub fn encode_foe_ack(counter : Byte, packet_number : UInt) -> Bytes {
let payload_len = foe_header_len
let buf = Buffer(size_hint=mailbox_header_len + payload_len)
let mbh : MailboxHeader = {
length: payload_len.reinterpret_as_uint(),
address: 0U,
channel: b'\x00',
priority: b'\x00',
mb_type: FoE,
counter,
}
let hdr = mbh.to_bytes()
for i = 0; i < hdr.length(); i = i + 1 {
buf.write_byte(hdr[i])
}
buf.write_byte(FoeOpCode::Ack.to_byte())
buf.write_byte(b'\x00')
write_u32_le(buf, packet_number)
buf.to_bytes()
}
///|
/// Decode a FoE response from a raw mailbox frame.
pub fn decode_foe_response(
data : Bytes,
) -> (MailboxHeader, FoeResponse) raise @hal.EcError {
if data.length() < mailbox_header_len + 2 {
raise @hal.EcError::MailboxProtocol("FoE response frame too short")
}
let mbh = MailboxHeader::from_bytes(data)
if mbh.mb_type != FoE {
raise @hal.EcError::MailboxProtocol(
"expected FoE mailbox, got " + @debug.to_string(mbh.mb_type),
)
}
let payload_len = mbh.length.reinterpret_as_int()
if payload_len < 2 {
raise @hal.EcError::MailboxProtocol("FoE response payload too short")
}
let opcode = FoeOpCode::from_byte(data[mailbox_header_len])
match opcode {
Data => {
if payload_len < foe_header_len {
raise @hal.EcError::MailboxProtocol("FoE Data frame too short")
}
let pn = read_u32_le_bytes(data, mailbox_header_len + 2)
let data_start = mailbox_header_len + foe_header_len
let data_end = mailbox_header_len + payload_len
let segment = if data_end > data_start && data.length() >= data_end {
Bytes::makei(data_end - data_start, fn(i) { data[data_start + i] })
} else {
Bytes::new(0)
}
(mbh, FoeData(packet_number=pn, data=segment))
}
Ack => {
let pn = if payload_len >= foe_header_len {
read_u32_le_bytes(data, mailbox_header_len + 2)
} else {
0U
}
(mbh, FoeAck(packet_number=pn))
}
Error => {
let error_code = if payload_len >= foe_header_len {
FoeErrorCode::from_uint(read_u32_le_bytes(data, mailbox_header_len + 2))
} else {
NotDefined
}
let text = if payload_len > foe_header_len &&
data.length() >= mailbox_header_len + payload_len {
let start = mailbox_header_len + foe_header_len
let end = mailbox_header_len + payload_len
let chars : Array[Char] = []
for i = start; i < end; i = i + 1 {
chars.push(data[i].to_int().unsafe_to_char())
}
String::from_array(chars)
} else {
""
}
(mbh, FoeError(code=error_code, text~))
}
Busy => (mbh, FoeBusy)
_ =>
raise @hal.EcError::MailboxProtocol(
"unexpected FoE opcode in response: " + @debug.to_string(opcode),
)
}
}
///|
fn write_u32_le(buf : @buffer.Buffer, value : UInt) -> Unit {
buf.write_byte((value & 0xFFU).to_byte())
buf.write_byte(((value >> 8) & 0xFFU).to_byte())
buf.write_byte(((value >> 16) & 0xFFU).to_byte())
buf.write_byte(((value >> 24) & 0xFFU).to_byte())
}
///|
fn read_u32_le_bytes(data : Bytes, offset : Int) -> UInt {
if data.length() < offset + 4 {
return 0U
}
data[offset].to_uint() |
(data[offset + 1].to_uint() << 8) |
(data[offset + 2].to_uint() << 16) |
(data[offset + 3].to_uint() << 24)
}