use crate::utils::ffi;
use std::ffi::CString;
use std::os::raw::{c_void, c_int};
use std::net::UdpSocket;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
const DEFAULT_TIMEOUT: c_int = 5_000_000;
pub const DEFAULT_PORT: u16 = 0x88A4;
#[derive(Debug, Default, Clone, Copy)]
pub struct MailboxGatewayStats {
pub rx_packets: u64,
pub tx_packets: u64,
pub rx_bytes: u64,
pub tx_bytes: u64,
pub master_od_requests: u64,
pub slave_requests: u64,
pub drop_malformed: u64,
pub drop_timeout: u64,
}
#[derive(Default)]
struct AtomicStats {
rx_packets: AtomicU64,
tx_packets: AtomicU64,
rx_bytes: AtomicU64,
tx_bytes: AtomicU64,
master_od_requests: AtomicU64,
slave_requests: AtomicU64,
drop_malformed: AtomicU64,
drop_timeout: AtomicU64,
}
impl AtomicStats {
fn snapshot(&self) -> MailboxGatewayStats {
MailboxGatewayStats {
rx_packets: self.rx_packets.load(Ordering::Relaxed),
tx_packets: self.tx_packets.load(Ordering::Relaxed),
rx_bytes: self.rx_bytes.load(Ordering::Relaxed),
tx_bytes: self.tx_bytes.load(Ordering::Relaxed),
master_od_requests: self.master_od_requests.load(Ordering::Relaxed),
slave_requests: self.slave_requests.load(Ordering::Relaxed),
drop_malformed: self.drop_malformed.load(Ordering::Relaxed),
drop_timeout: self.drop_timeout.load(Ordering::Relaxed),
}
}
fn reset(&self) {
self.rx_packets.store(0, Ordering::Relaxed);
self.tx_packets.store(0, Ordering::Relaxed);
self.rx_bytes.store(0, Ordering::Relaxed);
self.tx_bytes.store(0, Ordering::Relaxed);
self.master_od_requests.store(0, Ordering::Relaxed);
self.slave_requests.store(0, Ordering::Relaxed);
self.drop_malformed.store(0, Ordering::Relaxed);
self.drop_timeout.store(0, Ordering::Relaxed);
}
}
pub struct MailboxGatewayService {
master_index: u16,
port: u16,
running: Arc<AtomicBool>,
listener_thread: Option<thread::JoinHandle<()>>,
stop_socket: Option<Arc<UdpSocket>>,
_mailbox_counter: std::sync::atomic::AtomicU8,
stats: Arc<AtomicStats>,
}
impl MailboxGatewayService {
pub fn new(master_index: u16) -> Self {
Self {
master_index,
port: DEFAULT_PORT + master_index.saturating_sub(1),
running: Arc::new(AtomicBool::new(false)),
listener_thread: None,
stop_socket: None,
_mailbox_counter: std::sync::atomic::AtomicU8::new(0),
stats: Arc::new(AtomicStats::default()),
}
}
pub fn get_statistics(&self) -> MailboxGatewayStats {
self.stats.snapshot()
}
pub fn reset_statistics(&self) {
self.stats.reset();
}
pub fn start(&mut self) -> Result<(), String> {
if self.running.load(Ordering::SeqCst) {
return Ok(()); }
self.stop_internal();
let socket = self.bind_udp_port(self.port)?;
let actual_port = socket.local_addr()
.map(|a| a.port())
.unwrap_or(self.port);
self.port = actual_port;
let socket = Arc::new(socket);
self.stop_socket = Some(socket.clone());
self.running.store(true, Ordering::SeqCst);
let running = self.running.clone();
let master_index = self.master_index;
let stats = self.stats.clone();
self.listener_thread = Some(thread::spawn(move || {
listen_loop(socket, running, master_index, stats);
}));
Ok(())
}
pub fn stop(&mut self) {
self.stop_internal();
}
pub fn is_running(&self) -> bool {
self.running.load(Ordering::SeqCst)
}
pub fn port(&self) -> u16 {
self.port
}
pub fn set_port(&mut self, port: u16) {
if !self.running.load(Ordering::SeqCst) {
self.port = port;
}
}
fn stop_internal(&mut self) {
if !self.running.load(Ordering::SeqCst) && self.listener_thread.is_none() {
return;
}
self.running.store(false, Ordering::SeqCst);
if let Some(ref socket) = self.stop_socket {
let _ = UdpSocket::bind("0.0.0.0:0")
.and_then(|s| s.send_to(&[0], socket.local_addr().unwrap_or_else(|_| {
std::net::SocketAddr::from(([127, 0, 0, 1], self.port))
})));
}
self.stop_socket = None;
if let Some(handle) = self.listener_thread.take() {
let _ = handle.join();
}
}
fn bind_udp_port(&self, preferred_port: u16) -> Result<UdpSocket, String> {
for i in 0..10u16 {
let port = preferred_port.wrapping_add(i);
match UdpSocket::bind(format!("0.0.0.0:{}", port)) {
Ok(socket) => return Ok(socket),
Err(_) => continue,
}
}
UdpSocket::bind("0.0.0.0:0")
.map_err(|e| format!("无法绑定 UDP 端口: {}", e))
}
}
impl Drop for MailboxGatewayService {
fn drop(&mut self) {
self.stop_internal();
}
}
fn listen_loop(
socket: Arc<UdpSocket>,
running: Arc<AtomicBool>,
master_index: u16,
stats: Arc<AtomicStats>,
) {
let mut buf = [0u8; 2048];
let _ = socket.set_read_timeout(Some(std::time::Duration::from_millis(500)));
while running.load(Ordering::SeqCst) {
match socket.recv_from(&mut buf) {
Ok((len, remote_addr)) => {
if !running.load(Ordering::SeqCst) { break; }
stats.rx_packets.fetch_add(1, Ordering::Relaxed);
stats.rx_bytes.fetch_add(len as u64, Ordering::Relaxed);
if len < 9 {
stats.drop_malformed.fetch_add(1, Ordering::Relaxed);
continue;
}
if let Some(response) = process_request(&buf[..len], master_index, &stats) {
match socket.send_to(&response, remote_addr) {
Ok(sent) => {
stats.tx_packets.fetch_add(1, Ordering::Relaxed);
stats.tx_bytes.fetch_add(sent as u64, Ordering::Relaxed);
}
Err(_) => {
stats.drop_timeout.fetch_add(1, Ordering::Relaxed);
}
}
}
}
Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut
|| e.kind() == std::io::ErrorKind::WouldBlock => {
continue; }
Err(_) => {
if running.load(Ordering::SeqCst) {
thread::sleep(std::time::Duration::from_millis(10));
} else {
break;
}
}
}
}
}
fn process_request(data: &[u8], master_index: u16, stats: &Arc<AtomicStats>) -> Option<Vec<u8>> {
if data.len() < 8 {
stats.drop_malformed.fetch_add(1, Ordering::Relaxed);
return None;
}
let ecat_header = u16::from_le_bytes([data[0], data[1]]);
let ecat_data_type = ((ecat_header >> 12) & 0x0F) as u8;
if ecat_data_type != 0x05 {
stats.drop_malformed.fetch_add(1, Ordering::Relaxed);
return None;
}
let mb_length = u16::from_le_bytes([data[2], data[3]]) as usize;
let address = u16::from_le_bytes([data[4], data[5]]);
let _channel_prio = data[6];
let type_cnt_res = data[7];
let mailbox_type = (type_cnt_res >> 4) & 0x0F;
let _mailbox_cnt = (type_cnt_res >> 1) & 0x07;
let mb_data_start = 8;
let avail = data.len().saturating_sub(mb_data_start);
let actual_len = mb_length.min(avail);
let mailbox_data = &data[mb_data_start..mb_data_start + actual_len];
let response_data = if address == 0x0000 {
stats.master_od_requests.fetch_add(1, Ordering::Relaxed);
process_master_request(master_index, mailbox_type, mailbox_data)
} else {
stats.slave_requests.fetch_add(1, Ordering::Relaxed);
process_slave_request(master_index, address, mailbox_type, mailbox_data)
};
response_data.map(|resp| build_response_frame(address, mailbox_type, &resp))
}
fn process_master_request(master_index: u16, mailbox_type: u8, mailbox_data: &[u8]) -> Option<Vec<u8>> {
if mailbox_type != 0x03 {
return Some(build_coe_abort_response(0x06010000)); }
if mailbox_data.len() < 6 { return None; }
let coe_type = (mailbox_data[1] >> 4) & 0x0F;
if coe_type != 0x02 { return None; }
let sdo_command = mailbox_data[2];
let index = u16::from_le_bytes([mailbox_data[3], mailbox_data[4]]);
let subindex = mailbox_data[5];
let is_upload = (sdo_command & 0x40) != 0;
let is_download = (sdo_command & 0x20) != 0 && (sdo_command & 0x40) == 0;
if is_upload {
let data = read_master_od_object(master_index, index, subindex);
match data {
Some(d) => Some(build_coe_upload_response(index, subindex, &d)),
None => Some(build_coe_abort_response(0x06020000)), }
} else if is_download {
let data_offset = 6;
if mailbox_data.len() > data_offset {
let write_data = &mailbox_data[data_offset..];
write_master_od_object(master_index, index, subindex, write_data)
} else {
Some(build_coe_abort_response(0x05040001))
}
} else {
Some(build_coe_abort_response(0x05040001)) }
}
fn read_master_od_object(master_index: u16, index: u16, subindex: u8) -> Option<Vec<u8>> {
match index {
0x1000 => Some(0x000011A4u32.to_le_bytes().to_vec()),
0x1008 => Some(b"Darra EtherCAT Master".to_vec()),
0x1009 => Some(b"1.0".to_vec()),
0x100A => {
let ptr = unsafe { ffi::GetDllVersionInfo() };
if ptr.is_null() { return Some(b"0.0.0.0".to_vec()); }
let info = unsafe { std::ptr::read_unaligned(ptr) };
let ver = format!("{}.{}.{}.{}", {info.major}, {info.minor}, {info.patch}, {info.build});
Some(ver.into_bytes())
}
0x1018 => {
match subindex {
0 => Some(vec![4]),
1 => Some(0x00001164u32.to_le_bytes().to_vec()),
2 => Some(0x00000001u32.to_le_bytes().to_vec()),
3 => {
let ptr = unsafe { ffi::GetDllVersionInfo() };
if ptr.is_null() { return Some(vec![0; 4]); }
let info = unsafe { std::ptr::read_unaligned(ptr) };
let rev = ((info.major as u32) << 16) | (info.minor as u32);
Some(rev.to_le_bytes().to_vec())
}
4 => Some(0u32.to_le_bytes().to_vec()),
_ => None,
}
}
0xF120 => {
let mut diag = ffi::MasterDiagData {
cyclic_lost_frames: 0, acyclic_lost_frames: 0,
cyclic_frames_per_sec: 0, acyclic_frames_per_sec: 0,
master_state: 0,
};
unsafe { ffi::GetMasterDiagData(master_index, &mut diag) };
match subindex {
0 => Some(vec![16]),
1 => Some(diag.cyclic_lost_frames.to_le_bytes().to_vec()),
2 => Some(diag.acyclic_lost_frames.to_le_bytes().to_vec()),
3 => Some(diag.cyclic_frames_per_sec.to_le_bytes().to_vec()),
4 => Some(diag.acyclic_frames_per_sec.to_le_bytes().to_vec()),
5 => Some(diag.master_state.to_le_bytes().to_vec()),
_ => Some(vec![0; 4]),
}
}
_ => None,
}
}
fn write_master_od_object(master_index: u16, index: u16, subindex: u8, data: &[u8]) -> Option<Vec<u8>> {
match index {
0xF120 => {
match subindex {
5 => {
if data.len() >= 4 {
let val = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
unsafe { ffi::SetDiagnosticsEnabled(master_index, val as i32) };
Some(build_coe_download_response(index, subindex))
} else {
Some(build_coe_abort_response(0x06070010)) }
}
_ => Some(build_coe_abort_response(0x06010002)), }
}
0x1000 | 0x1008 | 0x1009 | 0x100A | 0x1018 => {
Some(build_coe_abort_response(0x06010002)) }
_ => Some(build_coe_abort_response(0x06010001)), }
}
fn process_slave_request(
master_index: u16,
address: u16,
mailbox_type: u8,
mailbox_data: &[u8],
) -> Option<Vec<u8>> {
match mailbox_type {
0x03 => process_slave_coe_request(master_index, address, mailbox_data),
0x04 => process_slave_foe_request(master_index, address, mailbox_data),
0x05 => process_slave_soe_request(master_index, address, mailbox_data),
0x0F => process_slave_voe_request(master_index, address, mailbox_data),
_ => Some(build_coe_abort_response(0x06010000)), }
}
fn process_slave_coe_request(
master_index: u16,
address: u16,
mailbox_data: &[u8],
) -> Option<Vec<u8>> {
if mailbox_data.len() < 6 { return None; }
let sdo_command = mailbox_data[2];
let index = u16::from_le_bytes([mailbox_data[3], mailbox_data[4]]);
let subindex = mailbox_data[5];
let is_upload = (sdo_command & 0x40) != 0;
if is_upload {
let slave = crate::slave::Slave::new(master_index, address);
match slave.sdo_read(index, subindex, false) {
Ok(data) => Some(build_coe_upload_response(index, subindex, &data)),
Err(_) => Some(build_coe_abort_response(0x06090011)), }
} else {
let data_offset = 6;
if mailbox_data.len() > data_offset {
let write_data = &mailbox_data[data_offset..];
let slave = crate::slave::Slave::new(master_index, address);
match slave.sdo_write(index, subindex, false, write_data) {
Ok(_) => Some(build_coe_download_response(index, subindex)),
Err(_) => Some(build_coe_abort_response(0x06010002)), }
} else {
Some(build_coe_abort_response(0x05040001))
}
}
}
fn process_slave_soe_request(
master_index: u16,
address: u16,
mailbox_data: &[u8],
) -> Option<Vec<u8>> {
if mailbox_data.len() < 4 { return Some(build_soe_error_response(0, 0, 0x7001)); }
let op_code = mailbox_data[0] & 0x07;
let drive_no = (mailbox_data[1] >> 4) & 0x0F;
let element_flags = mailbox_data[1] & 0x0F;
let idn = u16::from_le_bytes([mailbox_data[2], mailbox_data[3]]);
match op_code {
1 => {
let mut data_ptr: *mut c_void = std::ptr::null_mut();
let mut data_size: c_int = 0;
let ret = unsafe {
ffi::SoERead(
master_index, address, drive_no, element_flags, idn,
&mut data_ptr, &mut data_size, DEFAULT_TIMEOUT,
)
};
if ret == 0 && !data_ptr.is_null() && data_size > 0 {
let data = unsafe {
std::slice::from_raw_parts(data_ptr as *const u8, data_size as usize)
}.to_vec();
unsafe { ffi::FreeMemory(data_ptr); }
Some(build_soe_response(drive_no, element_flags, idn, &data))
} else {
if !data_ptr.is_null() {
unsafe { ffi::FreeMemory(data_ptr); }
}
Some(build_soe_error_response(drive_no, idn, 0x7002))
}
}
2 => {
if mailbox_data.len() <= 4 {
return Some(build_soe_error_response(drive_no, idn, 0x7001));
}
let write_data = &mailbox_data[4..];
let ret = unsafe {
ffi::SoEWrite(
master_index, address, drive_no, element_flags, idn,
write_data.as_ptr(), write_data.len() as c_int, DEFAULT_TIMEOUT,
)
};
if ret == 0 {
Some(build_soe_response(drive_no, element_flags, idn, &[]))
} else {
Some(build_soe_error_response(drive_no, idn, 0x7002))
}
}
_ => Some(build_soe_error_response(drive_no, idn, 0x7001)), }
}
fn process_slave_foe_request(
master_index: u16,
address: u16,
mailbox_data: &[u8],
) -> Option<Vec<u8>> {
if mailbox_data.len() < 6 { return Some(build_foe_error_response(0x8001)); }
let op_code = mailbox_data[0];
let password = u32::from_le_bytes([
mailbox_data[2], mailbox_data[3], mailbox_data[4], mailbox_data[5],
]);
match op_code {
1 => {
let filename_bytes = &mailbox_data[6..];
let name_end = filename_bytes.iter().position(|&b| b == 0)
.unwrap_or(filename_bytes.len());
let filename_str = std::str::from_utf8(&filename_bytes[..name_end])
.unwrap_or("");
let c_filename = match CString::new(filename_str) {
Ok(s) => s,
Err(_) => return Some(build_foe_error_response(0x8002)),
};
let mut file_data: *mut c_void = std::ptr::null_mut();
let mut file_size: c_int = 0;
let ret = unsafe {
ffi::FOERead(
master_index, address, c_filename.as_ptr(),
password, &mut file_data, &mut file_size, DEFAULT_TIMEOUT,
)
};
if ret == 0 && !file_data.is_null() && file_size > 0 {
let data = unsafe {
std::slice::from_raw_parts(file_data as *const u8, file_size as usize)
}.to_vec();
unsafe { ffi::FreeMemory(file_data); }
Some(build_foe_data_response(&data))
} else {
if !file_data.is_null() {
unsafe { ffi::FreeMemory(file_data); }
}
Some(build_foe_error_response(0x8001))
}
}
2 => {
let payload = &mailbox_data[6..];
let name_end = payload.iter().position(|&b| b == 0)
.unwrap_or(0);
let filename_str = std::str::from_utf8(&payload[..name_end])
.unwrap_or("");
let c_filename = match CString::new(filename_str) {
Ok(s) => s,
Err(_) => return Some(build_foe_error_response(0x8002)),
};
let data_start = if name_end < payload.len() { name_end + 1 } else { payload.len() };
let file_data = &payload[data_start..];
let ret = unsafe {
ffi::FOEWrite(
master_index, address, c_filename.as_ptr(),
password, file_data.as_ptr() as *const c_void,
file_data.len() as c_int, DEFAULT_TIMEOUT,
)
};
if ret == 0 {
Some(build_foe_ack_response())
} else {
Some(build_foe_error_response(0x8001))
}
}
_ => Some(build_foe_error_response(0x8003)), }
}
fn process_slave_voe_request(
master_index: u16,
address: u16,
mailbox_data: &[u8],
) -> Option<Vec<u8>> {
if mailbox_data.len() < 6 { return Some(build_voe_error_response()); }
let _vendor_id = u32::from_le_bytes([
mailbox_data[0], mailbox_data[1], mailbox_data[2], mailbox_data[3],
]);
let _vendor_type = u16::from_le_bytes([mailbox_data[4], mailbox_data[5]]);
let ret_send = unsafe {
ffi::VOESendRaw(
master_index, address,
mailbox_data.as_ptr(), mailbox_data.len() as c_int,
DEFAULT_TIMEOUT,
)
};
if ret_send != 0 {
return Some(build_voe_error_response());
}
let mut recv_data: *mut c_void = std::ptr::null_mut();
let mut recv_size: c_int = 0;
let ret_recv = unsafe {
ffi::VOEReceiveRaw(
master_index, address,
&mut recv_data, &mut recv_size,
DEFAULT_TIMEOUT,
)
};
if ret_recv == 0 && !recv_data.is_null() && recv_size > 0 {
let data = unsafe {
std::slice::from_raw_parts(recv_data as *const u8, recv_size as usize)
}.to_vec();
unsafe { ffi::FreeMemory(recv_data); }
Some(data)
} else {
if !recv_data.is_null() {
unsafe { ffi::FreeMemory(recv_data); }
}
Some(build_voe_error_response())
}
}
fn build_response_frame(address: u16, mailbox_type: u8, data: &[u8]) -> Vec<u8> {
let mb_length = data.len() as u16;
let ecat_header: u16 = (mb_length + 6) & 0x07FF | (0x05 << 12);
let mut frame = Vec::with_capacity(8 + data.len());
frame.extend_from_slice(&ecat_header.to_le_bytes());
frame.extend_from_slice(&mb_length.to_le_bytes()); frame.extend_from_slice(&address.to_le_bytes()); frame.push(0x00); frame.push((mailbox_type << 4) | 0x02); frame.extend_from_slice(data);
frame
}
fn build_coe_upload_response(index: u16, subindex: u8, data: &[u8]) -> Vec<u8> {
let mut resp = Vec::with_capacity(6 + data.len());
resp.push(0x00);
resp.push(0x20); if data.len() <= 4 {
let size_indicator = (4 - data.len()) as u8;
resp.push(0x43 | (size_indicator << 2));
} else {
resp.push(0x41);
}
resp.extend_from_slice(&index.to_le_bytes());
resp.push(subindex);
if data.len() <= 4 {
resp.extend_from_slice(data);
for _ in data.len()..4 {
resp.push(0x00);
}
} else {
resp.extend_from_slice(&(data.len() as u32).to_le_bytes());
resp.extend_from_slice(data);
}
resp
}
fn build_coe_download_response(index: u16, subindex: u8) -> Vec<u8> {
let mut resp = Vec::with_capacity(6);
resp.push(0x00);
resp.push(0x20); resp.push(0x60);
resp.extend_from_slice(&index.to_le_bytes());
resp.push(subindex);
resp
}
fn build_coe_abort_response(abort_code: u32) -> Vec<u8> {
let mut resp = Vec::with_capacity(10);
resp.push(0x00);
resp.push(0x20); resp.push(0x80);
resp.push(0x00); resp.push(0x00); resp.push(0x00); resp.extend_from_slice(&abort_code.to_le_bytes());
resp
}
fn build_soe_response(drive_no: u8, element_flags: u8, idn: u16, data: &[u8]) -> Vec<u8> {
let op_code: u8 = if data.is_empty() { 0x00 } else { 0x03 }; let mut resp = Vec::with_capacity(4 + data.len());
resp.push(op_code);
resp.push((drive_no << 4) | (element_flags & 0x0F));
resp.extend_from_slice(&idn.to_le_bytes());
if !data.is_empty() {
resp.extend_from_slice(data);
}
resp
}
fn build_soe_error_response(drive_no: u8, idn: u16, error_code: u16) -> Vec<u8> {
let mut resp = Vec::with_capacity(6);
resp.push(0x04); resp.push(drive_no << 4);
resp.extend_from_slice(&idn.to_le_bytes());
resp.extend_from_slice(&error_code.to_le_bytes());
resp
}
fn build_foe_data_response(data: &[u8]) -> Vec<u8> {
let mut resp = Vec::with_capacity(6 + data.len());
resp.push(0x03); resp.push(0x00); resp.extend_from_slice(&1u32.to_le_bytes()); resp.extend_from_slice(data);
resp
}
fn build_foe_ack_response() -> Vec<u8> {
let mut resp = Vec::with_capacity(6);
resp.push(0x04); resp.push(0x00); resp.extend_from_slice(&1u32.to_le_bytes()); resp
}
fn build_foe_error_response(error_code: u32) -> Vec<u8> {
let mut resp = Vec::with_capacity(6);
resp.push(0x05); resp.push(0x00); resp.extend_from_slice(&error_code.to_le_bytes());
resp
}
fn build_voe_error_response() -> Vec<u8> {
let mut resp = Vec::with_capacity(6);
resp.extend_from_slice(&0u32.to_le_bytes()); resp.extend_from_slice(&0xFFFFu16.to_le_bytes()); resp
}