darra-ethercat-master 2.1.0

商业 EtherCAT 主站协议栈 · 实时内核驱动 · 抖动 1µs · Windows + Linux · 多编程语言 · 全协议 · 支持复杂拓扑 + 热插拔 · ethercat.darra.xyz · Commercial EtherCAT Master protocol stack · Real-time kernel driver · 1µs jitter · Multi-platform · Multi-language · Complex topology + hot-plug.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
//! EoE (Ethernet over EtherCAT) 完整实现
//!
//! 对齐 C# EoEInstance,提供:
//! - EoEInstance: IP/子网/网关/MAC/DNS 属性读写、帧收发、Ping
//! - EoEPingResult: Ping 结果
//! - 以太网帧构建辅助 (IP/ICMP/ARP)
//! - ARP 缓存管理
//! - 地址过滤器管理
//!
//! # 异步使用 (对齐 C# EoEInstance.PingAsync / SendFrameAsync)
//!
//! EoE 方法同步阻塞, Ping 尤其建议异步化 (超时前要等若干秒)。
//! 本 crate 提供**双轨**异步 API:
//!
//! ## 轨道 1: std::thread (无依赖)
//!
//! ```ignore
//! let handle = EoEInstance::ping_blocking(0, 1, 0, target_ip_u32, 5000);
//! let result: EoEPingResult = handle.join().unwrap();
//!
//! let handle = EoEInstance::receive_frame_blocking(0, 1, 0, 100);
//! let frame = handle.join().unwrap()?;
//! ```
//!
//! ## 轨道 2: tokio async (可选 feature `async-tokio`)
//!
//! ```ignore
//! let eoe = slave.eoe(port);
//! let result = eoe.ping_async(target_ip, 5000).await;
//! let frame = eoe.receive_frame_async(100).await?;
//! eoe.send_frame_async(frame_bytes, 100).await?;
//!
//! // 并行 Ping 多个从站
//! use tokio::task::JoinSet;
//! let mut set = JoinSet::new();
//! for slave_idx in 1..=num_slaves {
//!     set.spawn(async move {
//!         let eoe = master.slave(slave_idx).eoe(0);
//!         eoe.ping_async(target_ip, 1000).await
//!     });
//! }
//! ```

use std::collections::HashMap;
use std::time::{Duration, Instant};
use crate::data::error::{DarraError, Result};
use crate::utils::ffi;
use std::os::raw::c_int;

// ===================== IP/ICMP 校验和 =====================

/// 计算 Internet 校验和(RFC 1071)
///
/// 适用于 IP 头、ICMP 报文等需要 16 位反码累加和的场景。
pub fn internet_checksum(data: &[u8]) -> u16 {
    let mut sum: u32 = 0;
    let mut i = 0;
    while i + 1 < data.len() {
        sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
        i += 2;
    }
    // 奇数字节补零处理
    if i < data.len() {
        sum += (data[i] as u32) << 8;
    }
    // 折叠进位
    while (sum >> 16) != 0 {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }
    !(sum as u16)
}

// ===================== 以太网帧构建辅助 =====================

/// 构建最小以太网帧(不含 FCS)
///
/// - dst_mac: 6 字节目标 MAC
/// - src_mac: 6 字节源 MAC
/// - ethertype: 0x0800=IPv4, 0x0806=ARP
/// - payload: 有效载荷
fn build_ethernet_frame(dst_mac: &[u8; 6], src_mac: &[u8; 6], ethertype: u16, payload: &[u8]) -> Vec<u8> {
    let mut frame = Vec::with_capacity(14 + payload.len());
    frame.extend_from_slice(dst_mac);
    frame.extend_from_slice(src_mac);
    frame.extend_from_slice(&ethertype.to_be_bytes());
    frame.extend_from_slice(payload);
    // 以太网最小帧长度为 60 字节(不含 FCS)
    while frame.len() < 60 {
        frame.push(0);
    }
    frame
}

/// 构建 IPv4 头(20 字节,不含选项)
fn build_ip_header(src_ip: u32, dst_ip: u32, protocol: u8, payload_len: u16) -> Vec<u8> {
    let total_len = 20u16 + payload_len;
    let mut header = vec![
        0x45,                              // 版本=4, 头长度=5*4=20
        0x00,                              // DSCP/ECN
        (total_len >> 8) as u8,            // 总长度高字节
        (total_len & 0xFF) as u8,          // 总长度低字节
        0x00, 0x01,                        // 标识
        0x40, 0x00,                        // 标志=DF, 分片偏移=0
        0x40,                              // TTL=64
        protocol,                          // 协议
        0x00, 0x00,                        // 校验和(先填0)
        // 源 IP(大端)
        ((src_ip >> 24) & 0xFF) as u8,
        ((src_ip >> 16) & 0xFF) as u8,
        ((src_ip >>  8) & 0xFF) as u8,
        ( src_ip        & 0xFF) as u8,
        // 目标 IP(大端)
        ((dst_ip >> 24) & 0xFF) as u8,
        ((dst_ip >> 16) & 0xFF) as u8,
        ((dst_ip >>  8) & 0xFF) as u8,
        ( dst_ip        & 0xFF) as u8,
    ];
    // 填写 IP 头校验和
    let checksum = internet_checksum(&header);
    header[10] = (checksum >> 8) as u8;
    header[11] = (checksum & 0xFF) as u8;
    header
}

/// 构建 ICMP Echo 请求报文
fn build_icmp_echo(id: u16, seq: u16, data: &[u8]) -> Vec<u8> {
    let mut icmp = Vec::with_capacity(8 + data.len());
    icmp.push(8);                          // 类型=8 (Echo Request)
    icmp.push(0);                          // 代码=0
    icmp.extend_from_slice(&[0, 0]);       // 校验和(先填0)
    icmp.extend_from_slice(&id.to_be_bytes());
    icmp.extend_from_slice(&seq.to_be_bytes());
    icmp.extend_from_slice(data);
    // 填写 ICMP 校验和
    let checksum = internet_checksum(&icmp);
    icmp[2] = (checksum >> 8) as u8;
    icmp[3] = (checksum & 0xFF) as u8;
    icmp
}

// ===================== EoE Ping =====================

/// Ping 结果
#[derive(Debug, Clone)]
pub struct PingResult {
    /// 是否收到回应
    pub success: bool,
    /// RTT(往返时间),None 表示超时
    pub rtt: Option<Duration>,
    /// 响应报文的 ICMP 序列号
    pub seq: u16,
}

/// 通过 EoE 向从站内部 IP 发送 ICMP Echo 请求(Ping)
///
/// 参数说明:
/// - `master_index`: 主站索引
/// - `slave_index`: 从站索引
/// - `port`: EoE 端口号(通常为 0)
/// - `src_mac`: 主站侧虚拟 MAC
/// - `src_ip`: 主站侧 IP(u32 大端, 如 192.168.1.1 = 0xC0A80101)
/// - `dst_ip`: 目标 IP
/// - `seq`: ICMP 序列号
/// - `payload`: ICMP 数据载荷
/// - `timeout_us`: 等待回应超时(微秒)
pub fn eoe_ping(
    master_index: u16,
    slave_index: u16,
    port: u8,
    src_mac: &[u8; 6],
    dst_mac: &[u8; 6],
    src_ip: u32,
    dst_ip: u32,
    seq: u16,
    payload: &[u8],
    timeout_us: i32,
) -> Result<PingResult> {
    // 构建 ICMP 报文
    let icmp = build_icmp_echo(0xDA12, seq, payload);
    // 构建 IP 报文
    let mut ip = build_ip_header(src_ip, dst_ip, 1, icmp.len() as u16);
    ip.extend_from_slice(&icmp);
    // 构建以太网帧 (EtherType 0x0800 = IPv4)
    let frame = build_ethernet_frame(dst_mac, src_mac, 0x0800, &ip);

    let start = Instant::now();

    // 通过 EoE 发送
    let ok = unsafe {
        ffi::EOESendFrame(master_index, slave_index, port,
                          frame.as_ptr(), frame.len() as c_int, timeout_us)
    };
    if ok == 0 {
        return Ok(PingResult { success: false, rtt: None, seq });
    }

    // 等待接收回应
    let mut resp_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
    let mut resp_size: c_int = 0;
    let recv_ok = unsafe {
        ffi::EOEReceiveFrame(master_index, slave_index, port,
                             &mut resp_ptr, &mut resp_size, timeout_us)
    };
    let rtt = start.elapsed();

    if recv_ok == 0 || resp_ptr.is_null() || resp_size < 42 {
        if !resp_ptr.is_null() { unsafe { ffi::FreeMemory(resp_ptr) }; }
        return Ok(PingResult { success: false, rtt: None, seq });
    }

    // 解析回应帧,验证 ICMP 类型和序列号
    let resp = unsafe {
        std::slice::from_raw_parts(resp_ptr as *const u8, resp_size as usize).to_vec()
    };
    unsafe { ffi::FreeMemory(resp_ptr) };

    // 以太网头 14 字节,IP 头 20 字节,ICMP 从偏移 34 开始
    if resp.len() < 36 {
        return Ok(PingResult { success: false, rtt: None, seq });
    }
    // 检查 EtherType = 0x0800
    let ethertype = u16::from_be_bytes([resp[12], resp[13]]);
    if ethertype != 0x0800 {
        return Ok(PingResult { success: false, rtt: None, seq });
    }
    // IP 协议 = ICMP (1)
    let protocol = resp[23];
    if protocol != 1 {
        return Ok(PingResult { success: false, rtt: None, seq });
    }
    // ICMP 类型 = 0 (Echo Reply)
    let icmp_type = resp[34];
    // ICMP 序列号(偏移 38-39)
    let resp_seq = u16::from_be_bytes([resp[38], resp[39]]);
    let success = icmp_type == 0 && resp_seq == seq;

    Ok(PingResult { success, rtt: Some(rtt), seq })
}

// ===================== ARP 缓存 =====================

/// ARP 缓存条目
#[derive(Debug, Clone)]
pub struct ArpEntry {
    /// IP 地址(u32 大端)
    pub ip: u32,
    /// MAC 地址
    pub mac: [u8; 6],
    /// 最后更新时间
    pub last_seen: Instant,
    /// 是否由 ARP 请求学习(false = 静态配置)
    pub dynamic: bool,
}

impl ArpEntry {
    /// 检查条目是否已超时(默认 300 秒)
    pub fn is_expired(&self, ttl: Duration) -> bool {
        self.last_seen.elapsed() > ttl
    }
}

/// EoE ARP 缓存
///
/// 维护 IP -> MAC 的映射关系,支持:
/// - 静态手动添加
/// - 通过 EoE 发送 ARP 请求动态解析
/// - 从接收帧中学习
pub struct ArpCache {
    /// IP -> ARP 条目映射
    entries: HashMap<u32, ArpEntry>,
    /// 动态条目生存时间
    ttl: Duration,
}

impl ArpCache {
    /// 创建新的 ARP 缓存,默认 TTL 300 秒
    pub fn new() -> Self {
        Self {
            entries: HashMap::new(),
            ttl: Duration::from_secs(300),
        }
    }

    /// 设置动态条目 TTL
    pub fn set_ttl(&mut self, ttl: Duration) {
        self.ttl = ttl;
    }

    /// 手动添加静态 ARP 条目
    pub fn add_static(&mut self, ip: u32, mac: [u8; 6]) {
        self.entries.insert(ip, ArpEntry {
            ip, mac,
            last_seen: Instant::now(),
            dynamic: false,
        });
    }

    /// 查找 IP 对应的 MAC,自动过滤超时的动态条目
    pub fn lookup(&self, ip: u32) -> Option<[u8; 6]> {
        self.entries.get(&ip).and_then(|e| {
            if e.dynamic && e.is_expired(self.ttl) {
                None
            } else {
                Some(e.mac)
            }
        })
    }

    /// 从接收到的以太网帧中学习 ARP 映射(被动学习)
    ///
    /// 若帧为 ARP 回复(操作码 2),则提取发送方 IP 和 MAC 存入缓存。
    pub fn learn_from_frame(&mut self, frame: &[u8]) {
        if frame.len() < 42 { return; }
        // 检查 EtherType = 0x0806 (ARP)
        if frame[12] != 0x08 || frame[13] != 0x06 { return; }
        // ARP 操作码 (偏移 20-21)
        let opcode = u16::from_be_bytes([frame[20], frame[21]]);
        if opcode != 2 { return; } // 仅处理 ARP 回复
        // 发送方 MAC (偏移 22-27)
        let mut mac = [0u8; 6];
        mac.copy_from_slice(&frame[22..28]);
        // 发送方 IP (偏移 28-31)
        let ip = u32::from_be_bytes([frame[28], frame[29], frame[30], frame[31]]);
        self.entries.insert(ip, ArpEntry {
            ip, mac,
            last_seen: Instant::now(),
            dynamic: true,
        });
    }

    /// 通过 EoE 发送 ARP 请求并等待回应,结果加入缓存
    ///
    /// 广播 ARP 请求 (目标 MAC = FF:FF:FF:FF:FF:FF),
    /// 等待目标 IP 的拥有者回应其 MAC 地址。
    pub fn resolve(
        &mut self,
        master_index: u16,
        slave_index: u16,
        port: u8,
        src_mac: &[u8; 6],
        src_ip: u32,
        target_ip: u32,
        timeout_us: i32,
    ) -> Result<Option<[u8; 6]>> {
        // 先查缓存(避免重复请求)
        if let Some(mac) = self.lookup(target_ip) {
            return Ok(Some(mac));
        }

        // 构建 ARP 请求帧
        let arp_frame = build_arp_request(src_mac, src_ip, target_ip);
        let broadcast_mac = [0xFF; 6];
        let frame = build_ethernet_frame(&broadcast_mac, src_mac, 0x0806, &arp_frame);

        // 发送 ARP 请求
        let send_ok = unsafe {
            ffi::EOESendFrame(master_index, slave_index, port,
                              frame.as_ptr(), frame.len() as c_int, timeout_us)
        };
        if send_ok == 0 {
            return Err(DarraError::EoeFailed);
        }

        // 等待 ARP 回应
        let mut resp_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
        let mut resp_size: c_int = 0;
        let recv_ok = unsafe {
            ffi::EOEReceiveFrame(master_index, slave_index, port,
                                 &mut resp_ptr, &mut resp_size, timeout_us)
        };
        if recv_ok == 0 || resp_ptr.is_null() {
            if !resp_ptr.is_null() { unsafe { ffi::FreeMemory(resp_ptr) }; }
            return Ok(None);
        }

        let resp = unsafe {
            std::slice::from_raw_parts(resp_ptr as *const u8, resp_size as usize).to_vec()
        };
        unsafe { ffi::FreeMemory(resp_ptr) };

        // 从响应帧中学习
        self.learn_from_frame(&resp);

        // 再次查缓存
        Ok(self.lookup(target_ip))
    }

    /// 清除所有超时的动态条目
    pub fn purge_expired(&mut self) {
        let ttl = self.ttl;
        self.entries.retain(|_, e| !e.dynamic || !e.is_expired(ttl));
    }

    /// 删除指定 IP 的缓存条目
    pub fn remove(&mut self, ip: u32) {
        self.entries.remove(&ip);
    }

    /// 返回当前缓存条目数量
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// 检查缓存是否为空
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }
}

impl Default for ArpCache {
    fn default() -> Self {
        Self::new()
    }
}

// ===================== EoE 地址过滤器 =====================

/// 估算 FoE 传输需要的数据包数量 (纯计算, 不依赖 DLL)
///
/// - `file_size`: 文件大小 (字节)
/// - `max_data_size`: 每包最大数据量 (字节, 通常取决于邮箱大小)
pub fn foe_estimate_packet_count(file_size: u32, max_data_size: u32) -> u32 {
    if max_data_size == 0 {
        return 0;
    }
    // 向上取整: (file_size + max_data_size - 1) / max_data_size
    (file_size + max_data_size - 1) / max_data_size
}

// ===================== ARP 帧构建辅助 =====================

// ===================== EoE Ping 结果 (对齐 C# EoEPingResult) =====================

/// EoE Ping 结果
#[derive(Debug, Clone)]
pub struct EoEPingResult {
    /// 是否成功
    pub success: bool,
    /// 往返时间 (毫秒)
    pub round_trip_time_ms: f64,
    /// 目标地址
    pub target_address: String,
    /// TTL
    pub ttl: u8,
    /// 错误消息
    pub error_message: String,
}

impl std::fmt::Display for EoEPingResult {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.success {
            write!(f, "来自 {} 的回复: 时间={:.2}ms TTL={}", self.target_address, self.round_trip_time_ms, self.ttl)
        } else {
            write!(f, "Ping {} 失败: {}", self.target_address, self.error_message)
        }
    }
}

// ===================== EoE 实例 (对齐 C# EoEInstance) =====================

/// EoE 实例 - 为从站提供完整的 EoE 功能
///
/// 通过 `Slave::eoe()` 获取实例。
pub struct EoEInstance {
    master_index: u16,
    slave_index: u16,
    port: u8,
    /// ARP 缓存
    pub arp_cache: ArpCache,
}

impl EoEInstance {
    /// 创建 EoE 实例
    pub(crate) fn new(master_index: u16, slave_index: u16, port: u8) -> Self {
        Self { master_index, slave_index, port, arp_cache: ArpCache::new() }
    }

    /// 主站索引
    pub fn master_index(&self) -> u16 { self.master_index }

    /// 从站索引
    pub fn slave_index(&self) -> u16 { self.slave_index }

    /// 从站是否支持 EoE 邮箱协议 (对齐 C# EoEInstance.IsSupported).
    ///
    /// 读取 SII mbx_proto bit 1 (ECT_MBXPROT_EOE = 0x02) 判断支持情况.
    /// DLL 调用失败时保守返回 true (未知能力视为支持).
    pub fn is_supported(&self) -> bool {
        let proto = unsafe { ffi::GetSlaveMailboxProto(self.master_index, self.slave_index) };
        (proto & 0x02) != 0
    }

    // ==================== IP 配置 ====================

    /// 读取 IP 地址 (u32, 网络序)
    pub fn ip(&self, timeout_ms: i32) -> Result<u32> {
        let mut ip: u32 = 0;
        let mut subnet: u32 = 0;
        let mut gateway: u32 = 0;
        let ret = unsafe {
            ffi::EOEGetIP(self.master_index, self.slave_index, self.port,
                         &mut ip, &mut subnet, &mut gateway, timeout_ms * 1000)
        };
        if ret != 0 { Ok(ip) } else { Err(DarraError::EoeFailed) }
    }

    /// 读取子网掩码 (u32, 网络序)
    pub fn subnet(&self, timeout_ms: i32) -> Result<u32> {
        let mut ip: u32 = 0;
        let mut subnet: u32 = 0;
        let mut gateway: u32 = 0;
        let ret = unsafe {
            ffi::EOEGetIP(self.master_index, self.slave_index, self.port,
                         &mut ip, &mut subnet, &mut gateway, timeout_ms * 1000)
        };
        if ret != 0 { Ok(subnet) } else { Err(DarraError::EoeFailed) }
    }

    /// 读取网关 (u32, 网络序)
    pub fn gateway(&self, timeout_ms: i32) -> Result<u32> {
        let mut ip: u32 = 0;
        let mut subnet: u32 = 0;
        let mut gateway: u32 = 0;
        let ret = unsafe {
            ffi::EOEGetIP(self.master_index, self.slave_index, self.port,
                         &mut ip, &mut subnet, &mut gateway, timeout_ms * 1000)
        };
        if ret != 0 { Ok(gateway) } else { Err(DarraError::EoeFailed) }
    }

    /// 设置 IP 配置
    pub fn set_ip(&self, ip: u32, subnet: u32, gateway: u32, timeout_ms: i32) -> Result<()> {
        let ret = unsafe {
            ffi::EOESetIP(self.master_index, self.slave_index, self.port,
                         ip, subnet, gateway, timeout_ms * 1000)
        };
        if ret != 0 { Ok(()) } else { Err(DarraError::EoeFailed) }
    }

    // ==================== MAC 配置 ====================

    /// 读取 MAC 地址
    pub fn mac(&self, timeout_ms: i32) -> Result<[u8; 6]> {
        let mut mac = [0u8; 6];
        let ret = unsafe {
            ffi::EOEGetMAC(self.master_index, self.slave_index, self.port,
                          mac.as_mut_ptr(), timeout_ms * 1000)
        };
        if ret != 0 { Ok(mac) } else { Err(DarraError::EoeFailed) }
    }

    /// 读取 MAC 地址字符串 (AA:BB:CC:DD:EE:FF)
    pub fn mac_string(&self, timeout_ms: i32) -> Result<String> {
        let mac = self.mac(timeout_ms)?;
        Ok(format!("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
                   mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]))
    }

    /// 设置 MAC 地址
    pub fn set_mac(&self, mac: &[u8; 6], timeout_ms: i32) -> Result<()> {
        let ret = unsafe {
            ffi::EOESetMAC(self.master_index, self.slave_index, self.port,
                          mac.as_ptr(), timeout_ms * 1000)
        };
        if ret != 0 { Ok(()) } else { Err(DarraError::EoeFailed) }
    }

    // ==================== DNS 配置 ====================

    /// 读取 DNS 地址 (u32, 网络序)
    pub fn dns(&self, timeout_ms: i32) -> Result<u32> {
        let mut dns_ip: u32 = 0;
        let mut dns_name = vec![0i8; 33];
        let ret = unsafe {
            ffi::EOEGetDNS(self.master_index, self.slave_index, self.port,
                          &mut dns_ip, dns_name.as_mut_ptr(), timeout_ms * 1000)
        };
        if ret != 0 { Ok(dns_ip) } else { Err(DarraError::EoeFailed) }
    }

    /// 设置 DNS 配置
    pub fn set_dns(&self, dns_ip: u32, dns_name: &str, timeout_ms: i32) -> Result<()> {
        let c_name = std::ffi::CString::new(dns_name).unwrap_or_default();
        let ret = unsafe {
            ffi::EOESetDNS(self.master_index, self.slave_index, self.port,
                          dns_ip, c_name.as_ptr(), timeout_ms * 1000)
        };
        if ret != 0 { Ok(()) } else { Err(DarraError::EoeFailed) }
    }

    // ==================== 帧收发 ====================

    /// 发送以太网帧
    pub fn send_frame(&self, frame: &[u8], timeout_ms: i32) -> Result<()> {
        let ret = unsafe {
            ffi::EOESendFrame(self.master_index, self.slave_index, self.port,
                             frame.as_ptr(), frame.len() as c_int, timeout_ms * 1000)
        };
        if ret != 0 { Ok(()) } else { Err(DarraError::EoeFailed) }
    }

    /// 接收以太网帧
    pub fn receive_frame(&self, timeout_ms: i32) -> Result<Vec<u8>> {
        let mut frame_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
        let mut frame_size: c_int = 0;
        let ret = unsafe {
            ffi::EOEReceiveFrame(self.master_index, self.slave_index, self.port,
                                &mut frame_ptr, &mut frame_size, timeout_ms * 1000)
        };
        if ret != 0 && !frame_ptr.is_null() && frame_size > 0 {
            let data = unsafe {
                std::slice::from_raw_parts(frame_ptr as *const u8, frame_size as usize).to_vec()
            };
            unsafe { ffi::FreeMemory(frame_ptr) };
            Ok(data)
        } else {
            Err(DarraError::EoeFailed)
        }
    }

    // ==================== 带时间戳的帧发送 ====================

    /// 发送带 DC 时间戳的以太网帧 (对应 C# SendFrameWithTimestamp)
    ///
    /// ETG.1000.6 §5.7 TimeAppended: 在帧末尾附加 4 字节时间戳
    /// (Unsigned32, DC 时间低 32 位), 用于时间同步场景。
    ///
    /// # 参数
    /// - `frame`: 以太网帧数据
    /// - `timestamp`: DC 时间戳 (纳秒), 取低 32 位附加到帧末尾
    /// - `timeout_ms`: 超时 (毫秒)
    pub fn send_frame_with_timestamp(&self, frame: &[u8], timestamp: u64, timeout_ms: i32) -> Result<()> {
        // 在帧末尾附加 4 字节时间戳 (ETG.1000.6 Table 78, Unsigned32)
        let ts32 = timestamp as u32;
        let mut frame_with_ts = Vec::with_capacity(frame.len() + 4);
        frame_with_ts.extend_from_slice(frame);
        frame_with_ts.extend_from_slice(&ts32.to_le_bytes());

        let ret = unsafe {
            ffi::EOESendFrame(
                self.master_index, self.slave_index, self.port,
                frame_with_ts.as_ptr(), frame_with_ts.len() as c_int,
                timeout_ms * 1000,
            )
        };
        if ret != 0 { Ok(()) } else { Err(DarraError::EoeFailed) }
    }

    // ==================== 地址过滤器管理 ====================

    /// 获取当前地址过滤器列表 (对应 C# GetAddressFilters)
    ///
    /// 返回当前从站配置的 MAC 过滤器列表。
    pub fn get_address_filters(&self, timeout_ms: i32) -> Result<Vec<[u8; 6]>> {
        let mut filter_count: u8 = 0;
        // 最多支持 16 个过滤器, 每个 6 字节
        let mut mac_buffer = vec![0u8; 16 * 6];
        let ret = unsafe {
            ffi::EOEGetAddressFilter(
                self.master_index, self.slave_index, self.port,
                &mut filter_count, mac_buffer.as_mut_ptr(), 16, timeout_ms * 1000,
            )
        };
        if ret == 0 {
            return Err(DarraError::EoeFailed);
        }
        let count = filter_count as usize;
        let mut filters = Vec::with_capacity(count);
        for i in 0..count {
            let mut mac = [0u8; 6];
            mac.copy_from_slice(&mac_buffer[i * 6..(i + 1) * 6]);
            filters.push(mac);
        }
        Ok(filters)
    }

    /// 批量设置地址过滤器 (对应 C# SetAddressFiltersBatch)
    ///
    /// 一次性设置多个 MAC 过滤器, 替换现有配置。
    ///
    /// # 参数
    /// - `filters`: MAC 地址列表
    /// - `timeout_ms`: 超时 (毫秒)
    pub fn set_address_filters_batch(&self, filters: &[[u8; 6]], timeout_ms: i32) -> Result<()> {
        // 直接通过 EOESetAddressFilter 设置过滤器列表 (替换现有配置)
        let mac_data: Vec<u8> = filters.iter().flat_map(|m| m.iter().copied()).collect();
        let ret = unsafe {
            ffi::EOESetAddressFilter(
                self.master_index, self.slave_index, self.port,
                filters.len() as u8, mac_data.as_ptr(), timeout_ms * 1000,
            )
        };
        if ret != 0 { Ok(()) } else { Err(DarraError::EoeFailed) }
    }

    // ==================== ARP 缓存管理 ====================

    /// 清除 ARP 缓存 (对应 C# ClearArpCache)
    pub fn clear_arp_cache(&mut self) {
        self.arp_cache = ArpCache::new();
    }

    // ==================== Ping ====================

    /// 通过 EoE 发送 Ping
    pub fn ping(&self, target_ip: u32, timeout_ms: i32) -> EoEPingResult {
        // 获取本地配置
        let ip = match self.ip(timeout_ms) {
            Ok(ip) => ip,
            Err(_) => return EoEPingResult {
                success: false, round_trip_time_ms: 0.0,
                target_address: format_ip(target_ip), ttl: 0,
                error_message: "本地网络配置无效".to_string(),
            },
        };
        let mac = match self.mac(timeout_ms) {
            Ok(mac) => mac,
            Err(_) => return EoEPingResult {
                success: false, round_trip_time_ms: 0.0,
                target_address: format_ip(target_ip), ttl: 0,
                error_message: "无法获取MAC地址".to_string(),
            },
        };

        let dst_mac = self.arp_cache.lookup(target_ip).unwrap_or([0xFF; 6]);
        match eoe_ping(self.master_index, self.slave_index, self.port,
                       &mac, &dst_mac, ip, target_ip, 1, &[0u8; 32], timeout_ms * 1000) {
            Ok(result) => EoEPingResult {
                success: result.success,
                round_trip_time_ms: result.rtt.map(|r| r.as_secs_f64() * 1000.0).unwrap_or(0.0),
                target_address: format_ip(target_ip),
                ttl: 64,
                error_message: if result.success { String::new() } else { "超时".to_string() },
            },
            Err(e) => EoEPingResult {
                success: false, round_trip_time_ms: 0.0,
                target_address: format_ip(target_ip), ttl: 0,
                error_message: format!("{:?}", e),
            },
        }
    }
}

/// 格式化 IP 地址 (u32 网络序 -> 字符串)
fn format_ip(ip: u32) -> String {
    format!("{}.{}.{}.{}",
            (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF)
}

// ===================== ARP 帧构建辅助 =====================

/// 构建 ARP 请求载荷(28 字节,不含以太网头)
fn build_arp_request(src_mac: &[u8; 6], src_ip: u32, target_ip: u32) -> Vec<u8> {
    let mut arp = Vec::with_capacity(28);
    // 硬件类型 = 以太网 (1)
    arp.extend_from_slice(&1u16.to_be_bytes());
    // 协议类型 = IPv4 (0x0800)
    arp.extend_from_slice(&0x0800u16.to_be_bytes());
    // 硬件地址长度 = 6
    arp.push(6);
    // 协议地址长度 = 4
    arp.push(4);
    // 操作码 = 请求 (1)
    arp.extend_from_slice(&1u16.to_be_bytes());
    // 发送方 MAC
    arp.extend_from_slice(src_mac);
    // 发送方 IP
    arp.extend_from_slice(&src_ip.to_be_bytes());
    // 目标 MAC(全零,表示未知)
    arp.extend_from_slice(&[0u8; 6]);
    // 目标 IP
    arp.extend_from_slice(&target_ip.to_be_bytes());
    arp
}

// ARP 表条目设置功能: DLL 未导出 EOESetArpEntry, 仅在 C# SDK 中可用

// ===================== 异步 API (轨道 1: std::thread, 无依赖) =====================

impl EoEInstance {
    /// 发送以太网帧 (std::thread 异步包装)
    pub fn send_frame_blocking(
        master_index: u16,
        slave_index: u16,
        port: u8,
        frame: Vec<u8>,
        timeout_ms: i32,
    ) -> std::thread::JoinHandle<Result<()>> {
        std::thread::spawn(move || {
            let eoe = EoEInstance::new(master_index, slave_index, port);
            eoe.send_frame(&frame, timeout_ms)
        })
    }

    /// 接收以太网帧 (std::thread 异步包装)
    pub fn receive_frame_blocking(
        master_index: u16,
        slave_index: u16,
        port: u8,
        timeout_ms: i32,
    ) -> std::thread::JoinHandle<Result<Vec<u8>>> {
        std::thread::spawn(move || {
            let eoe = EoEInstance::new(master_index, slave_index, port);
            eoe.receive_frame(timeout_ms)
        })
    }

    /// Ping (std::thread 异步包装)
    pub fn ping_blocking(
        master_index: u16,
        slave_index: u16,
        port: u8,
        target_ip: u32,
        timeout_ms: i32,
    ) -> std::thread::JoinHandle<EoEPingResult> {
        std::thread::spawn(move || {
            let eoe = EoEInstance::new(master_index, slave_index, port);
            eoe.ping(target_ip, timeout_ms)
        })
    }
}

// ===================== 异步接收 Hook (对齐 C# EoEInstance.FrameReceived) =====================
//
// FFI 路径: 走 [`crate::utils::ffi::dynamic_ffi`] (libloading 运行时绑定),
// 因为 EOESetReceiveHook / EOEClearReceiveHook 在不同 DLL 版本可能未导出,
// Rust 静态 extern 会 link 失败. 解析失败时方法返回 `DarraError::Other` 而不 panic.
//
// 全局回调表: 用 OnceLock + Mutex<HashMap<master_index, Arc<dyn Fn>>> 固定 trait object
// 生命周期, 防止 Box 被 drop 后 native 仍持有悬挂指针.

use std::sync::{Mutex as StdMutex, OnceLock as StdOnceLock, Arc};

/// 用户回调类型别名 (master_index, slave_index, &frame).
type EoEReceiveHook = Arc<dyn Fn(u16, u16, &[u8]) + Send + Sync + 'static>;

/// 全局回调注册表: master_index → 用户回调.
fn eoe_hook_registry() -> &'static StdMutex<HashMap<u16, EoEReceiveHook>> {
    static REG: StdOnceLock<StdMutex<HashMap<u16, EoEReceiveHook>>> = StdOnceLock::new();
    REG.get_or_init(|| StdMutex::new(HashMap::new()))
}

/// 共享 trampoline: native 回调 → 查 master 注册表 → 触发用户闭包.
unsafe extern "C" fn eoe_receive_trampoline(
    master_index: u16,
    slave: u16,
    frame_data: *const u8,
    frame_size: c_int,
) {
    if frame_data.is_null() || frame_size <= 0 {
        return;
    }
    let slice = unsafe { std::slice::from_raw_parts(frame_data, frame_size as usize) };
    let cb_opt = {
        match eoe_hook_registry().lock() {
            Ok(g) => g.get(&master_index).cloned(),
            Err(p) => p.into_inner().get(&master_index).cloned(),
        }
    };
    if let Some(cb) = cb_opt {
        cb(master_index, slave, slice);
    }
}

impl EoEInstance {
    /// 注册 EoE 异步接收回调.
    ///
    /// 内部用全局 trampoline 转发到用户闭包. 同一 `master_index` 多次调用会**覆盖**
    /// 之前的闭包 (与 C# DLL.EoE.EOESetReceiveHook 单回调语义一致).
    /// DLL 未导出 `EOESetReceiveHook` 时返回错误.
    pub fn set_receive_hook<F>(&self, callback: F) -> Result<()>
    where
        F: Fn(u16, u16, &[u8]) + Send + Sync + 'static,
    {
        let f = ffi::dynamic_ffi::ffi_gap()
            .eoe_set_receive_hook
            .ok_or_else(|| DarraError::Other(
                // 错误信息 obfstr 混淆, 反编译看不到符号/DLL 名明文
                obfstr::obfstr!("EOESetReceiveHook 未在 Darra.Core.dll 中导出").to_string()
            ))?;
        // 写入全局注册表 (后写入再调 native, 防止 native 在写入前已触发).
        match eoe_hook_registry().lock() {
            Ok(mut g) => { g.insert(self.master_index, Arc::new(callback)); }
            Err(p) => { p.into_inner().insert(self.master_index, Arc::new(callback)); }
        }
        let ok = unsafe { f(self.master_index, Some(eoe_receive_trampoline)) };
        if ok == 0 {
            // native 拒绝, 回滚注册表.
            if let Ok(mut g) = eoe_hook_registry().lock() {
                g.remove(&self.master_index);
            }
            return Err(DarraError::Other(
                obfstr::obfstr!("EOESetReceiveHook 返回 false (DLL 拒绝注册)").to_string()
            ));
        }
        Ok(())
    }

    /// 清除 EoE 异步接收回调.
    pub fn clear_receive_hook(&self) -> Result<()> {
        let f = ffi::dynamic_ffi::ffi_gap()
            .eoe_clear_receive_hook
            .ok_or_else(|| DarraError::Other(
                obfstr::obfstr!("EOEClearReceiveHook 未在 Darra.Core.dll 中导出").to_string()
            ))?;
        let _ok = unsafe { f(self.master_index) };
        if let Ok(mut g) = eoe_hook_registry().lock() {
            g.remove(&self.master_index);
        }
        Ok(())
    }
}

// ===================== 异步 API (轨道 2: tokio, feature 门控) =====================

#[cfg(feature = "async-tokio")]
impl EoEInstance {
    /// 发送帧 (tokio async)
    pub async fn send_frame_async(&self, frame: Vec<u8>, timeout_ms: i32) -> Result<()> {
        let master = self.master_index;
        let slave = self.slave_index;
        let port = self.port;
        tokio::task::spawn_blocking(move || {
            let eoe = EoEInstance::new(master, slave, port);
            eoe.send_frame(&frame, timeout_ms)
        })
        .await
        .map_err(|e| DarraError::Other(format!("tokio join error: {}", e)))?
    }

    /// 接收帧 (tokio async)
    pub async fn receive_frame_async(&self, timeout_ms: i32) -> Result<Vec<u8>> {
        let master = self.master_index;
        let slave = self.slave_index;
        let port = self.port;
        tokio::task::spawn_blocking(move || {
            let eoe = EoEInstance::new(master, slave, port);
            eoe.receive_frame(timeout_ms)
        })
        .await
        .map_err(|e| DarraError::Other(format!("tokio join error: {}", e)))?
    }

    /// Ping (tokio async)
    pub async fn ping_async(&self, target_ip: u32, timeout_ms: i32) -> EoEPingResult {
        let master = self.master_index;
        let slave = self.slave_index;
        let port = self.port;
        match tokio::task::spawn_blocking(move || {
            let eoe = EoEInstance::new(master, slave, port);
            eoe.ping(target_ip, timeout_ms)
        })
        .await
        {
            Ok(r) => r,
            Err(e) => EoEPingResult {
                success: false,
                round_trip_time_ms: 0.0,
                target_address: format_ip(target_ip),
                ttl: 0,
                error_message: format!("tokio join error: {}", e),
            },
        }
    }
}

// ===================== IMailboxProtocol trait impl (对齐 C# EoEInstance) =====================

impl crate::abstractions::MailboxProtocol for EoEInstance {
    fn protocol_type(&self) -> u8 { 0x02 }
    fn protocol_name(&self) -> &'static str { "EoE" }

    fn is_supported(&self) -> bool {
        EoEInstance::is_supported(self)
    }

    fn statistics(&self) -> crate::abstractions::MailboxStatistics {
        let mut stats = ffi::EcMbxStatsC::default();
        let rc = unsafe {
            ffi::mbx_get_stats_by_master(
                self.master_index, self.slave_index, 0x02, &mut stats,
            )
        };
        if rc == 1 {
            stats.into()
        } else {
            crate::abstractions::MailboxStatistics::empty()
        }
    }

    fn reset_statistics(&self) {
        unsafe {
            ffi::mbx_reset_stats_by_master(self.master_index, self.slave_index, 0x02);
        }
    }
}