docs+scripts: enet_protocol.md, verification plan, PS verify suite, session analysis scripts, DLL tools, README

This commit is contained in:
tonycao 2026-09-11 01:08:47 +08:00
parent e248a979e8
commit a98930aa71
85 changed files with 5634 additions and 0 deletions

69
README.md Normal file
View File

@ -0,0 +1,69 @@
# enet-validator — e:cue SymphoLight ↔ Butler e:net 协议逆向与复现
对 e:cue SymphoLight 5.5 与 Butler S2 之间 UDP/31772 私有协议(e:net)的完整逆向:
**AECH 认领算法破解、DMX 帧混淆破解、RDM over e:net 解码**,并分别实现了
**设备端仿真**(虚拟 Butler)与**主机端仿真**(hostdemo 直接接管真机)。
## 目录
```
├── src/ Rust 源码(std only,无外部依赖)
│ ├── main.rs 子命令入口
│ ├── enet.rs e:net 帧模板 / SO_REUSEADDR 绑定
│ ├── tests.rs 虚拟 Butler 设备端(AECH 本地应答 + RDM 灯具仿真 + 解密日志)
│ ├── aech.rs AgiosElias.G 认领算法、DMX zPprv0TXRRW 混淆加解密、dmxdec/dmxsend
│ ├── hostdemo.rs 主机侧等效示例(真机接管:认领→RDM 发现→加密 DMX 控制)
│ └── dmxhash.rs 早期帧标签分析
├── samples/ 37 个真实抓包帧样本(hex)
├── docs/
│ ├── enet_protocol.md ★协议逆向文档(帧布局/算法/实证,最核心)
│ └── rust_verification_plan.md 验证计划
├── scripts/
│ ├── Verify-EnetProtocol.ps1 协议验证套件
│ ├── Verify-Rdm.ps1 RDM 验证
│ ├── enet_lib.ps1 PS 公共库
│ └── session/ 逆向过程中的会话脚本(抓包/分析/部署/推送)
└── tools/ DLL 反编译辅助工具(C#)
├── IlDump.cs / GetTable.cs / Scan.cs
```
## 构建
```
cargo build --release # std only, 无外部依赖
```
## 子命令(设备端仿真,跑在任意机器,绑 58 网段示例)
```
enet-validator device # 虚拟 Butler 主循环(192.168.58.7)
enet-validator discover|announce|heartbeat|euce|session|all
```
## 子命令(主机侧 / 离线分析)
```
enet-validator hostdemo <目标IP> [秒] # 主机侧等效:认领→RDM 发现→加密 DMX 控制
# 环境变量 HOSTDEMO_MODE=quick 最小认领+统一会话环
# =claimonly 最小认领探针
# HOSTDEMO_PRE=<秒> 前奏时长(默认45)
enet-validator dmxsend <目标IP> [秒] # 主机侧加密 DMX 播放(自证闭环)
enet-validator dmxdec <tsv> # DMX 帧解密验证(离线)
enet-validator dmxroll <tsv> # 滚动值分析(离线)
enet-validator aechprobe <tsv> <ip> # AECH 在线探测
```
## 已破解要点(详见 docs/enet_protocol.md)
- **AECH 认领**:`G(x) = x ^ TABLE[(x%32 + 1 + (x*13)%30) % 32]`(无密钥,表内嵌 DLL)
- **DMX 混淆**:`r=Random.Next()`;`out[i]=src[(i+rot)%512]^cnt[i]`,`rot=(B12^r)&511`
- **RDM**:标准 E1.20 内嵌 type=04 帧(0x3C 起,免 0xCC 起始字节,checksum 含隐含 0xCC)
- **1947 端口**:40B ASCII 许可令牌广播(`BlZRQphA…`,每 ~34s 一轮)
- 真机固件过滤:AECH 仅应答源 IP=192.168.123.113
## Ubuntu 虚拟 Butler 部署
```
scp -r enet-validator/ user@192.168.58.7:~/
ssh user@192.168.58.7 "cd ~/enet-validator && cargo build --release && ~/run_butler_local.sh"
```

710
docs/enet_protocol.md Normal file
View File

@ -0,0 +1,710 @@
# e:net 协议复原文档(e:cue SymphoLight 5.5 ↔ Butler S2)
> 基于实测抓包复原(2026-09-08 初版,Wireshark/tshark 抓取于"以太网 3")。
> 抓包环境:SymphoLight 5.5 (192.168.123.113) ↔ Butler S2 固件 2.0.3322 (192.168.123.1)。
> 样本:discovery.pcap(启动/发现阶段 56 帧)、dmx40s.pcap(工程驱动阶段 ~5900 帧)。
>
> **2026-09-09 重大更新**:基于真机冷启动全程抓包 `cold2.pcap`(168.7s / 19471 帧,
> SymphoLight 冷启动 → 发现 → 注册 → AECH 认领 → RDM 全程)与虚拟 Butler 验证抓包
> `fix1.pcap`。补全设备上线协议链(§3.6/§3.7),RDM 层完全解码(§5 重写),
> 新增虚拟 Butler 实现与验证(§11)。
>
> **2026-09-10 更新**:稳态双网段对比(ss123/ss58)+ P1 修复验证(fix2/fix3)。
> DMX 帧完全解码(§4.1 重写:seq/保护字节 `(CH-1)^K`/节点序号/端口布局);
> 42B 帧重定性为事件计数遥测(§4.3,推翻"逐帧 ACK");发现收敛与稳态轮询
> 对齐(§11.3);新增 §11.5 DMX 解码里程碑、§12 缺口清单与攻关路线图。
>
> **尺寸口径约定**:本文所有帧长如无特别说明均为 **UDP 负载字节数**。
> 以太网整帧长 = UDP 负载 + 42(Eth14 + IP20 + UDP8)。历史记录中
> "89B 注册帧"=47B 负载、"594B AECH"=552B 负载、"259B 发现应答"=217B 负载、
> "101B 脉冲"=59B 负载、"84B ACK"=42B 负载(§4.3 遥测帧),均为同一帧的两种口径。
---
## 1. 总览
| 项目 | 值 |
|------|-----|
| 传输层 | UDP(无 TCP) |
| 主数据端口 | **31772** (0x7C1C),双向,主机与设备均使用该端口收发 |
| 密钥/许可端口 | **1947**,主机→广播,周期性,静态负载 |
| 帧签名 | 负载前 4 字节 = ASCII `"ENet"`(`45 4E 65 74`) |
| 字节序 | 混合:长度/计数器字段为 **小端 (LE)**,端口字段为 **大端 (BE)** |
| 加密 | **DMX 通道数据经 zPprv0TXRRW 逐帧混淆**(§4.1,已完全破解可双向复现):rot=(B12^r)&511,B12=AECH 实例随机(会话),r=Random.Next()(每帧每端口,帧内 0x22/0x26)。发现帧含部分随机材料 |
| Wireshark 识别 | 无 dissector,全部显示为 `data` |
---
## 2. e:net 通用帧头(所有 31772 报文)
UDP 负载布局(偏移为负载内字节偏移):
```
偏移 长度 示例(主机DMX帧) 含义
0x00 4 45 4E 65 74 魔数 "ENet"(另见 §3.4 的 "EUCE" 第二魔数)
0x04 4 04 00 00 00 消息类型 (LE uint32):01=发现/宣告, 02=主机标识心跳, 04=会话数据
0x08 4 00 00 1C 7C 端口 31772 (BE uint16 前置 0000)
0x0C 6 00 00 00 00 00 00 保留 (实测恒 0)
0x12 2 01 00 协议版本/标志 (实测恒 0x0001)
0x14 4 3C 04 00 00 负载总长 (LE uint32) = UDP负载字节数
0x18 2 01 00 固定字段 (实测所有 ENet 会话帧恒 0x0001)
0x1A 2 97 01 DMX 帧序号 (LE uint16,仅 DMX 数据帧;0x197→0x198→…)
0x1C ... 类型专属字段
```
已实测的长度字段对应关系:
| 帧总长(以太网) | UDP负载长 | 0x14 处 LE 值 |
|---------------|-----------|---------------|
| 222 / 259 / 540 / 686 / 1126 | 180/217/498/644/1084 | 0xB4/0xD9/0x1F2/0x284/0x43C ✓ |
| 84 / 171(Butler→主机) | 42/129 | 0x2A/0x81 ✓ |
---
## 3. 消息类型 01 —— 设备发现 / 宣告
### 3.1 主机发现请求(222 B,主机→255.255.255.255 广播)
- 负载 180 B。0x18 处 `B4 00 00 00`(=180)。
- 负载中含:主机 IP (LE `71 7B A8 C0`=192.168.123.113)、主机 MAC、
机器名+GUID ASCII(如 `tonycaoDESKTOP-PED2LVD-f7f4a043-1a`)、尾部 `09 00`。
### 3.2 Butler 发现应答(259 B,Butler→主机单播)
- 负载 217 B。含设备名 ASCII `"Butler S2"`、Butler IP (LE `7B A8 C0 00` 区域)、
Butler MAC (`00 16 1C F1 2E 39`)、固件信息区
(`23 01 00 00 00 00 24 00 00 00 24 00 00 00 1C 00 ...` 版本/参数块)、
DMX 时序参数区(`F0 0A`=BRK 100µs? `F0 0A`、MAB 等)。
### 3.3 主机宣告帧(540 B / 686 B,主机→广播,周期 ~30s)
- type=01,686 B 版负载 644 B(0x284 ✓)。含:
- 主机 IP、广播地址、主机 MAC
- 16 B 随机/密钥材料:`43 A0 F4 F7 EC 17 56 41 81 2B 90 9B DC 99 F2 FF`
- **UTF-16LE** 主机名+GUID:`tonycaoDESKTOP-PED2LVD-f7f4a043-17ec-4156-812b-909bdc99f2ff`
- `D0 07` = 2000(LE,疑似辅助端口)
- 时间戳 `EA 07 09 07 17 14 12 F4` = 2026-09-07 17:14:20.244(年/月/日/时/分/秒/毫秒)
- 尾部 `5A 00 FF FF`
### 3.4 主机标识心跳(36 B 负载 / 78 B 帧,type=02 sub=03,主机→广播,~6-30s 周期)
```
0x14 24000000 负载长 36
0x18 0300 子类型 3
0x1A 717BA8C0 主机 IP 192.168.123.113 (LE)
0x1E 300ED559BB36 主机 MAC
```
★2026-09-09 实测(cold2.pcap):认领后每 ~6s 一发,**设备从不应答**。
(旧虚拟 Butler 实现误回 42B 状态帧,已列入修正项。)
### 3.5 "EUCE" 帧(582 B,非 ENet 魔数,主机→广播,~30s 周期)
- 魔数 `45 55 43 45` = ASCII **"EUCE"**(与 "ENet" 并存的第二种 e:cue 载荷签名)。
- `EUCE 0000 0000 0000 0000 0500 0000 …`(**0x0C 处 LE `05`**=类型/版本 5),其余大量零。
- 40s 窗口观测 6 帧(帧号 668/1576/2530/3421/4309/5222,间隔 ~908 帧 ≈ 30s),
与 type=02 心跳交替出现,属主机的周期性身份/发现通告族。
### 3.6 设备注册(type=02 sub=01 → sub=02)—— 认领前的关键步骤 ★2026-09-09 补全
冷启动实测(cold2.pcap,t=38.087):
```
主机 → Butler: 102B type=02 sub=01 主机身份宣告
0x1A-0x1D 主机 IP (LE)
0x1E-0x23 主机 MAC
Butler → 主机: 47B type=02 sub=02 注册应答 (10ms 内)
0x1A-0x1D 设备 IP (LE) 例: 017ba8c0 = 192.168.123.1
0x1E-0x23 设备 MAC 例: 00161cf12e39
0x24 00
0x25-0x28 主机 IP (LE) 例: 717ba8c0 = 192.168.123.113
0x29-0x2E 主机 MAC 例: 300ed559bb36
```
47B 注册应答完整样本:
`454e6574 02000000 00001c7c 00000000 0000 0100 2f000000 0200 017ba8c0 00161cf12e39 00717ba8c0 300ed559bb36`
广播的 sub=01 宣告(255.255.255.255)同样触发设备回 sub=02 注册帧。
### 3.7 AECH 认领(type=02 sub=05 → sub=06)—— 加密会话建立 ★★2026-09-10 完全破解
注册完成后主机立即发起认领(cold2.pcap,t=38.105,**连续 3 轮**,设备 **1ms** 内应答):
```
主机 → Butler: 552B type=02 sub=05 AECH 质询 (含 "AECH"=41454348 @0x28)
0x1A-0x1D 主机 IP (LE)
0x1E-0x23 主机 MAC
0x28-0x2B "AECH"
0x2C-0x87 92B 纯随机填充 (设备完全忽略!)
0x88-0x8B ★ 4B 质询 nonce (LE) — 唯一影响应答的输入!
0x8C-0x8F ★ 4B 主机实例随机 B12 (LE, 会话内不变, 会话间不同)
— 兼任 DMX 混淆基值: rot=(B12^r)&511 (§4.1)
0x90-0x123 148B 纯随机填充 (设备完全忽略!)
Butler → 主机: 552B type=02 sub=06 AERE 应答 (含 "AERE"=41455245 @0x28)
0x1A-0x1D / 0x41-0x44 设备 IP (LE, 两处)
0x1E-0x23 / 0x49-0x4E 设备 MAC (两处)
0x25-0x27 主机 IP 前3字节 (LE)
0x2C-0x2E 主机 MAC 后3字节
0xC0-0xC3 ★ 应答 = G(nonce) — 算法见下
其余 设备身份串 + RDM 灯具摘要 + 259 式信息块 (全部静态)
```
**★应答算法(源自 SymphoLight `Ecue.Devices.dll` 反编译,类 `Ecue.Devices.ENet.Protocol.AgiosElias`)**:
```
G(x) = x ^ TABLE[(x%32 + 1 + (x*13)%30) % 32] (u32 环绕乘法)
TABLE = 32×u32 常量表, 内嵌于 DLL 静态数组 (0A8F2B10..., 128B):
1008de00 e3b77ff6 31183e1c 90fb53c8 ff10ba5c 030bc4b6 e22935c8 10e8ede1
55f409a0 3eb0b9a3 f7333308 edf07810 8abf4078 60ec0545 1f4645c8 54094148
04c0f500 fabe50bb f3f3e54c 6c2a02cb a2546e60 4f6ae10e 51dfe8d8 2eedb0ec
74006928 48fade70 e5346e9c 1c3fe0bc f6700700 6b7f1ba0 a1143cf4 3be8426c
主机验证 (AgiosElias.i4GrvIBQ6xF): AERE blob[152] (= 帧 0xC0) == G(主机 nonce)
```
**无密钥、无 dongle 参与**——纯混淆级算法。黑盒探测(aechprobe/aechprobe2 子命令)实证:
仅 0x88-0x8B 影响应答(全雪崩),确定性;离线 G 对 cold2+fix1 共 10/10 样本命中;
虚拟 Butler 已实现本地计算(`aech::agios_g`),随机 nonce 测试 5/5 PASS。
**终极实测(2026-09-10 14:29)**:SymphoLight 重启后 .7 在无桥/无中继条件下完成
3 轮 AECH 本地应答 → 89B 注册 → DMX 流恢复 → RDM 灯具重新注册,全流程独立。
**认领不再依赖真机中继**(中继/桥代码保留作对比,桥已退役)。
**实测过滤行为**:真机 .1 只应答**源 IP=192.168.123.113** 的 AECH(任意源端口均可),
其他源 IP 的质询直接丢弃(这是真机固件行为;虚拟设备无需此过滤)。
认领通过后:主机开始 DMX 会话(§4),心跳 sub=03(36B,每 ~6s,**设备不应答**)。
---
## 4. 消息类型 04 —— 会话数据(连接后的主通道)
type=04 的帧在 0x20 处有 2 字节**子类型/方向标记**:
- `01 01` = 主机→设备 **DMX 通道数据**(形态 A)
- `02 00` = 事件/状态帧(形态 B,双向:主机发事件,Butler 周期回 42B 计数遥测 §4.3)
### 4.1 形态 A:DMX 通道数据帧(1126 B,主机→Butler,~30 Hz)★2026-09-10 完全解码(含混淆算法)
```
负载偏移 长度 示例 含义
0x00 4 454E6574 "ENet"
0x04 4 04000000 type=4
0x08 4 00001C7C 端口 31772
0x0C 6 000000000000 保留
0x12 2 0100 版本/标志
0x14 4 3C040000 负载长 1084
0x18 2 0100 wnor8bDK5nr u16 = 1 (DMX 数据消息)
0x1A 4 4CB20000 ★ 帧序号 aOKr8qAKOAa LE u32,逐帧 +1(高 2B 恒 0)
0x1E 2 0000 OcpmOpr u16 = 0
0x20 2 0101 cbY0uIrZR2S3QxGbNbNC × 2 (每端口 1B, 均=1)
0x22 4 6E100C2D ★ r1 = Random.Next() LE u32 (每帧每端口随机, MSB=0)
0x26 4 80681200 ★ r2 = Random.Next() LE u32 (端口2)
0x2A 2 0000 端口状态字节 × 2 (universe 状态表查得, 通常 0)
0x2C 2 0400 端口1 ID = 4 (LE, 两设备同值)
0x2E 2 0200/0300 ★ 端口2 ID = 节点序号 LE: 真机 .1=2, 虚拟 .7=3
0x30 2 0000 端口索引0 (LE)
0x32 2 0100 端口索引1 (LE)
0x34 2 0002 端口1 通道数 = 512 (LE! 00 02, 旧文档"BE"系误读)
0x36 2 0002 端口2 通道数 = 512 (LE)
0x38 2 0100 固定 (01 00)
0x3A 2 0000 固定
0x3C 512 ★ 端口1 DMX 通道 1-512 — zPprv0TXRRW 混淆 (见下, 非明文!)
0x23C 512 ★ 端口2 DMX 通道 513-1024 — 同式混淆 (用 r2)
```
**★通道数据混淆(2026-09-10 反编译+实证完全破解;旧文档"明文/保护字节/滚动值"系误判)**:
构建链(反编译 `iey7k4rDmrsMD1TvRywm::ScheduleUpdate` → `ENet2MessageCreator::QJiBkgLcunE`
→ `vpFbA8r8ibe5ncT1Lum7::CopyToBuffer`,60B 头结构逐字段与线值吻合):
```
r = Random.Next() # 每帧每端口独立, u31 (MSB 恒 0), 写入 0x22/0x26
rot = (B12 ^ r) & 511 # B12 = AECH 质询 0x8C-0x8F 实例随机 (AgiosElias.B12rvMuR8pT, 会话不变)
out[i] = src[(i+rot) % 512] ^ cnt[i] # src = 真实通道值
cnt[i] = (512-rot-i) & 255 (i < 512-rot)
= (rot-(i-(512-rot))) & 255 (i >= 512-rot)
```
接收方解密:`src[(i+rot)%512] = wire[i] ^ cnt[i]`(rot 由帧内 r 与会话 B12 重建)。
**实证**:cold2(B12=0x1e67079f)3893 帧 + fix1(B12=0x70959ecf)1781 帧全部解密为
跨帧稳定的全零场景(测试期间主机发的是 blackout),加解密回环 5674/5674 逐字节一致
(`aech::agios_dmx_encrypt`/`agios_dmx_decrypt`,`dmxdec` 子命令)。
**"保护字节 (CH-1)^K" 系涌现巧合**:src=0 时 wire[0x3C]=(512-rot)&255,代数化简得
K = ~B12 & 0xFF(cold2: ~0x9f=0x60 ✓、fix1: ~0xcf=0x30 ✓、ss123: K=0x70 → B12l=0x8f);
0.5% 失配 = rot%256==0 的边界帧。宿主侧另有 `kWarv47o79k`(新算法,
`out[i]=src[i]^((512-i)&255)^key[(512-i)&3]`, key=r^B12,CP7rDoD7VL4() 标志选择)——
Butler S2 走旧算法。
**端口映射**:工程 universe 号(U6/U2、U1/U5)**不上线**,主机按端口序拼包,Butler 解密后按
port1/port2 各 512B 直接映射到 RS485 输出。实测帧率 29.8/s(中位间隔 33ms),稳态恒定。
### 4.2 形态 B:事件/状态帧(主机→Butler,1126 B)★2026-09-10 完全解码
> 速率随主机活动模式变化:纯 DMX 播放时 A:B ≈ 1:2(dmx40s: 1207A+2336B/40s);
> RDM 激活稳态时 B ≈ 24.7/s、A 恒定 29.8/s(ss123)。旧版"A·B·B 固定节奏"
> 仅为其中一种模式。
>
> **帧构成 = [事件头 0x00-0x3B] + [标准 E1.20 RDM 消息] + [通道图案填充至 1084B]**。
> ss123 实证:RDM 激活稳态下 2963/2963 事件帧 100% 为 RDM 查询
> (cmd24×1886 + cmd18×645 + cmd19×352 + flag=01 内嵌副本×80),无非 RDM 事件帧。
```
负载偏移 长度 示例 含义
0x00-0x17 24 … 通用头 ("ENet"/type=4/端口/负载长 1084)
0x18 4 01000000 固定
0x1C 4 00000000 固定
0x20 2 0200 子类型 = 事件
0x22 4 65890000 ★ 事件序号 u32 LE,严格 +1(2962/2962 无例外)
0x26 4 00000000 固定
0x2A 4 CC000000 常量 0xCC
0x2E 2 0100 事件类标记(DMX-A 帧此位为节点序号 2/3)
0x30 1 00/01 ★ flag:00=独立查询帧(设备应答);
01=DMX 内嵌副本(设备不应答),此时 0x32=01
0x31 3 000000 固定
0x34 4 25000000 ★ RDM 消息长度 LE u32 = E1.20 msgLen+1(含 SubSC)
0x38 4 01000000 疑似 RDM 总线/端口号(LE,本样本恒 1)
0x3C … ★ 标准 E1.20 消息(免 0xCC 起始字节):
3C:SubSC=01 3D:msgLen 3E-43:destUID 44-49:srcUID
4A:TN 4B:port 4C:msgCount 4D-4E:subDevice 4F:CC
50-51:PID(BE) 52:PDL 53+:PD 末尾:checksum(BE)
… … ★ 通道图案填充:从 0x3C+RDM长 到帧尾,约 1011-1031B
```
**填充区性质(2026-09-10 实证)**:外观为 DMX 通道图案(渐变波形),但 24B
签名在本网段与跨网段 DMX-A 流中均只命中**周期性效果的自相似片段**
(时间差中位 -50s、偏移散乱)→ 与任何实时 universe 数据无系统对应,
判定为**主机缓冲复用/填充,无协议语义**。Butler 只需解析 RDM 消息即可
(虚拟 Butler 实现即如此)。
**E1.20 字段实证对齐**(逐偏移标注):
- 0x4F = Command Class:cmd24/18 → 0x10(DISCOVERY)、cmd19 → 0x20(GET)、cmd=1a → 0x30(SET)
- cmd24 → PID=0001 UNIQUE_BRANCH(PD=分支 lo/hi 12B);cmd18 → PID=0002 UN_MUTE
(PDL=0);cmd19 → PID=0201 SENSOR_VALUE(PD=传感器序号)
- RDM 长度字段实证:cmd24→37、cmd18→25、cmd19→26(= 24 头 + PDL + 1)
- flag=01 内嵌副本:srcUID 末字节 fe(独立帧为 ff)、0x4B(port)=01
### 4.3 Butler 42B 事件计数遥测帧(Butler→主机,~每 115ms 一帧)
```
0x14 2A000000 负载长 42
0x18 0200 子类型
0x1A 4275746C 设备名前缀 "Butl"
0x1E 0100
0x20 4 xxxxxxxx ★ Butler 本地"已收事件B帧"累计计数(LE, 严格递增)
0x24 6 010000000000
```
★2026-09-10 更正(ss123 实证):0x20-0x23 **不是** "被确认事件帧的序号",
而是 Butler 自己累计收到的事件B帧数(120s 内 +2959 ≈ 实收事件 2963;
相邻帧差 +1~+10,1039/1039 严格递增)。该帧以 ~8.7/s(中位间隔 35ms,
有事件才发)周期上报,与具体查询**无逐帧配对关系**——旧结论"ACK seq =
事件 seq - 1"是"计数器≈主机事件序号"的自然巧合,叠加分析脚本
PowerShell 运算符优先级 bug(`$a.seq -eq $q.seq - 1` 解析为
`($a.seq -eq $q.seq) - 1`)导致的假相关。收到非 RDM 事件B帧**不应**即时
回 42B。
### 4.4 Butler 状态帧(171 B 等,Butler→主机,周期性)
与 42B 遥测同构:`ENet` + type=04 + `0200` + `"Butl"` + `0100` + 事件计数器(0x317…) +
`0000` + 状态块(首字节 `57`=87…,尾部补零至 129 B 负载)。
观测到的状态帧长度分布:171/177-247 B(约 60 余种长度,随状态内容伸缩)。
★2026-09-09 补注(cold2.pcap):上述"状态帧"实际多为 RDM 应答帧(69-129B 负载);
认领后的稳态会话中,真机**几乎不发送任何自发状态帧**。旧虚拟 Butler 的
"每 3.5s 突发 10 帧 84B"为错误指纹。
★2026-09-10 再补(ss123):稳态下真机**确有** 42B 帧,但性质是 §4.3 的
事件计数遥测(~8.7/s,有事件才发),既非状态帧也非逐帧 ACK。cold2 的
168s 仅 1 帧是因为冷启动窗口内事件量小。
### 4.5 流量节奏(实测 40 s 窗口)
| 流量 | 包数 | 说明 |
|------|------|------|
| 主机→Butler 1126 B | 3543 | 形态A 1207 + 形态B 2336(该窗口 A:B≈1:2;稳态 RDM 窗口 A 29.8/s 恒定、B ~24.7/s) |
| Butler→主机 84 B(负载42B) | 1250 | 事件计数遥测(§4.3,非逐帧 ACK) |
| Butler→主机 状态帧 | ~700 | 各长度状态/心跳 |
| 主机→广播 1947 | 42 | 密钥报文,~1 Hz 成对 |
| 主机→广播 686 B | 13 | type=01 宣告,~3 s 间隔 |
| 主机→广播 78 B | 7 | type=02 标识心跳,~30 s 间隔 |
| 主机→广播 582 B | 6 | "EUCE" 帧,~30 s 间隔 |
## 5. RDM over e:net ★2026-09-09 完全解码(推翻旧版"私有变体"结论)
> 触发条件:SymphoLight RDM 窗口 → Butler 端口 Enable RDM → Discover;
> 认领通过后主机持续进行后台 RDM 发现/轮询(无需人工触发)。
> 旧版"e:cue 私有变体、字段序与 E1.20 不一致"结论系解析偏移错误——
> **e:net 封装的 RDM 负载就是标准 E1.20 报文**(含标准 checksum,真机 1396/1396 验证通过)。
RDM 流量全部复用 type=04 会话帧,无独立消息类型。
主机→设备的 RDM 查询帧完整布局见 **§4.2**(事件头 + 免 0xCC 起始的 E1.20 消息
@0x3C + 通道图案填充)。
### 5.1 e:net 封装(RDM 应答,Butler → 主机)
```
0x00 4 "ENet"
0x04 4 04000000 type=4
0x08 4 00001C7C 端口
0x0C 6 00…00 保留
0x12 2 0100
0x14 4 负载总长 (LE)
0x18 2 0200 子类型
0x1A 4 "Butl" (4275746C)
0x1E 2 0000 / 0100 标志
0x20 4 序号 (LE):参数应答 = 回显查询 0x22-0x23;DISC 脉冲 = 设备本地自增
0x24 4 00000000
0x28 2 ★ RDM 数据区长度 (LE) = msgLen + 2
0x2A ... ★ RDM 数据区
- 标准 E1.20 报文: CC 01 <msgLen@0x2C> ... <checksum 2B BE>
- DISC 脉冲: AA <9-bit 编码 16B> (无 CC01, 见 §5.4)
```
### 5.2 标准 E1.20 字段布局(0x2A 起)
```
0x2A CC Start Code (SC_RDM)
0x2B 01 Sub Start Code
0x2C msgLen Message Length = CC 起到最后一个 PD 字节的字节数 (不含 checksum)
0x2D 6B Dest UID = 主机 RDM 控制器 UID (实测 45:3a:7f:ff:ff:ff)
0x33 6B Src UID = 灯具 UID (应答时)
0x39 TN Transaction Number — 应答回显查询的 TN
0x3A Response Type (00=ACK)
0x3B Message Count
0x3C 2B Sub-Device (0000)
0x3E CC Command Class (0x11=DISC_RESP, 0x21=GET_RESP, 0x31=SET_RESP)
0x3F 2B PID (BE)
0x41 PDL
0x42 PD (PDL 字节)
末尾 2B Checksum = 从 CC(0x2A) 起 msgLen 字节的 16-bit 和, 大端
```
**checksum 规则(已验证 1396/1396)**:`sum16(CC..最后一个PD字节) == checksum(BE)`。
任何模板 patch(改 UID/TN/bridge-id)后**必须重算**,否则主机按 E1.20 规范丢弃。
(注意 PowerShell 陷阱:`-shl` 保持类型宽度,`[byte] -shl 8` 恒得 0,验证脚本须先转 int。)
### 5.3 主机 → Butler 的 RDM 请求(type=04,"cmd" @0x3D 实为 E1.20 msgLen)
请求帧两种形态:独立短帧(~112B 负载)与 1084B DMX 内嵌帧(0x30 flag=01)。
**实测真机只应答 flag=00 的帧**,flag=01 为 DMX 流内嵌副本,不答。
★2026-09-10 更正(SET 抓包实证):**0x3D 不是 e:net 命令字节,是 E1.20 msgLen**。
旧称 cmd=24/18/19/1a 恰为各类请求的 msgLen:0x24=36(UNIQUE_BRANCH, PDL=12)、
0x18=24(PDL=0 的 DISC/GET)、0x19=25(GET SENSOR_VALUE, PDL=1)、
0x1A=26(SET DMX_START_ADDRESS, PDL=2)。命令语义由 CC@0x4F 决定
(0x10 DISCOVERY / 0x20 GET / 0x30 SET)。下文沿用"cmd=xx"仅作 msgLen 类别简写。
```
0x20 2 子类型 0200
0x22 2 查询序号 (LE) — 应答在 0x20-0x21 回显
0x30 1 flag: 00=独立帧(应答), 01=DMX内嵌副本(不答)
0x3C 1 SubSC = 01
0x3D 1 E1.20 msgLen(非命令字节!)
0x3E 6 目标灯具 UID (发现时为分支下界, 全范围=ffffffffffff)
0x44 6 主机 RDM 控制器 UID (bridge-id, 453a7fffffff)
0x4A 1 TN (Transaction Number)
0x4F 1 Command Class: 0x10=DISCOVERY / 0x20=GET / 0x30=SET
0x50 2 ★ PID (BE)
0x52 1 PDL;0x53+ PD;末尾 checksum (BE)
```
### 5.4 DISC 脉冲(DISC_UNIQUE_BRANCH 响应)—— 编码已破解
脉冲不是标准 RDM 包,是 E1.20 发现响应的 9-bit 线路编码(17B):
```
AA 分隔符前缀
<16B: 8×2B 编码 UID> 每字节 b → byte1=0xAA|(b&0x55), byte2=0x55|(b&0xAA)
(GF(2) 仿射求解, 13 样本全过; 解码: b=(b1&0x55)|(b2&0xAA))
<4B: 2×2B 编码 checksum> checksum=16 位编码后 UID 字节的和, 先高字节后低字节
```
- 脉冲帧 e:net 序号(0x20)= **设备本地自增序号**(非回显查询!),每次 +1..3。
- 实测脉冲时延 2-48ms(中位 29ms);应答率 ~40%(967/2419,含 mute 效果)。
### 5.5 发现/验证语义(实测裁决)
| 主机查询 | 真机应答 | 说明 |
|---|---|---|
| cmd=24 全范围 UNIQUE_BRANCH (UID=ffffffffffff) | DISC 脉冲(59B 负载) | 未静音灯具应答 |
| cmd=18 PID=**0002** (DISC_UN_MUTE) | **70B 参数 ACK**: `CC=11 PID=0002 PDL=2 PD=0000` + 有效 checksum,延迟 17-29ms | ⚠️ **不是脉冲!** 虚拟 Butler 曾错答脉冲导致主机死循环重试(947 次),灯具永不注册 |
| cmd=18 PID=**0003** 广播 MUTE (UID=ffff…) | **无应答**(0/10) | 广播静音不应答;780 条 flag=01 0003 为 DMX 内嵌广播 MUTE |
| cmd=18 PID=0060 (DEVICE_INFO) | 87B,每灯具每轮 ~10 次/3min | 注册后主机每 ~1min 重读 |
| cmd=18 PID=00f0 (DMX_START_ADDRESS) | 70B | 同上节奏 |
| cmd=18 PID=0050 (SUPPORTED_PARAMETERS) | 78/178/182B | — |
| cmd=18 PID=80xx(厂商-specific 40 余个) | 69-72B,100% 应答 | 延迟 12-35ms(中位 29ms) |
**主机 RDM 流程**:后台持续全范围发现(~14-24/s)→ 对新 UID 发 UN_MUTE(0002) 验证
→ 拿到合法 ACK 后读 DEVICE_INFO(0060)/标签/参数注册进拓扑 → 每 ~1min 轮询。
**若 0002 应答非法,主机永远停在 0002 重试,不会进入参数读取阶段。**
### 5.6 SET 写路径(★2026-09-10 set123b.pcap 实证,GUI 改地址触发)
**抓取方法**:SymphoLight RDM 窗口改真机灯具 DMX 地址(NFM680S `010734880900`
111→114→111→112),双网段同抓。
**SET 请求**(主机→Butler,1084B 事件帧,flag=00):
- CC=**0x30**@0x4F、PID=00f0@0x50 (BE)、PDL=2@0x52、PD@0x53 = **DMX 地址 BE u16**
- 0x3D msgLen=26 (=24+2),0x34 RDM 长=27 (=msgLen+1)
- 完整布局与 GET 相同(§4.2/§5.3),仅 CC/PDL/PD 不同
**SET 应答**(Butler→主机,68B 负载):
- CC=**0x31**@0x3E (SET_COMMAND_RESPONSE)、RT=**00 (ACK)**@0x3A、**PDL=0**(无参数数据)
- TN 回显(查询 0x4A → 应答 0x39)、checksum 有效、延迟 18-25ms(RS485 指纹)
**主机行为**:SET 后 **+33ms 立即 GET 同一 PID 验证**(TN 连续 +1),应答中
新地址已生效;此后稳态轮询按新地址继续。三次 SET (114/111/112) 模式完全一致。
**虚拟 Butler 实现(2026-09-10 上线,GUI 改地址实测通过)**:SET (CC=0x30) 一律回
68B ACK(`rdm_setack_68.hex` 模板,TN/UID/seq/PID 回显 + checksum 重算,延迟 15-25ms),
并按 `(UID, PID) → PD` 落存 `set_store`;后续 GET 同 PID 时以存储值覆盖模板 PD 再重算
checksum。**只 ACK 不落存会被主机的 SET→GET 验证识破**(GUI 显示改地址无效)。
该机制对所有 PID 通用(地址/标签等均可写)。
**残余**:厂商 PID 0x0200 族的 SET 写路径未触发(GUI 未暴露对应操作);
IDENTIFY_DEVICE 等其他 SET PID 未专项。
---
## 6. 端口 1947 —— 密钥/许可报文
- 主机→`192.168.123.255` 与 `255.255.255.255` 成对广播,源端口 53685/53687。
- 负载恒定 40 B,ASCII Base64:
`BlZRQphAGxUbi7at8KEfmdZ7TdSB903b+qoC1RIA`
- Base64 解码 = 30 B 高熵二进制(`06 56 51 42 98 40 1B 15 1B 8B B6 AD F0 A1 1F 99 D6 7B 4D D4 81 F7 4D DB FA AA 02 D5 12 00`)。
- 长时间观察内容**不变** → 静态许可/身份令牌,非会话密钥。
- 与 OLA 社区所述 "key message" 一致。DMX 数据并不依赖其加密(见 §4.1 明文结论)。
---
## 7. 会话建立时序(★2026-09-09 以 cold2.pcap 冷启动实测更新)
```
t=0~23s 主机 Butler S2
│ 01 发现请求 180B 广播(~每3-5s) ─▶│
│◀────── 01 发现应答 217B (设备名/MAC/IP/ConnectedTo) │
t=38.087 │ 02 宣告 sub=01 102B ─────────▶│
│◀────── 02 注册 sub=02 47B (10ms) │ ★ 认领前关键步骤
t=38.105 │ 02 AECH sub=05 552B ─────────▶│
│◀────── 02 AERE sub=06 552B (1ms) │ ×3 轮 (38.105/38.111/38.140)
t=40+ │ 04 DMX帧 A(seq) ─────────────▶│
│ 04 事件帧 B ──────────────────▶│
│◀─── 04 计数遥测 42B (~115ms) ────│ … A 29.8/s 恒定 + B 随模式 …
│ 04 RDM 查询(cmd24/18) ────────▶│ 后台持续发现/轮询
│◀─────── 04 RDM 脉冲/参数应答 ────│ 延迟 12-48ms
t=44+ │ 02 心跳 sub=03 36B (每~6s) ───▶│ ★ 设备不应答
```
Butler 若 ~数秒收不到主机数据则自动回落 standalone 模式(播 SD 卡 show)。
---
## 8. ESP32 模拟可行性结论
| 需求 | 结论 |
|------|------|
| 接收 DMX 数据 | ✅ **可行**:明文。监听 UDP 31772,过滤 `454E6574 04000000` + 0x20 处 `0101`,从负载 0x3C 取 1024 B 通道数据 |
| 维持连接 | ⚠️ 需模拟:周期回复 ACK(0x2A 负载、递增序号)+ 状态帧,防止主机判定离线;同时需应答 type=01 发现(259B 应答帧格式见 §3.2) |
| 阻止 standalone 抢发 | 停止收 DMX 后设备(真 Butler)会播 SD show;ESP32 自身无此问题 |
| 1947 端口 | 无需实现(仅主机广播,设备不监听) |
| 加密 | 无需解密——通道数据明文 |
ESP32 最小实现 = **被动接收**:绑 31772 收形态A 帧、取 0x3C 起 1024 B 即得到全通道值;
要长期稳定在线再补 ACK/状态/发现应答三类模拟帧(结构均已在本文档给出)。
---
## 9. 抓包样本索引
| 文件 | 内容 |
|------|------|
| `C:\Users\tonycao\AppData\Local\Temp\opencode\discovery.pcap` | 启动/发现阶段 56 帧 |
| `C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s.pcap` | 驱动阶段 40s,5903 帧 |
| `dmx_one_hex.txt` | 1126 B DMX 帧完整 hex ×2 |
| `rdm3.pcap` | RDM 会话(Enable RDM + Discover,DUT 停发后抓取) |
| `f686.txt` | 686 B 宣告帧完整 hex |
| `cold2.pcap` ★ | **真机 .1 冷启动全程**(168.7s/19471 帧:发现→注册→AECH→RDM),2026-09-09 |
| `fix1.pcap` ★ | **虚拟 Butler .7 验证**(58 网段,P0 修复后主机读 DEVICE_INFO 全程),2026-09-09 |
| `ss123.pcap` / `ss58.pcap` ★ | **稳态双网段对比**(120s:.1 真机 vs .7 虚拟),2026-09-10 |
| `fix2.pcap` / `fix3.pcap` ★ | **P1 修复后 .7 验证**(cmd24 收敛/稳态轮询/42B 遥测),2026-09-10 |
## 10. 待确认项(低置信度字段)
- ~~0x22 处 8 B 字段~~ → **已破解**(§4.1,2026-09-10):0x22/0x26 = 两端口首通道
保护字节 `(CH-1) XOR K`(K 会话相关,三会话验证 2000/2000);残余开放:
0x23-0x25/0x27-0x29 共 6B 为会话密钥派生滚动值(标准哈希/计数器/时间戳
全部排除),破解需先还原 AECH 密钥协商;K 与 AECH 凭证字节的对应关系未考
- ~~形态 B 事件帧内部布局~~ → **已完全解码**(§4.2,2026-09-10):事件头 +
标准 E1.20 消息 @0x3C + 通道图案填充(缓冲复用,无协议语义);
事件序号 u32@0x22 严格 +1(2962/2962,旧"偶发交换/重号"为抓包顺序现象)。
残余细节:0x38 疑似总线/端口号(样本恒 1,多端口语境未验证);flag=01
内嵌副本的生成规则(srcUID 末字节 fe、port=01)
- Butler 应答帧(217B 负载)内的版本/时序参数块逐字段含义
- "EUCE" 帧内部布局(仅确认魔数与 05 版本字节)
- ~~RDM 数据区内层字段序~~ → 已完全解码,标准 E1.20(§5.2,2026-09-09)
- cmd=19/1a(SET 类,厂商 PID 0x0200 族)的请求/应答布局(§5.6)
- RDM / show 上传 / 配置写回报文:RDM 已覆盖(§5),show 上传与配置写回未触发
---
## 11. 虚拟 Butler 实现与验证(★2026-09-09)
### 11.1 已实现能力(Rust,enet-validator)
- 发现应答(217B,Connected To 动态取请求方主机 IP/MAC,网段自适应)
- 注册应答(sub=02,47B)
- **AECH 认领本地计算(2026-09-10 起)**:虚拟设备收 AECH → 取 nonce@0x88-0x8B(LE) →
本地算 `G(nonce)`(§3.7,无密钥)写入 AERE 0xC0-0xC3 → 直接应答,**无需真机中继/桥**。
(旧中继路径保留:经源 113 socket 转发真机 .1 算 AERE;非 123 网段曾需 Windows 桥。)
- RDM 生成式应答:全新 UID 灯具的 DISC 脉冲(§5.4 编码器)+ pid_map 模板参数应答
- 高速主循环:socket 读超时 2ms(原 80/100ms 时吞吐仅 ~5/s,无法支撑发现收敛)
- DMX 接收:形态 A 帧按 §4.1 布局;0x22/0x26 = 每端口随机 r,通道数据经
zPprv0TXRRW 混淆 → **已实现实时解密**(AECH 认领时取 B12=质询 0x8C-0x8F,
`agios_dmx_decrypt` 还原真实通道值后打日志;旧日志里的 "CH1-16" 递减序列
实为 blackout 全零场景的密文形态)
### 11.2 灯具注册根因与修复(P0)
**症状**:虚拟 Butler 被认领、DMX 流入、主机能发现全新 UID,但 RDM 拓扑里
.7 的总线始终 "no devices";主机只对 .7 反复发 PID=0002(947 次)从不读 DEVICE_INFO。
**根因(cold2.pcap 实证)**:
1. 虚拟 Butler 把 PID=0002(DISC_UN_MUTE)错答成 DISC 脉冲;
真机应答为 70B 参数 ACK(§5.5)。主机拿不到合法 UN_MUTE ACK → 死循环重试。
2. 所有模板应答 patch UID 后未重算 E1.20 checksum(§5.2)→ 主机按规范丢弃。
**修复**:0002 走 pid_map 70B 模板 + TN 回显(查询 0x4A → 应答 0x39)+
全部 RDM 应答 patch 后重算 checksum。
**验证结果(fix1.pcap + GUI)**:主机对 .7 的查询变为 `0002×772 + 0060×771`
(3 个新 UID 各 257 次 DEVICE_INFO,0002:0060 = 1:1 与真机模式一致),
.7 应答 checksum 777/777 有效;SymphoLight RDM 拓扑显示
**Butler S2(.7) → Port 1 (U1) — 3 devices**,灯具注册成功。
### 11.3 稳态对齐修复(P1,2026-09-10 全部落地)
**根因(ss123/ss58 双网段对比实证)**:cmd=24 是标准 DISC_UNIQUE_BRANCH
(分支范围 lo@0x53-0x58 / hi@0x59-0x5E),真机灯具只答 UID 在范围内的查询
(二分收敛);虚拟 Butler 无视范围全答 → 主机二分永不排空 → 发现洪水
51/s(真机仅 16/s)→ 稳态轮询对 .7 永不启动。
**已修复项**:
1. cmd24 分支范围过滤,仅范围内灯具各答一个脉冲(延时入队);
2. RDM 应答延迟队列:脉冲 2-40ms / 参数 15-40ms(LCG),对齐真机 RS485
延迟指纹(中位 29ms);
3. 心跳 sub=03 改静默(真机从不应答);删除 84B 突发遥测;
4. 参数应答按镜像灯具取帧池:FIXTURE_MIRROR 130→125 / 131→80900 /
132→26c9,cmd=19/1a 再按 PD[0](传感器序号)匹配;
5. 42B 改为周期遥测(§4.3):event_ctr 累计事件B帧,~115ms 上报一次,
不再即时 ACK。
**验证(fix2/fix3.pcap)**:cmd24 率 51/s → **8.5/s**(真机 15.7/s,洪水消除);
主机对 .7 启动稳态轮询:0002/0060 每灯具每周期 + **0201 SENSOR_VALUE
23/17/4 次,与镜像灯具传感器数完全一致**;脉冲 4.9/s(真机 6.2/s);
42B 遥测 6.5/s 计数严格递增(真机 8.7/s);RDM 应答 checksum 全有效。
**残余开放项(不影响注册与轮询)**:42B 精确节流规则未明(真机差值 +1~+10
不平滑);mute 状态机语义未明(真机灯具在广播 0003 后反而恢复应答,未实现
mute 亦无碍);cmd=19/1a 中 SET 类厂商 PID(0x0200 族)写路径未专项。
### 11.4 运维要点
- 模板全部 `include_str!` 编译期内嵌,改 samples/*.hex 必须重新编译部署
- 部署:分块 base64 + Invoke-SSHCommand(Set-SFTPItem 不可用);.7 用 `~/run_butler.sh` 启动
- 进程窗口:用 86400s(24h)避免反复到期退出
- ~~桥若退出,.7 认领即卡死~~ → 2026-09-10 起本地计算 G(§3.7),桥/中继不再是必需
- .200(Windows 次 IP)的 Butler 必须与 .7 错开 MAC,避免冲突
- 离线分析:`enet-validator dmxdec <tsv>` — DMX 帧 zPprv0TXRRW 解密 + 加解密回环验证
(需 TSV 含 AECH 质询以取 B12);`dmxroll <tsv>` — 滚动值/保护字节历史分析;
tshark 导出 TSV 经 PowerShell 重定向会落为 UTF-16LE(带 BOM)
### 11.5 DMX 帧解码里程碑(2026-09-10,§4.1)★混淆算法完全破解
- 60B 帧头逐字段反编译对账(`vpFbA8r8ibe5ncT1Lum7` 结构体,Pack=2):
帧序号 u32@0x1A、cbY0u×2@0x20、r1/r2@0x22/0x26、端口ID@0x2C/0x2E、
端口索引@0x30/0x32、通道数 512 LE@0x34/0x36;
- **通道数据非明文**——zPprv0TXRRW 逐帧混淆:rot=(B12^r)&511,r=Random.Next() 每帧每端口,
B12=AECH 实例随机;cold2+fix1 共 5674 帧全部解密为稳定 blackout 场景,
加解密回环 5674/5674 逐字节一致;
- 旧结论 "(CH-1)^K 保护字节/6B 滚动值/会话密钥 K" 全部证伪——K=~B12&0xFF 为
计数器结构的涌现巧合(0.5% 失配=rot%256==0 边界);滚动值=Random.Next() 本身;
- 主机侧复现要素齐备:B12 主机自选、r 随机、加解密双向实现(`dmxdec` 验证);
- 帧率 29.8/s 恒定,A:B 比例随模式变化(推翻"A·B·B 固定节奏 ~40Hz")。
### 11.6 主机侧等效播放实测(2026-09-10,档 4 首个里程碑)★
`dmxsend` 子命令实现**完整主机侧流程**(不依赖 SymphoLight):
1. sub=01 宣告 → 收 89B 注册应答 ✓
2. AECH 认领(nonce/B12 均自选随机)→ .7 本地 G 应答 `AERE[0xC0]==G(nonce)` ✓
3. 30fps 发送 zPprv0TXRRW 加密 DMX(583 帧,高识别度图案 ch1=AA/ch2=55/ch3-10=1..128/
ch101=EF/ch513=77/ch1024=33)→ **.7 实时解密日志精确还原** `CH1-16: aa550102040810204080…` ✓
全链路(认领→注册→加密→解密)闭环自证。对真机 .1 仿冒的唯一额外约束:
AECH 质询须源自 192.168.123.113(真机固件过滤,§3.7)。
### 11.7 hostdemo:真机 .1 主机侧仿真(2026-09-10 晚)★进行中
`hostdemo [目标IP] [秒]` 子命令(src/hostdemo.rs):完整主机等效流程——发现 →
冷启动前奏(discovery/announce 广播 ×10s + EUCE)→ 102B 身份帧 → AECH×3 →
DISC_UN_MUTE 广播 → UNIQUE_BRANCH 二分遍历(含 DISC 脉冲解码,§5.4 逆运算)
→ 逐 UID DISC_MUTE → GET DEVICE_INFO/DMX_START_ADDRESS → 30fps 加密 DMX 呼吸控制。
**实测进展**:
- ✅ 发现应答(217B)每次都正常——设备在任何状态下都答发现
- ✅ **AECH 认领真机 3 次完整通过**(3 轮 G(nonce) 验证 PASS,nonce/B12 自选随机)
- ⏳ RDM 发现/控制段:尚未跑到(受下方认领门控阻断)
- cold2 时序佐证:首个 RDM 请求在认领后 54ms 发出;首个 RDM 应答与首个 42B
遥测同步出现于认领后 1.37s(此前主机已连发 ~14 次 UNIQUE_BRANCH——
冷启动应答延迟大属正常,hostdemo 每分支按 4 次 ×250ms 重试)
**★新发现:真机认领门控/锁死行为(§3.7 补遗)**
连续 3 次成功认领但**不发任何会话流量**(无 DMX/心跳)后,.1 进入锁定态:
- 对 ident102 不回 47B 注册应答、对 AECH 质询完全不答——
**连 cold2 原始 ident/质询的逐字节重放也一并忽略**(设备端状态,非帧构造问题)
- 发现请求(type=1)仍正常应答(225B)
- SymphoLight 于锁定约 55-70 分钟后认领成功(19:2x,期间它先广播了 >90s 才发起)
- SymphoLight 关闭(20:04,无释放帧、无 AECH,DMX 流直接消失)后 15 分钟仍锁定
- 暂定模型:**会话保持 ≈ 55-60 分钟**(自最后一次会话流量起算),期间拒答一切新认领
---
## 12. 缺口清单与攻关路线图(2026-09-10)
### 档 1 — 核心缺口(阻塞完整复现)
| 缺口 | 位置 | 影响 |
|---|---|---|
| ~~AECH 密钥协商/凭证算法~~ | §3.7 | **已破解(2026-09-10)**:反编译 SymphoLight Ecue.Devices.dll 得 AgiosElias.G —— 无密钥公开算法,本地计算应答,10/10 样本 + 在线 5/5 验证。**同日 SymphoLight 重启无中继终极实测通过**(14:29:35,3 轮本地 G 应答 + 89B 注册 + DMX 恢复 + RDM 灯具重新注册,Windows 桥已退役)|
| ~~会话 K 派生关系~~ | §4.1 | **证伪并消解(2026-09-10)**:K 不是密钥——是 zPprv0TXRRW 计数器结构的涌现巧合,K=~B12&0xFF(cold2/fix1 精确吻合)|
| ~~DMX 帧 6B 滚动值/通道加密~~(0x22-0x29 + 0x3C/0x23C 数据区) | §4.1 | **已破解(2026-09-10)**:r=Random.Next() 每帧每端口;数据 zPprv0TXRRW 混淆,rot=(B12^r)&511;5674 帧解密+回环 100% |
| ~~形态 B 事件帧内部布局~~ | §4.2 | **已完全解码(2026-09-10)**:事件头 + 标准 E1.20 @0x3C + 通道图案填充(无协议语义);残余细节见 §10 |
### 档 2 — 写路径/配置类(从未触发过捕获)
- ~~RDM SET 写路径(DMX_START_ADDRESS)~~ → **已解码(§5.6,2026-09-10)**:
CC=0x30 请求 / CC=0x31 ACK PDL=0 应答 / SET 后 +33ms GET 验证;
残余:厂商 PID 0x0200 族 SET、IDENTIFY_DEVICE 等其他 SET PID
- **show 上传 / SD 卡 / standalone 配置写回**:协议存在性已知,报文从未捕获
- **节点序号(0x2E)分配机制**:仅从 2 个样本推断(.1=2/.7=3),注册顺序还是工程配置未验证
### 档 3 — 辅助帧细节
- **EUCE 帧**(§3.5):仅确认魔数 + 版本字节,内部布局空白
- **217B 发现应答**(§3.2):版本/时序参数块逐字段含义不全
- **1947 许可报文**(§6):静态 token 已记录,Butler 是否/如何校验未知
- 行为指纹级小问题(不影响功能):42B 遥测精确节流规则(差值 +1~+10)、形态 B 序号偶发交换/重号、RDM mute 状态机语义
### 档 4 — 主机侧仿真(协议要素齐备,首个里程碑已实测 ★§11.6)
文档主要覆盖**设备侧**(Butler 视角收/答)。主机侧全部协议要素已解码且
`dmxsend` 已实测闭环(§11.6:认领→注册→加密 DMX→对端精确解密);
剩余工作为产品化(工程解析、场景调度、RDM 主轮询、ESP32 移植)。
§8 的 ESP32 章节也只写了"消费 DMX"的被动模式。
### 建议攻关顺序
1. ~~形态 B 事件帧布局~~ —— **已完成(2026-09-10,§4.2)**;
2. ~~AECH 密钥协商~~ —— **已完成(2026-09-10,§3.7:反编译 AgiosElias,无密钥 G 算法)**;
3. ~~RDM SET 写路径~~ —— **已完成(2026-09-10,§5.6)**;
4. ~~K 派生 + 6B 滚动值 + DMX 加密~~ —— **已完成(2026-09-10,§4.1)**:反编译
ScheduleUpdate 构建链,zPprv0TXRRW 混淆完全破解(rot=(B12^r)&511),
"K" 证实为 ~B12&0xFF 涌现巧合;5674 帧解密+回环 100%,加解密双向实现;
5. 顺带修正:0x3D "cmd 字节"实为 E1.20 msgLen(§5.3 更正)。
**至此档 1 全部关闭。** 剩余开放项均为非阻塞细节(档 2/3):show 上传/SD 写回
(未捕获)、节点序号分配机制、EUCE 内部布局、42B 节流细则、mute 状态机。

View File

@ -0,0 +1,85 @@
# e:net 协议 Rust 独立验证程序 —— 实施计划
> 目标:关闭 SymphoLight 5.5,用 Rust 程序独立扮演 e:cue 主机,按《enet_protocol.md》
> 对每一条协议做**实测验证**(连接真机 Butler S2 192.168.123.1,以其真实应答为判据)。
---
## 0. 前置条件
| 项 | 动作 | 状态 |
|----|------|------|
| P1 | 关闭 SymphoLight 5.5(PID 检查 + Stop-Process) | 待执行 |
| P2 | 确认 Butler 在线(ping 192.168.123.1) | 已通 |
| P3 | 网口"以太网 3" = 192.168.123.113/24(Rust 程序绑定此地址) | 已确认 |
| P4 | 抓包样本齐备(discovery/dmx40s/rdm3 classic pcap)用于报文参数提取 | 已有 |
| P5 | 注意:SymphoLight 关闭后 Butler 回落 standalone 播 SD show(DMX 输出仍活动),Rust 程序需完成"发现→认领"接管 | 已知 |
---
## 1. 技术选型与工程结构
```
enet-validator/
├── Cargo.toml # clap(CLI) anyhow hex rand;(无 async,std::net::UdpSocket 足够)
├── src/
│ ├── main.rs # 子命令入口:discover / dmux / session / rdm / all
│ ├── enet.rs # ENet 帧构造/解析(§2 通用头:magic+type+port+len)
│ ├── discovery.rs # §3:type=01 发现请求/应答解析、宣告帧、type=02 心跳、EUCE 帧
│ ├── dmx.rs # §4.1:形态A DMX 帧(60B头+1024B通道,序号0x1A、时间戳0x22)
│ ├── event.rs # §4.2-4.4:形态B 事件帧、ACK 配对、状态帧解析
│ ├── rdm.rs # §5:RDM 事件帧封装(0x4A cc01)、应答解析(0x28长度+cc01)、UID/TN
│ └── report.rs # 验证报告输出(JSON + 控制台 PASS/FAIL)
└── samples/ # 从 pcap 提取的基准报文(hex 常量,参数化模板)
```
报文构造策略:**以实测样本为模板**(非凭空拼字段)——从 pcap 提取 222B 发现请求、
686B 宣告、1126B DMX 帧、1126B 事件帧的原始字节,替换可变字段(序号/时间戳/通道值)。
---
## 2. 验证项分解(对应协议文档章节,判据=Butler 实际行为)
| ID | 协议条目(文档章节) | 程序动作 | PASS 判据 |
|----|---------------------|----------|-----------|
| T1 | 发现请求 222B 广播(§3.1→§3.2) | 发 type=01 请求 → 监听 31772 | 收到 259B 应答;含 ASCII "Butler S2"、MAC `00:16:1c:f1:2e:39`、负载长字段 0xD9 ✓ |
| T2 | 主机宣告 686B(§3.3) | 发 type=01 宣告(主机名GUID/时间/密钥材料按模板) | 不破坏会话;后续 Butler 状态流正常 |
| T3 | type=02 心跳 78B(§3.4) | 发主机 IP+MAC 标识帧 | 同上,无异常 |
| T4 | EUCE 帧 582B(§3.5) | 发 EUCE 魔数帧(版本05@0x0C) | 同上(观察性验证) |
| T5 | DMX 数据帧(§4.1) | 按 A·B·B 节奏发形态A:序号 0x1A 递增、0x22 时间戳变化、0x3C 起 1024B 通道值(先全0→渐变图案) | Butler 持续 ACK/状态应答(不回落 standalone);7 段显示离开 standalone |
| T6 | 事件帧 + ACK 配对(§4.2/4.3) | 每帧 DMX 配 2 事件帧,记录事件序号 0x22 与 ACK 序号 0x20 | ACK 配对率 ≥95%;序号规律与文档一致 |
| T7 | 状态帧解析(§4.4) | 被动接收 171-247B 状态帧 | 长度字段 0x14 自洽率 100%;"Butl"前缀 100% |
| T8 | 会话保持(§6) | 持续会话 ≥60s | 全程无中断;通道值变化(全0↔全255↔渐变)期间 ACK 持续 |
| T9 | 回落行为(§6 注) | 停发 10s 后观察 | Butler 回落 standalone(状态帧模式变化)→ 再恢复会话成功 |
| T10 | RDM 应答解析(§5.1-5.2) | 主动监听(若 Butler 已 Enable RDM)或发送 RDM 请求事件帧(0x4A cc01 模板) | 收到 cc01 起始帧;0x28 长度自洽;提取灯具 UID |
| T11 | 写地址探索(§5.4) | 在 RDM 请求模板中尝试标准 PID 0x0330 SET 与私有 PID 0x2100 族 | 观察 Butler 是否下发 RDM 应答(记录 PID 行为,产出逆向数据) |
判据统一由程序自动记录(报文 hex + 时间戳 + PASS/FAIL),输出 `validation_report.json`。
---
## 3. 执行阶段
- **阶段0 准备**(P1-P5):关 SymphoLight、(可选)抓 10s standalone 对照流量存档。
- **阶段1 骨架**:Rust 工程 + pcap 样本提取器(PowerShell 导出基准 hex 常量)。
- **阶段2 单向验证**:T1→T2/T3/T4(每项独立子命令,互不依赖)。
- **阶段3 会话验证**:T5→T6→T7→T8(核心:接管 Butler,验证 DMX/ACK/状态全链路)。
- **阶段4 行为验证**:T9 回落与恢复。
- **阶段5 RDM 探索**:T10/T11(可选,依赖 Butler RDM 端口已启用状态)。
- **阶段6 报告**:validation_report.json + 更新 enet_protocol.md(修正/补充实测发现)。
## 4. 风险与对策
| 风险 | 对策 |
|------|------|
| Butler 在 standalone 播 show 干扰观察 | T5 认领成功即接管 DMX;必要时临时拔 SD 卡(用户决定) |
| DUT(ESP32-P4) 仍在总线上 | 验证前确认 DUT 状态:保持暂停(download mode)或已恢复;勿冲突 |
| 报文字段拼错导致 Butler 无响应 | 全部以 pcap 样本为模板参数化;T1 失败时 diff 实发与样本 |
| 端口 31772 被占(SymphoLight 残留) | P1 确认进程退出;程序绑定失败即报错 |
| RDM 未启用时 T10/T11 无效 | 作为可选阶段,跳过不影响主结论 |
## 5. 交付物
1. Rust 工程(可执行 `enet-validator.exe`,子命令式)
2. `validation_report.json` + 控制台报告
3. `enet_protocol.md` 增补(以 Rust 实测修正/确认各字段)

File diff suppressed because it is too large Load Diff

38
scripts/Verify-Rdm.ps1 Normal file
View File

@ -0,0 +1,38 @@
# Verify-Rdm.ps1 — 验证 enet_protocol.md §5 RDM 断言
$ErrorActionPreference = 'Stop'
$script:pass = 0; $script:fail = 0
function Check($name, $cond, $detail) {
if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) }
else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) }
}
. "C:\Users\tonycao\work\light\enet_lib.ps1"
$pkts = Read-Pcap "C:\Users\tonycao\AppData\Local\Temp\opencode\rdm3_classic.pcap"
$msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } }
Write-Host ("RDM 抓包总包数: {0}" -f $msgs.Count)
# R1: RDM 应答帧 = Butler→主机 + "Butl" + cc01 起始
$rdm = @($msgs | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Payload.Length -gt 0x2C -and (LE32 $_.Payload 4) -eq 4 -and (Hex $_.Payload 0x1A 4) -eq '4275746c' -and (Hex $_.Payload 0x2A 2) -eq 'cc01' })
$dist = ($rdm | Group-Object { $_.Payload.Length } | ForEach-Object { "$($_.Name)x$($_.Count)" }) -join ','
Check "R1 RDM帧=Butler→主机+cc01" ($rdm.Count -gt 0) ("| n={0} ({1})" -f $rdm.Count, $dist)
# R2: 0x28 长度字段 == 实际 RDM 区长 (负载长 - 0x2A)
$bad = @($rdm | Where-Object { [int](LE16 $_.Payload 0x28) -ne ($_.Payload.Length - 0x2A) })
Check "R2 RDM长度字段0x28" ($bad.Count -eq 0) ("| {0}/{1} 一致" -f ($rdm.Count - $bad.Count), $rdm.Count)
# R3: 灯具 UID (Src @ 0x32, 6字节) — 至少 1 台, 收集全部
$uids = @($rdm | ForEach-Object { (Hex $_.Payload 0x32 6) } | Sort-Object -Unique)
$uidFmt = ($uids | ForEach-Object { ($_.ToCharArray() | ForEach-Object { if ($_ -eq '*') { $_ } else { $_ } }) -join '' }) -join ':'
Check "R3 灯具UID@0x32" ($uids.Count -ge 1) ("| {0} 台: {1}" -f $uids.Count, ($uids -join ' '))
# R4: DISC 发现响应原始脉冲
$disc = @($msgs | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Payload.Length -gt 0x2E -and (Hex $_.Payload 0x2A 4) -eq 'aaaa5dbb' })
Check "R4 DISC发现响应脉冲" ($disc.Count -gt 0) ("| n={0} 负载={1}" -f $disc.Count, $(if($disc.Count){ (Hex $disc[0].Payload 0x2A 17) } else { '-' }))
# R5: 主机请求封装在 1126 事件帧 (0x4A 处 cc01)
$req = @($msgs | Where-Object { $_.Src -eq '192.168.123.113' -and $_.Payload.Length -ge 0x4C -and (Hex $_.Payload 0x4A 2) -eq 'cc01' })
Check "R5 主机请求封装于事件帧@0x4A" ($req.Count -gt 0) ("| n={0}" -f $req.Count)
Write-Host "`n========================================"
Write-Host ("RDM 验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail)
if ($script:fail -eq 0) { Write-Host ">>> RDM 协议断言全部验证通过 <<<" }

47
scripts/enet_lib.ps1 Normal file
View File

@ -0,0 +1,47 @@
# enet_lib.ps1 — e:net 协议验证公共函数库
$script:pass = 0; $script:fail = 0
function Check($name, $cond, $detail) {
if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) }
else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) }
}
function Read-Pcap([string]$path) {
$b = [IO.File]::ReadAllBytes($path)
function U32([byte[]]$x, [int]$o, [bool]$sw) {
if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] }
return [BitConverter]::ToUInt32($x, $o)
}
$magic = U32 $b 0 $false
$swap = ($magic -eq 0xa1b2c3d4)
$pkts = New-Object System.Collections.Generic.List[object]
$o = 24
while ($o + 16 -le $b.Length) {
$incl = U32 $b ($o+8) $swap
if ($o + 16 + $incl -gt $b.Length) { break }
$data = New-Object byte[] $incl
[Array]::Copy($b, $o+16, $data, 0, $incl)
$pkts.Add([pscustomobject]@{ Len = $incl; Data = $data })
$o += 16 + $incl
}
return $pkts
}
function UdpPayload($pkt) {
$d = $pkt.Data
if ($d.Length -lt 42) { return $null }
$ethType = ([uint32]$d[12] -shl 8) -bor $d[13]
$off = 14
if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 }
if ($ethType -ne 0x0800) { return $null }
$ihl = ($d[$off] -band 0x0F) * 4
if ($d[$off + 9] -ne 17) { return $null }
$ipStart = $off; $udp = $off + $ihl
$src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15]
$dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19]
$sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1]
$dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3]
$pl = New-Object byte[] ($d.Length - $udp - 8)
[Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length)
return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl }
}
function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) }
function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) }
function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' }

View File

@ -0,0 +1,55 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\ss123.tsv")
$acks = New-Object System.Collections.ArrayList
$qs = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x2A) { continue }
$t = [double]$p[0]
if ($p[1] -eq "192.168.123.1" -and $p[2] -eq "192.168.123.113" -and $b.Length -eq 42 -and [int]$b[4] -eq 4) {
$v = [int]$b[0x20] + [int]$b[0x21]*256 + [int]$b[0x22]*65536 + [int]$b[0x23]*16777216
[void]$acks.Add(@{ t=$t; v=$v; hex=(($b[0x1A..0x29] | ForEach-Object { $_.ToString("x2") }) -join "") })
}
if ($p[1] -eq "192.168.123.113" -and $p[2] -eq "192.168.123.1" -and $b.Length -gt 0x52 -and $b.Length -lt 2000 -and [int]$b[4] -eq 4 -and $b[0x20] -eq 2 -and $b[0x21] -eq 0 -and $b[0x30] -eq 0 -and $b[0x3C] -eq 1) {
[void]$qs.Add(@{ t=$t; cmd=$b[0x3D] })
}
}
Write-Host ("42B 帧数={0}" -f $acks.Count)
# 0x20-0x23 值序列: 单调性 + 间隔
$mono = 0; $notMono = 0; $gaps = @{}
for ($i = 1; $i -lt $acks.Count; $i++) {
$d = $acks[$i].v - $acks[$i-1].v
if ($d -gt 0) { $mono++ } else { $notMono++ }
$gk = "$d"
if (-not $gaps.ContainsKey($gk)) { $gaps[$gk] = 0 }; $gaps[$gk]++
}
Write-Host ("0x20-0x23 值: 递增={0} 非递增={1}" -f $mono, $notMono)
Write-Host "--- 相邻差值分布 ---"
$gaps.GetEnumerator() | Sort-Object { [int]$_.Key } | Select-Object -First 10 | ForEach-Object { Write-Host (" 差={0} : {1}" -f $_.Key, $_.Value) }
Write-Host "--- 前 8 个 42B 帧 (时间/值/hex) ---"
foreach ($a in ($acks | Select-Object -First 8)) { Write-Host (" t={0,8:F3} v={1} hex={2}" -f $a.t, $a.v, $a.hex) }
Write-Host "--- 后 4 个 ---"
foreach ($a in ($acks | Select-Object -Last 4)) { Write-Host (" t={0,8:F3} v={1} hex={2}" -f $a.t, $a.v, $a.hex) }
# 42B 帧与 cmd 查询的时间相关性: 每个 42B 前 50ms 内最近的查询 cmd
$byCmd = @{}
foreach ($a in $acks) {
$best = $null
for ($i = $qs.Count - 1; $i -ge 0; $i--) { if ($qs[$i].t -le $a.t) { $best = $qs[$i]; break } }
$k = if ($best -and (($a.t - $best.t) -lt 0.05)) { "cmd={0:x2}" -f $best.cmd } else { "无临近查询" }
if (-not $byCmd.ContainsKey($k)) { $byCmd[$k] = 0 }; $byCmd[$k]++
}
Write-Host "--- 42B 帧前 50ms 内最近查询 ---"
$byCmd.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
# 42B 帧间时间隔
$tlast = $null; $tg = New-Object System.Collections.ArrayList
foreach ($a in $acks) { if ($null -ne $tlast) { [void]$tg.Add([int](($a.t - $tlast) * 1000)) }; $tlast = $a.t }
$tgs = $tg | Sort-Object
Write-Host ("--- 42B 帧时间隔ms: min={0} 中位={1} max={2}" -f $tgs[0], $tgs[[int]($tgs.Count/2)], $tgs[-1])

View File

@ -0,0 +1,48 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
# Extract AECH (sub=05) / AERE (sub=06) frames from cold2.tsv, diff across rounds
$raw = [IO.File]::ReadAllBytes("$env:TEMP\opencode\cold2.tsv")
$text = [Text.Encoding]::UTF8.GetString($raw)
if ($raw[0] -eq 0xFF -and $raw[1] -eq 0xFE) { $text = [Text.Encoding]::Unicode.GetString($raw) }
$lines = $text -split "`r?`n"
function H2B($hex) { $h = $hex -replace " ",""; $n = $h.Length/2; $b = New-Object byte[] $n; for ($i=0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($h.Substring(2*$i,2),16) }; return $b }
$chals = @(); $resps = @()
foreach ($l in $lines) {
$p = $l -split "`t"
if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]
if ($b.Length -lt 0x30) { continue }
if ($b[0] -ne 0x45 -or $b[1] -ne 0x4e -or $b[2] -ne 0x65 -or $b[3] -ne 0x74) { continue } # ENet
$tag = [Text.Encoding]::ASCII.GetString($b[0x28..0x2B])
if ($tag -eq "AECH") { $chals += ,@{ t=[double]$p[0]; src=$p[1]; b=$b } }
if ($tag -eq "AERE") { $resps += ,@{ t=[double]$p[0]; src=$p[1]; b=$b } }
}
Write-Host "AECH challenges: $($chals.Count), AERE responses: $($resps.Count)"
foreach ($c in $chals) { Write-Host (" CHAL t={0,8:F3} src={1} len={2}" -f $c.t, $c.src, $c.b.Length) }
foreach ($r in $resps) { Write-Host (" RESP t={0,8:F3} src={1} len={2}" -f $r.t, $r.src, $r.b.Length) }
# diff rounds: varying offset runs
function Diff-Frames($frames, $label) {
if ($frames.Count -lt 2) { return }
$len = $frames[0].b.Length
$vary = @()
for ($i = 0; $i -lt $len; $i++) {
$v = $frames[0].b[$i]; $diff = $false
foreach ($f in $frames) { if ($f.b[$i] -ne $v) { $diff = $true; break } }
if ($diff) { $vary += $i }
}
Write-Host "`n=== $label varying offsets ($($vary.Count) bytes) ==="
# group into runs
$runs = @(); $start = -1; $prev = -2
foreach ($o in $vary) { if ($o -ne $prev + 1) { if ($start -ge 0) { $runs += ,"$start-$prev" }; $start = $o }; $prev = $o }
if ($start -ge 0) { $runs += ,"$start-$prev" }
foreach ($r in $runs) {
$a, $bb = $r -split "-"; $a = [int]$a; $bb = [int]$bb
$parts = @()
foreach ($f in $frames) { $parts += (($f.b[$a..$bb] | ForEach-Object { $_.ToString("x2") }) -join "") }
Write-Host (" 0x{0:x2}-0x{1:x2} ({2}B): {3}" -f $a, $bb, ($bb-$a+1), ($parts -join " | "))
}
}
Diff-Frames $chals "CHALLENGES"
Diff-Frames $resps "RESPONSES"

View File

@ -0,0 +1,74 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\ss123.tsv")
# 收集 cmd=24 查询 (0x20==0200, flag==0) 与 其后 100ms 内的脉冲 UID
$queries = New-Object System.Collections.ArrayList
$pulses = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x30) { continue }
$t = [double]$p[0]
if ($p[1] -eq "192.168.123.113" -and $p[2] -eq "192.168.123.1" -and [int]$b[4] -eq 4 -and $b.Length -gt 0x52 -and $b.Length -lt 2000 -and $b[0x20] -eq 2 -and $b[0x21] -eq 0 -and $b[0x30] -eq 0 -and $b[0x3C] -eq 1 -and $b[0x3D] -eq 0x24) {
[void]$queries.Add(@{ t=$t; b=$b })
}
if ($p[1] -eq "192.168.123.1" -and $p[2] -eq "192.168.123.113" -and $b.Length -lt 200 -and $b[0x2A] -eq 0xAA) {
$uid = ""
for ($i = 0; $i -lt 6; $i++) {
$b1 = $b[0x2B + 2*$i]; $b2 = $b[0x2C + 2*$i]
$byte = ([int]$b1 -band 0x55) -bor ([int]$b2 -band 0xAA)
$uid += $byte.ToString("x2")
}
[void]$pulses.Add(@{ t=$t; uid=$uid })
}
}
Write-Host ("cmd=24 查询={0} 脉冲={1}" -f $queries.Count, $pulses.Count)
# cmd=24 查询布局: 打印 0x3C..0x60 区域的不同取值分布
Write-Host "`n--- cmd=24 查询 0x3E..0x55 字段分布 (前 8 条不同样本) ---"
$seen = @{}
$shown = 0
foreach ($q in $queries) {
$key = ($q.b[0x3E..0x55] | ForEach-Object { $_.ToString("x2") }) -join ""
if ($seen.ContainsKey($key)) { continue }
$seen[$key] = 1
$b = $q.b
Write-Host (" t={0,7:F2} 0x3E={1} 0x44={2} 0x4A={3} 0x50={4}" -f $q.t,
(($b[0x3E..0x43] | ForEach-Object { $_.ToString("x2") }) -join ""),
(($b[0x44..0x49] | ForEach-Object { $_.ToString("x2") }) -join ""),
(($b[0x4A..0x4F] | ForEach-Object { $_.ToString("x2") }) -join ""),
(($b[0x50..0x55] | ForEach-Object { $_.ToString("x2") }) -join ""))
$shown++; if ($shown -ge 12) { break }
}
# 关键验证: 每个查询的分支范围 [lo,hi] 与 其后 100ms 内应答脉冲 UID 的关系
# 假设: 0x3E-0x43=下界, 0x44-0x49=上界 (大端 UID 序)
function UidNum($b, $o) {
$v = [double]0
for ($i = 0; $i -lt 6; $i++) { $v = $v * 256 + [int]$b[$o + $i] }
return $v
}
Write-Host "`n--- 分支范围 vs 应答脉冲 (前 25 条有应答/无应答对比) ---"
$fx = @("010734670125", "010734880900", "0899500026c9")
$fxNum = @{}
foreach ($f in $fx) { $n = [double]0; for ($i = 0; $i -lt 6; $i++) { $n = $n * 256 + [Convert]::ToInt32($f.Substring(2*$i, 2), 16) }; $fxNum[$f] = $n }
$n2 = 0
foreach ($q in $queries) {
$lo = UidNum $q.b 0x3E; $hi = UidNum $q.b 0x44
$ans = @($pulses | Where-Object { $_.t -ge $q.t -and $_.t -lt $q.t + 0.1 })
$ansStr = ($ans | ForEach-Object { $_.uid.Substring(8) }) -join ","
$inRange = @()
foreach ($f in $fx) { if ($fxNum[$f] -ge $lo -and $fxNum[$f] -le $hi) { $inRange += $f.Substring(8) } }
Write-Host (" t={0,7:F2} lo={1} hi={2} 范围内灯具=[{3}] 应答=[{4}]" -f $q.t,
(($q.b[0x3E..0x43] | ForEach-Object { $_.ToString("x2") }) -join "").Substring(4),
(($q.b[0x44..0x49] | ForEach-Object { $_.ToString("x2") }) -join "").Substring(4),
($inRange -join ","), $ansStr)
$n2++; if ($n2 -ge 25) { break }
}

View File

@ -0,0 +1,74 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
function UidNumArr($arr) { $v = [double]0; foreach ($x in $arr) { $v = $v * 256 + [int]$x }; return $v }
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\ss123.tsv")
$queries = New-Object System.Collections.ArrayList
$pulses = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x30) { continue }
$t = [double]$p[0]
if ($p[1] -eq "192.168.123.113" -and $p[2] -eq "192.168.123.1" -and [int]$b[4] -eq 4 -and $b.Length -gt 0x5E -and $b.Length -lt 2000 -and $b[0x20] -eq 2 -and $b[0x21] -eq 0 -and $b[0x30] -eq 0 -and $b[0x3C] -eq 1 -and $b[0x3D] -eq 0x24) {
[void]$queries.Add(@{ t=$t; lo=(UidNumArr $b[0x53..0x58]); hi=(UidNumArr $b[0x59..0x5E]); loS=(($b[0x53..0x58] | ForEach-Object { $_.ToString("x2") }) -join ""); hiS=(($b[0x59..0x5E] | ForEach-Object { $_.ToString("x2") }) -join "") })
}
if ($p[1] -eq "192.168.123.1" -and $p[2] -eq "192.168.123.113" -and $b.Length -lt 200 -and $b[0x2A] -eq 0xAA) {
$uidBytes = @()
for ($i = 0; $i -lt 6; $i++) {
$b1 = $b[0x2B + 2*$i]; $b2 = $b[0x2C + 2*$i]
$uidBytes += ([int]$b1 -band 0x55) -bor ([int]$b2 -band 0xAA)
}
[void]$pulses.Add(@{ t=$t; uid=(UidNumArr $uidBytes); uidS=(($uidBytes | ForEach-Object { $_.ToString("x2") }) -join "") })
}
}
Write-Host ("cmd=24 查询={0} 脉冲={1}" -f $queries.Count, $pulses.Count)
# 分支范围分布
Write-Host "`n--- 分支范围 lo-hi 分布 (Top 15) ---"
$ranges = @{}
foreach ($q in $queries) { $k = "$($q.loS)-$($q.hiS)"; if (-not $ranges.ContainsKey($k)) { $ranges[$k] = 0 }; $ranges[$k]++ }
$ranges.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 15 | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
# 核心验证: 范围内含灯具的查询 → 应答率; 范围外 → 应答率
$fx = @{}
foreach ($f in @("010734670125", "010734880900", "0899500026c9")) {
$n = [double]0; for ($i = 0; $i -lt 6; $i++) { $n = $n * 256 + [Convert]::ToInt32($f.Substring(2*$i, 2), 16) }
$fx[$f] = $n
}
$inN = 0; $inAns = 0; $outN = 0; $outAns = 0; $mismatches = 0
foreach ($q in $queries) {
$ans = @($pulses | Where-Object { $_.t -ge $q.t -and $_.t -lt $q.t + 0.1 })
$hasFx = $false; foreach ($f in $fx.Values) { if ($f -ge $q.lo -and $f -le $q.hi) { $hasFx = $true; break } }
if ($hasFx) {
$inN++
if ($ans.Count -gt 0) {
$inAns++
# 应答的 UID 是否都在范围内?
foreach ($a in $ans) { if ($a.uid -lt $q.lo -or $a.uid -gt $q.hi) { $mismatches++ } }
}
} else {
$outN++
if ($ans.Count -gt 0) { $outAns++ }
}
}
Write-Host "`n--- 分支语义验证 ---"
Write-Host (" 范围内有灯具的查询: {0}, 有应答: {1} ({2:F0}%)" -f $inN, $inAns, $(if ($inN) {100.0*$inAns/$inN} else {0}))
Write-Host (" 范围内无灯具的查询: {0}, 有应答: {1} ({2:F0}%)" -f $outN, $outAns, $(if ($outN) {100.0*$outAns/$outN} else {0}))
Write-Host (" 应答 UID 超出查询范围的次数: {0}" -f $mismatches)
# 有应答查询的时间序列样本
Write-Host "`n--- 样本: 分支范围 vs 应答 UID (前 15 条有应答) ---"
$shown = 0
foreach ($q in $queries) {
$ans = @($pulses | Where-Object { $_.t -ge $q.t -and $_.t -lt $q.t + 0.1 })
if ($ans.Count -eq 0) { continue }
Write-Host (" t={0,7:F2} [{1}..{2}] → {3}" -f $q.t, $q.loS, $q.hiS, (($ans | ForEach-Object { $_.uidS }) -join ","))
$shown++; if ($shown -ge 15) { break }
}

View File

@ -0,0 +1,81 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
$f = "$env:TEMP\opencode\cold2.tsv"
$lines = [IO.File]::ReadAllLines($f)
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
function Hex($b) { return ($b | ForEach-Object { $_.ToString("x2") }) -join "" }
function UidStr($b, $o) { if ($b.Length -lt $o+6) { return "?" }; return ($b[$o..($o+5)] | ForEach-Object { $_.ToString("x2") }) -join "" }
# 采集 70B RDM 应答
$resp70 = @()
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.1" -or $p[2] -ne "192.168.123.113") { continue }
$b = H2B $p[4]
if ($b.Length -eq 70 -and $b[0x2A] -eq 0xCC) { $resp70 += ,$b }
}
Write-Host ("70B RDM 应答数: {0}" -f $resp70.Count)
# 全字段偏移扫描: sum(b[o..66])==ck16 且唯一解
Write-Host "`n--- 唯一偏移扫描: sum(b[o..66]) == ck@68-69 (BE) ---"
foreach ($o in 0x2A..0x34) {
$matchAll = $true
foreach ($b in $resp70) {
$sum = 0; for ($i = $o; $i -le 66; $i++) { $sum += $b[$i] }
$ck = ($b[67] -shl 8) -bor $b[68]
# 注意: 上面偏移占位, 实际 ck 在最后两字节 68-69
$ck = ($b[68] -shl 8) -bor $b[69]
if (($sum -band 0xFFFF) -ne $ck) { $matchAll = $false; break }
}
Write-Host (" start=0x{0:x2} end=66 ck@68-69 : {1}" -f $o, $(if ($matchAll) {"全部匹配"} else {"否"}))
}
# 结合 msgLen: 若 start=0x2E, 覆盖字节数 = 66-0x2E+1 = 0x27? 试 sum(b[0x2A..0x2A+msgLen+2]) (msgLen 不含 CC01)
Write-Host "`n--- 假设: msgLen@0x2C 不含 CC01 两字节, 求和范围 = CC01 + msgLen 字节 ---"
$ok = 0; $bad = 0
foreach ($b in $resp70) {
$msgLen = $b[0x2C]
$sum = 0; for ($i = 0x2A; $i -lt 0x2A + 2 + $msgLen; $i++) { $sum += $b[$i] }
$ck = ($b[68] -shl 8) -bor $b[69]
if (($sum -band 0xFFFF) -eq $ck) { $ok++ } else { $bad++ }
}
Write-Host (" OK={0} BAD={1}" -f $ok, $bad)
# 展示两个 70B 样本的完整 hex + 各字段
Write-Host "`n--- 70B 样本字段 ---"
foreach ($b in ($resp70 | Select-Object -First 2)) {
$msgLen = $b[0x2C]
Write-Host (" frame: " + (Hex $b))
Write-Host (" 0x28 RDMlen={0} 0x2C msgLen={1} dest={2} src={3} TN={4:x2} RT={5:x2} CC={6:x2} PID={7:x2}{8:x2} PD={9} ck={10:x2}{11:x2}" -f `
($b[0x28] -bor ($b[0x29] -shl 8)), $msgLen, (UidStr $b 0x2D), (UidStr $b 0x33), $b[0x39], $b[0x3A], $b[0x3E], $b[0x3F], $b[0x40], (Hex $b[0x42..0x43]), $b[0x44], $b[0x45])
# 试 sum(0x2E..0x2E+msgLen-1)
$s1 = 0; for ($i = 0x2E; $i -lt 0x2E + $msgLen; $i++) { $s1 += $b[$i] }
Write-Host (" sum(0x2E..0x2E+msgLen-1)=0x{0:x4} (end=0x{1:x2})" -f ($s1 -band 0xFFFF), (0x2E + $msgLen - 1))
$s2 = 0; for ($i = 0x2A; $i -lt 0x2A + 2 + $msgLen; $i++) { $s2 += $b[$i] }
Write-Host (" sum(0x2A..0x2A+msgLen+1)=0x{0:x4} (end=0x{1:x2})" -f ($s2 -band 0xFFFF), (0x2A + 2 + $msgLen - 1))
}
# 同样验证一个 87B (DEVICE_INFO) 应答
Write-Host "`n--- 87B DEVICE_INFO 应答验证 ---"
$cnt = 0
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.1" -or $p[2] -ne "192.168.123.113") { continue }
$b = H2B $p[4]
if ($b.Length -eq 87 -and $b[0x2A] -eq 0xCC) {
$msgLen = $b[0x2C]
$ckOff = $b.Length - 2
$ck = ($b[$ckOff] -shl 8) -bor $b[$ckOff+1]
$s1 = 0; for ($i = 0x2E; $i -lt 0x2E + $msgLen; $i++) { $s1 += $b[$i] }
$s2 = 0; for ($i = 0x2A; $i -lt 0x2A + 2 + $msgLen; $i++) { $s2 += $b[$i] }
Write-Host (" msgLen={0} ck=0x{1:x4} sum_noCC01=0x{2:x4}({3}) sum_withCC01=0x{4:x4}({5})" -f $msgLen, $ck, ($s1 -band 0xFFFF), $(if (($s1 -band 0xFFFF) -eq $ck) {"OK"} else {"x"}), ($s2 -band 0xFFFF), $(if (($s2 -band 0xFFFF) -eq $ck) {"OK"} else {"x"}))
$cnt++; if ($cnt -ge 5) { break }
}
}

View File

@ -0,0 +1,56 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$f = "$env:TEMP\opencode\cold2.tsv"
$lines = [IO.File]::ReadAllLines($f)
function H2B($hex) { $n = $hex.Length / 2; $b = New-Object byte[] $n; for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }; return $b }
function Hex($b) { return ($b | ForEach-Object { $_.ToString("x2") }) -join "" }
# (a) 38.087 的 102B type=2 帧
Write-Host "=== (a) 首个 type=2 102B 帧 (claim 前) ==="
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5) { continue }
if ($p[1] -ne "192.168.123.113" -or $p[2] -ne "192.168.123.1") { continue }
$b = H2B $p[4]
if ($b.Length -eq 102 -and $b[4] -eq 2) {
Write-Host (" t={0} len={1}" -f $p[0], $b.Length)
Write-Host (" hex=" + (Hex $b))
break
}
}
# (b) 首个 644B announce: 找主机IP(717ba8c0)偏移 + 其它可变字段
Write-Host "`n=== (b) 首个 644B type=1 announce ==="
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5) { continue }
if ($p[1] -ne "192.168.123.113") { continue }
$b = H2B $p[4]
if ($b.Length -eq 644) {
Write-Host (" t={0} dst={1}" -f $p[0], $p[2])
$ip = [byte[]]@(0x71,0x7b,0xa8,0xc0) # 113.123.168.192 = LE of 192.168.123.113
for ($i = 0; $i -lt $b.Length - 4; $i++) {
if ($b[$i] -eq 0x71 -and $b[$i+1] -eq 0x7b -and $b[$i+2] -eq 0xa8 -and $b[$i+3] -eq 0xc0) {
Write-Host (" 主机IP@0x{0:x}" -f $i)
}
}
Write-Host (" 0x00-0x40: " + (Hex $b[0..0x3F]))
break
}
}
# (c) rdm_disc_req 模板 0x18-0x19 / 0x2A-0x3B
Write-Host "`n=== (c) rdm_disc_req 模板关键字段 ==="
$h = (Get-Content "C:\Users\tonycao\work\light\enet-validator\samples\rdm_disc_req_1126.hex" -Raw).Trim() -replace '\s',''
$b = H2B $h
Write-Host (" len={0} 0x18-0x19={1} 0x20-0x23={2} 0x2A-0x3B={3}" -f $b.Length, (Hex $b[0x18..0x19]), (Hex $b[0x20..0x23]), (Hex $b[0x2A..0x3B]))
# (d) 首个 .1→113 的 RDM 应答帧: 0x18 sub + 0x2A 起内容
Write-Host "`n=== (d) 首个 .1→113 RDM 应答 (sub@0x18, 0x2A 起 32B) ==="
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5) { continue }
if ($p[1] -ne "192.168.123.1") { continue }
$b = H2B $p[4]
if ($b.Length -ge 0x30 -and $b[4] -eq 4 -and ($b[0x2A] -eq 0xCC -or $b[0x2A] -eq 0xAA)) {
Write-Host (" t={0} len={1} 0x18-0x19={2} 0x20-0x21={3} 0x28-0x29={4}" -f $p[0], $b.Length, (Hex $b[0x18..0x19]), (Hex $b[0x20..0x21]), (Hex $b[0x28..0x29]))
Write-Host (" 0x2A+: " + (Hex $b[0x2A..([Math]::Min(0x4A, $b.Length-1))]))
break
}
}

View File

@ -0,0 +1,240 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
$f = "$env:TEMP\opencode\cold2.tsv"
$lines = [IO.File]::ReadAllLines($f)
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
function G16($b, $o) { if ($b.Length -lt $o+2) { return -1 }; return $b[$o] -bor ($b[$o+1] -shl 8) }
function G32($b, $o) { if ($b.Length -lt $o+4) { return -1 }; return $b[$o] -bor ($b[$o+1] -shl 8) -bor ($b[$o+2] -shl 16) -bor ($b[$o+3] -shl 24) }
function UidStr($b, $o) { if ($b.Length -lt $o+6) { return "?" }; return ($b[$o..($o+5)] | ForEach-Object { $_.ToString("x2") }) -join "" }
# ============ 1) 冷启动时间线: 前 30 秒关键帧 ============
Write-Host "`n=========== [1] 冷启动前 30s 关键帧时间线 ==========="
$shown = 0
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$t = [double]$p[0]; if ($t -gt 30) { break }
$b = H2B $p[4]; if ($b.Length -lt 0x1A) { continue }
if ($b[0] -ne 0x45 -or $b[1] -ne 0x4E) { continue } # "EN"
$type = G32 $b 4; $dir = "$($p[1])->$($p[2])"; $len = $b.Length
$desc = $null
if ($type -eq 1) { $desc = "发现请求" }
elseif ($type -eq 2) { $sub = G16 $b 0x18
if ($sub -eq 1) { $desc = "宣告sub=01" }
elseif ($sub -eq 2) { $desc = "注册sub=02(89B)" }
elseif ($sub -eq 3) { $desc = "心跳sub=03" }
elseif ($sub -eq 5) { $desc = "AECH质询sub=05" }
elseif ($sub -eq 6) { $desc = "AERE应答sub=06" }
else { $desc = "type2 sub=$sub" } }
elseif ($type -eq 4 -and $len -eq 259) { $desc = "259发现应答" }
elseif ($type -eq 4 -and $len -eq 84) { $desc = "84B状态" }
elseif ($type -eq 4 -and $len -gt 1000) { $desc = "DMX/RDM长帧" }
elseif ($type -eq 4) { $desc = "type4短帧" }
if ($desc -and $desc -ne "84B状态" -and $desc -ne "心跳sub=03" -and $shown -lt 60) {
Write-Host (" t={0,8:F3} {1,-26} len={2,-5} {3}" -f $t, $dir, $len, $desc); $shown++
}
}
# ============ 2) RDM 查询直方图 (主机->.1, type=4) ============
Write-Host "`n=========== [2] 主机 RDM 查询 cmd 直方图 (type=4, 0x3D) ==========="
$cmdHist = @{}; $pidHist = @{}; $queries = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.113" -or $p[2] -ne "192.168.123.1") { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x52) { continue }
if ((G32 $b 4) -ne 4) { continue }
if ($b.Length -gt 2000) { continue } # 纯DMX 1126B? 实际 1084=DMX
$t = [double]$p[0]
$len = $b.Length
if ($len -eq 1084 -or $len -eq 1126) { # DMX 帧或内嵌 RDM 副本
if ($b[0x3C] -ne 0x01) { continue }
}
$cmd = $b[0x3D]
if ($cmd -ne 0x24 -and $cmd -ne 0x18 -and $cmd -ne 0x19 -and $cmd -ne 0x1a) { continue }
$flag = $b[0x30]
$key = ("cmd=0x{0:x2} flag={1:x2}" -f $cmd, $flag)
if (-not $cmdHist.ContainsKey($key)) { $cmdHist[$key] = 0 }; $cmdHist[$key]++
$uid = UidStr $b 0x3E
$pid2 = ""
if ($cmd -eq 0x18 -and $b.Length -gt 0x51) { $pid2 = "{0:x2}{1:x2}" -f $b[0x50], $b[0x51]
$pk = "$pid2 flag=$("{0:x2}" -f $flag)"; if (-not $pidHist.ContainsKey($pk)) { $pidHist[$pk] = 0 }; $pidHist[$pk]++ }
[void]$queries.Add(@{ t=$t; cmd=$cmd; flag=$flag; uid=$uid; pid=$pid2; seq=((G16 $b 0x22)); seq16lo=(G16 $b 0x22) })
}
Write-Host "--- cmd 分布 ---"
$cmdHist.GetEnumerator() | Sort-Object Value -Descending | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
Write-Host "--- cmd=18 的 PID 分布 ---"
$pidHist.GetEnumerator() | Sort-Object Value -Descending | ForEach-Object { Write-Host (" PID={0} : {1}" -f $_.Key, $_.Value) }
# ============ 3) .1 应答帧: 长度/内容直方图 + checksum 验证 ============
Write-Host "`n=========== [3] .1 应答帧分析 (.1->113, type=4 短帧) ==========="
$answers = New-Object System.Collections.ArrayList
$ansLenHist = @{}
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.1" -or $p[2] -ne "192.168.123.113") { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x2E) { continue }
if ((G32 $b 4) -ne 4) { continue }
if ($b.Length -gt 200) { continue } # 只看 RDM 短应答 (59/69/77/87/101/111/119B)
if ($b[0x2A] -ne 0xCC -and $b[0x2A] -ne 0xAA) { continue }
$t = [double]$p[0]
$len = $b.Length
if (-not $ansLenHist.ContainsKey($len)) { $ansLenHist[$len] = 0 }; $ansLenHist[$len]++
[void]$answers.Add(@{ t=$t; b=$b; len=$len })
}
Write-Host "--- 应答长度分布 ---"
$ansLenHist.GetEnumerator() | Sort-Object Name | ForEach-Object { Write-Host (" {0}B : {1}" -f $_.Key, $_.Value) }
# checksum 验证 (标准 RDM 帧: 0x2A=0xCC, msgLen@0x2C, checksum=前 msgLen 字节和, BE)
Write-Host "--- 标准 RDM 应答 checksum 验证 (抽样全部) ---"
$okC = 0; $badC = 0; $badSamples = New-Object System.Collections.ArrayList
foreach ($a in $answers) {
$b = $a.b
if ($b[0x2A] -ne 0xCC) { continue }
$msgLen = $b[0x2C]
if ($b.Length -lt 0x2A + $msgLen + 2) { continue }
$sum = 0; for ($i = 0x2A; $i -lt 0x2A + $msgLen; $i++) { $sum += $b[$i] }
$ck = ($b[0x2A + $msgLen] -shl 8) -bor $b[0x2A + $msgLen + 1]
if (($sum -band 0xFFFF) -eq $ck) { $okC++ } else { $badC++; if ($badSamples.Count -lt 3) { [void]$badSamples.Add(@{t=$a.t; len=$a.len; sum=($sum -band 0xFFFF); ck=$ck}) } }
}
Write-Host (" checksum OK={0} BAD={1}" -f $okC, $badC)
foreach ($s in $badSamples) { Write-Host (" BAD样本: t={0:F3} len={1} sum=0x{2:x4} ck=0x{3:x4}" -f $s.t, $s.len, $s.sum, $s.ck) }
# 脉冲帧 (0x2A=0xAA) 内容: 解码 UID
Write-Host "--- DISC 脉冲帧 (0x2A=0xAA) UID 解码 ---"
$pulseUids = @{}
foreach ($a in $answers) {
$b = $a.b
if ($b[0x2A] -ne 0xAA) { continue }
# 定位 0xAA 分隔符后的 16 字节编码 UID
$uid = ""
for ($i = 0; $i -lt 6; $i++) {
$b1 = $b[0x2B + 2*$i]; $b2 = $b[0x2C + 2*$i]
$byte = ($b1 -band 0x55) -bor ($b2 -band 0xAA)
$uid += $byte.ToString("x2")
}
if (-not $pulseUids.ContainsKey($uid)) { $pulseUids[$uid] = 0 }; $pulseUids[$uid]++
}
$pulseUids.GetEnumerator() | Sort-Object Value -Descending | ForEach-Object { Write-Host (" UID={0} : {1} 脉冲" -f $_.Key, $_.Value) }
# ============ 4) 查询-应答配对: cmd=18 按 PID, 时延统计 ============
Write-Host "`n=========== [4] cmd=18 查询→应答配对 (seq 0x22-0x23 ↔ 0x20-0x21) ==========="
$ansBySeq = @{}
foreach ($a in $answers) {
$b = $a.b
if ($b.Length -lt 0x22) { continue }
$s = G16 $b 0x20
if (-not $ansBySeq.ContainsKey($s)) { $ansBySeq[$s] = New-Object System.Collections.ArrayList }
[void]$ansBySeq[$s].Add($a)
}
$pairStats = @{}; $latByPid = @{}
foreach ($q in $queries) {
if ($q.cmd -ne 0x18 -or $q.flag -ne 0) { continue }
$pk = if ($q.pid) { $q.pid } else { "????" }
if (-not $pairStats.ContainsKey($pk)) { $pairStats[$pk] = @{ n=0; ans=0; lens=@{} } }
$pairStats[$pk].n++
# 找该查询之后 200ms 内 seq 回显的应答
$best = $null
foreach ($a in $answers) {
if ($a.t -lt $q.t -or $a.t -gt $q.t + 0.2) { continue }
$s = G16 $a.b 0x20
if ($s -eq $q.seq) { $best = $a; break }
}
if ($best) {
$pairStats[$pk].ans++
$al = $best.len
if (-not $pairStats[$pk].lens.ContainsKey($al)) { $pairStats[$pk].lens[$al] = 0 }
$pairStats[$pk].lens[$al]++
if (-not $latByPid.ContainsKey($pk)) { $latByPid[$pk] = New-Object System.Collections.ArrayList }
[void]$latByPid[$pk].Add([int](($best.t - $q.t) * 1000))
}
}
foreach ($k in ($pairStats.Keys | Sort-Object)) {
$st = $pairStats[$k]
$lensStr = ($st.lens.GetEnumerator() | ForEach-Object { "$($_.Key)B×$($_.Value)" }) -join " "
$latStr = ""
if ($latByPid.ContainsKey($k) -and $latByPid[$k].Count -gt 0) {
$la = $latByPid[$k] | Sort-Object
$latStr = (" 时延ms: min={0} 中位={1} max={2}" -f $la[0], $la[[int]($la.Count/2)], $la[-1])
}
Write-Host (" PID={0} 查询={1} 配对应答={2} 长度[{3}]{4}" -f $k, $st.n, $st.ans, $lensStr, $latStr)
}
# ============ 5) 0002/0003 应答逐字节样本 ============
Write-Host "`n=========== [5] PID=0002 / 0003 应答样本 (前2个) ==========="
foreach ($wantPid in @("0002", "0003")) {
$cnt = 0
foreach ($q in $queries) {
if ($q.cmd -ne 0x18 -or $q.flag -ne 0 -or $q.pid -ne $wantPid) { continue }
foreach ($a in $answers) {
if ($a.t -lt $q.t -or $a.t -gt $q.t + 0.2) { continue }
if ((G16 $a.b 0x20) -eq $q.seq) {
Write-Host (" PID={0} UID={1} t={2:F3} 查询len -> 应答{3}B: {4}" -f $wantPid, $q.uid, $q.t, $a.len, (($a.b | ForEach-Object { $_.ToString("x2") }) -join ""))
$cnt++; break
}
}
if ($cnt -ge 2) { break }
}
if ($cnt -eq 0) { Write-Host " PID=$wantPid 无配对样本" }
}
# ============ 6) 发现收敛: 全范围查询(24) 与脉冲随时间 ============
Write-Host "`n=========== [6] 发现收敛时间线 (10s 桶: 全范围查询数 / 脉冲应答数 / MUTE数) ==========="
$buckets = @{}
foreach ($q in $queries) {
if ($q.flag -ne 0) { continue }
$bk = [int]($q.t / 10) * 10
if (-not $buckets.ContainsKey($bk)) { $buckets[$bk] = @{ disc=0; get=0; pulse=0; mute=0; unmute=0 } }
if ($q.cmd -eq 0x24) { $buckets[$bk].disc++ }
if ($q.cmd -eq 0x18 -and $q.pid -eq "0003") { $buckets[$bk].mute++ }
if ($q.cmd -eq 0x18 -and $q.pid -eq "0002") { $buckets[$bk].unmute++ }
if ($q.cmd -eq 0x18) { $buckets[$bk].get++ }
}
# 脉冲数按桶
foreach ($a in $answers) {
if ($a.b[0x2A] -ne 0xAA) { continue }
$bk = [int]($a.t / 10) * 10
if (-not $buckets.ContainsKey($bk)) { $buckets[$bk] = @{ disc=0; get=0; pulse=0; mute=0; unmute=0 } }
$buckets[$bk].pulse++
}
foreach ($bk in ($buckets.Keys | Sort-Object)) {
$v = $buckets[$bk]
Write-Host (" t={0,4}-{1,-4}s 全范围查询={2,-5} GET查询={3,-5} 脉冲={4,-5} MUTE={5,-4} UNMUTE={6}" -f $bk, ($bk+10), $v.disc, $v.get, $v.pulse, $v.mute, $v.unmute)
}
# ============ 7) 84B 状态帧节奏 ============
Write-Host "`n=========== [7] 84B 状态帧节奏 (.1->113) ==========="
$statTimes = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.1" -or $p[2] -ne "192.168.123.113") { continue }
$b = H2B $p[4]
if ($b.Length -eq 84 -and (G32 $b 4) -eq 4) { [void]$statTimes.Add([double]$p[0]) }
}
Write-Host (" 84B 总数={0}" -f $statTimes.Count)
if ($statTimes.Count -gt 2) {
$gaps = New-Object System.Collections.ArrayList
for ($i = 1; $i -lt $statTimes.Count; $i++) { [void]$gaps.Add([int](($statTimes[$i] - $statTimes[$i-1]) * 1000)) }
$gs = $gaps | Sort-Object
Write-Host (" 帧间隔ms: min={0} 中位={1} max={2}" -f $gs[0], $gs[[int]($gs.Count/2)], $gs[-1])
# 突发检测: 间隔>1000ms 为新突发
$bursts = 1; foreach ($g in $gaps) { if ($g -gt 1000) { $bursts++ } }
Write-Host (" 突发数={0} (平均每突发 {1:F1} 帧)" -f $bursts, ($statTimes.Count / $bursts))
}
# ============ 8) DEVICE_INFO(0060) 读取对象 UID ============
Write-Host "`n=========== [8] DEVICE_INFO(0060)/参数读取的 UID 分布 ==========="
$diUids = @{}
foreach ($q in $queries) {
if ($q.cmd -ne 0x18 -or $q.flag -ne 0) { continue }
if ($q.pid -eq "0060" -or $q.pid -eq "0080" -or $q.pid -eq "00f0" -or $q.pid -eq "0003") {
$k = "$($q.pid)->$($q.uid)"; if (-not $diUids.ContainsKey($k)) { $diUids[$k] = 0 }; $diUids[$k]++
}
}
$diUids.GetEnumerator() | Sort-Object Name | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
Write-Host "`n分析完成。"

View File

@ -0,0 +1,142 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
$f = "$env:TEMP\opencode\cold2.tsv"
$lines = [IO.File]::ReadAllLines($f)
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
function G16le($b, $o) { if ($b.Length -lt $o+2) { return -1 }; return $b[$o] -bor ($b[$o+1] -shl 8) }
function G32le($b, $o) { if ($b.Length -lt $o+4) { return -1 }; return $b[$o] -bor ($b[$o+1] -shl 8) -bor ($b[$o+2] -shl 16) -bor ($b[$o+3] -shl 24) }
function Hex($b) { return ($b | ForEach-Object { $_.ToString("x2") }) -join "" }
function UidStr($b, $o) { if ($b.Length -lt $o+6) { return "?" }; return ($b[$o..($o+5)] | ForEach-Object { $_.ToString("x2") }) -join "" }
# checksum 测试: 先用已知 0002 样本验证公式
Write-Host "=========== [0] checksum 公式自验 (0002 样本) ==========="
$sample = H2B "454e65740400000000001c7c00000000000001004600000002004275746c0000a5000000000000001c00cc011a453a7fffffff0899500026c952000000001100020200000729"
$msgLen = $sample[0x2C]
$sum = 0; for ($i = 0x2A; $i -lt 0x2A + $msgLen; $i++) { $sum += $sample[$i] }
$ckOff = 0x2A + $msgLen
Write-Host (" msgLen={0} sum=0x{1:x4} field=0x{2:x4} -> {3}" -f $msgLen, ($sum -band 0xFFFF), (($sample[$ckOff] -shl 8) -bor $sample[$ckOff+1]), $(if (($sum -band 0xFFFF) -eq (($sample[$ckOff] -shl 8) -bor $sample[$ckOff+1])) {"OK"} else {"BAD"}))
# ============ A) .1->113 全帧型直方图 (type x len) ============
Write-Host "`n=========== [A] .1→113 全帧型 (type×len) 直方图 ==========="
$h = @{}
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.1") { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x1A) { continue }
$t = G32le $b 4
$k = "type=$t len=$($b.Length)"
if (-not $h.ContainsKey($k)) { $h[$k] = 0 }; $h[$k]++
}
$h.GetEnumerator() | Sort-Object { [int]($_.Value) } -Descending | Select-Object -First 25 | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
# ============ B) type=2 帧时间线 (注册/AECH/AERE/心跳) ============
Write-Host "`n=========== [B] type=2 帧时间线 (全程, 前 40 条) ==========="
$shown = 0
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x1A) { continue }
if ((G32le $b 4) -ne 2) { continue }
$sub = G16le $b 0x18
$t = [double]$p[0]
Write-Host (" t={0,8:F3} {1,-26} len={2,-4} sub={3}" -f $t, "$($p[1])->$($p[2])", $b.Length, $sub)
$shown++; if ($shown -ge 40) { break }
}
# ============ C) 发现请求/应答: 180B 请求 vs 217B 应答 全文 ============
Write-Host "`n=========== [C] 180B 发现请求 与 217B 应答 样本 ==========="
$req180 = $null; $resp217 = $null
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]
if (-not $req180 -and $p[1] -eq "192.168.123.113" -and $b.Length -eq 180) { $req180 = $b }
if (-not $resp217 -and $p[1] -eq "192.168.123.1" -and $b.Length -eq 217) { $resp217 = $b }
if ($req180 -and $resp217) { break }
}
if ($req180) { Write-Host (" 请求180B: " + (Hex $req180)) }
if ($resp217) { Write-Host (" 应答217B: " + (Hex $resp217)) }
# ============ D) cmd=19 / cmd=1a 查询与应答样本 ============
Write-Host "`n=========== [D] cmd=19 / cmd=1a 样本 (各3条查询+应答) ==========="
foreach ($wantCmd in @(0x19, 0x1a)) {
$cnt = 0
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.113" -or $p[2] -ne "192.168.123.1") { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x52 -or $b.Length -gt 2000) { continue }
if ((G32le $b 4) -ne 4 -or $b[0x3D] -ne $wantCmd -or $b[0x30] -ne 0) { continue }
$t = [double]$p[0]
Write-Host (" cmd={0:x2} t={1:F3} UID={2} 查询({3}B): {4}" -f $wantCmd, $t, (UidStr $b 0x3E), $b.Length, (Hex ($b[0x20..0x2F] + $b[0x3C..([Math]::Min($b.Length-1, 0x58))])))
$cnt++; if ($cnt -ge 3) { break }
}
}
# ============ E) flag=01 PID=0003 帧样本 (780条是什么) ============
Write-Host "`n=========== [E] flag=01 PID=0003 帧样本 (前3条, 含UID) ==========="
$cnt = 0
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.113" -or $p[2] -ne "192.168.123.1") { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x52 -or $b.Length -gt 2000) { continue }
if ((G32le $b 4) -ne 4 -or $b[0x3D] -ne 0x18 -or $b[0x30] -ne 1) { continue }
if ($b[0x50] -ne 0 -or $b[0x51] -ne 3) { continue }
$t = [double]$p[0]
Write-Host (" t={0:F3} len={1} flag01 UID={2} hdr: {3}" -f $t, $b.Length, (UidStr $b 0x3E), (Hex $b[0x20..0x33]))
$cnt++; if ($cnt -ge 3) { break }
}
# ============ F) 脉冲时延: cmd=24 查询 → 脉冲 ============
Write-Host "`n=========== [F] cmd=24(flag=00) 查询→脉冲 时延分布 ==========="
$discQ = New-Object System.Collections.ArrayList
$pulses = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x2E) { continue }
$t = [double]$p[0]
if ($p[1] -eq "192.168.123.113" -and $p[2] -eq "192.168.123.1" -and $b.Length -gt 0x44 -and $b.Length -lt 2000 -and (G32le $b 4) -eq 4 -and $b[0x3D] -eq 0x24 -and $b[0x30] -eq 0) {
[void]$discQ.Add(@{ t=$t; lo=($b[0x3E..0x43] | ForEach-Object { $_.ToString("x2") }) -join "" })
}
if ($p[1] -eq "192.168.123.1" -and $b.Length -lt 200 -and $b[0x2A] -eq 0xAA) {
[void]$pulses.Add(@{ t=$t })
}
}
Write-Host (" cmd24查询={0} 脉冲={1}" -f $discQ.Count, $pulses.Count)
$lats = New-Object System.Collections.ArrayList
foreach ($pu in $pulses) {
# 找之前最近的查询
$best = $null
for ($i = $discQ.Count - 1; $i -ge 0; $i--) {
if ($discQ[$i].t -le $pu.t) { $best = $discQ[$i]; break }
}
if ($best -and ($pu.t - $best.t) -lt 0.5) { [void]$lats.Add([int](($pu.t - $best.t) * 1000)) }
}
if ($lats.Count -gt 0) {
$ls = $lats | Sort-Object
Write-Host (" 脉冲时延ms: n={0} min={1} 中位={2} max={3}" -f $ls.Count, $ls[0], $ls[[int]($ls.Count/2)], $ls[-1])
}
# ============ G) cmd=24 查询负载: 分支范围 vs 全范围 ============
Write-Host "`n=========== [G] cmd=24 查询的搜索范围分布 (UID 下界@0x3E) ==========="
$ranges = @{}
foreach ($q in $discQ) {
if (-not $ranges.ContainsKey($q.lo)) { $ranges[$q.lo] = 0 }; $ranges[$q.lo]++
}
$ranges.GetEnumerator() | Sort-Object Value -Descending | Select-Object -First 10 | ForEach-Object { Write-Host (" 范围下界={0} : {1}" -f $_.Key, $_.Value) }
# ============ H) AECH/AERE 帧 (594B) 抓取 ============
Write-Host "`n=========== [H] AECH/AERE (594B type=2) 时间点 ==========="
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]; if ($b.Length -lt 500) { continue }
if ((G32le $b 4) -ne 2) { continue }
$sub = G16le $b 0x18
$t = [double]$p[0]
Write-Host (" t={0:F3} {1} len={2} sub={3} hasAECH={4} hasAERE={5}" -f $t, "$($p[1])->$($p[2])", $b.Length, $sub, ((Hex $b) -match "41454348"), ((Hex $b) -match "41455245"))
}
Write-Host "`n分析完成。"

View File

@ -0,0 +1,31 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$f = "$env:TEMP\opencode\cold2.tsv"
$lines = [IO.File]::ReadAllLines($f)
function H2B($hex) { $n = $hex.Length / 2; $b = New-Object byte[] $n; for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }; return $b }
function G32le($b, $o) { return $b[$o] -bor ($b[$o+1] -shl 8) -bor ($b[$o+2] -shl 16) -bor ($b[$o+3] -shl 24) }
Write-Host "=== cold2 前 120 帧 113→.1 (认领建立期) ==="
$count = 0
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5) { continue }
if ($p[1] -ne "192.168.123.113") { continue }
$b = H2B $p[4]
$t = [double]$p[0]
$len = $b.Length
$magic = [Text.Encoding]::ASCII.GetString($b[0..3])
$type = G32le $b 4
$desc = ""
if ($magic -eq "ENet" -and $type -eq 4 -and $len -ge 0x50) {
$sub = "{0:x2}{1:x2}" -f $b[0x19], $b[0x18]
$flag = $b[0x30]
if ($b[0x2A] -eq 0xCC -or $b[0x3C] -eq 0x01) {
$msgLen = $b[0x3D]; $cc = $b[0x4F]; $pid = ($b[0x50] -shl 8) -bor $b[0x51]
$desc = "RDM请求 sub=$sub flag=$('{0:x2}' -f $flag) msgLen=$msgLen CC=$('{0:x2}' -f $cc) PID=$('{0:x4}' -f $pid) TN=$('{0:x2}' -f $b[0x4A])"
} else { $desc = "type4 sub=$sub flag=$('{0:x2}' -f $flag) 0x2A=$('{0:x2}' -f $b[0x2A])" }
} elseif ($magic -eq "ENet" -and $type -eq 4) {
$desc = "type4 短帧"
}
Write-Host ("{0,9:N3} len={1,-5} magic={2} type={3} {4}" -f $t, $len, $magic, $type, $desc)
$count++
if ($count -ge 120) { break }
}

View File

@ -0,0 +1,70 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\ss123.tsv")
$dmx = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.113" -or $p[2] -ne "192.168.123.1") { continue }
$b = H2B $p[4]; if ($b.Length -ne 1084) { continue }
if ([int]$b[4] -ne 4) { continue }
if (-not ($b[0x20] -eq 1 -and $b[0x21] -eq 1)) { continue }
[void]$dmx.Add(@{ t=[double]$p[0]; b=$b })
}
Write-Host ("DMX-A 帧数: {0}" -f $dmx.Count)
if ($dmx.Count -eq 0) { exit }
# --- 1. 头部逐字节变化分析 (0x00-0x3B): 常量 or 变量 ---
Write-Host "`n=== 头部 0x00-0x3B 逐字节分析 ==="
$first = $dmx[0].b
for ($off = 0; $off -lt 0x3C; $off++) {
$min = 255; $max = 0; $diff = 0
foreach ($f in $dmx) { $v = [int]$f.b[$off]; if ($v -lt $min) { $min = $v }; if ($v -gt $max) { $max = $v }; if ($v -ne [int]$first[$off]) { $diff++ } }
if ($diff -gt 0) {
Write-Host (" 0x{0:x2}: 变量 首值={1:x2} min={2:x2} max={3:x2} 变化帧数={4}" -f $off, $first[$off], $min, $max, $diff)
}
}
Write-Host " (未列出 = 全部帧常量)"
Write-Host "`n=== 常量头 hex (首帧 0x00-0x3B) ==="
Write-Host (" " + (($first[0..0x3B] | ForEach-Object { $_.ToString("x2") }) -join " "))
# --- 2. 变化字段的语义: 找 16/32 位递增计数器 ---
Write-Host "`n=== 疑似序号字段 (按 LE u16/u32 检验单调性) ==="
foreach ($off in @(0x22, 0x24, 0x26, 0x28, 0x2A, 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A)) {
$vals = $dmx | ForEach-Object { [int]$_.b[$off] + [int]$_.b[$off+1]*256 }
$inc = 0; $dec = 0
for ($i = 1; $i -lt $vals.Count; $i++) { if ($vals[$i] -gt $vals[$i-1]) { $inc++ } elseif ($vals[$i] -lt $vals[$i-1]) { $dec++ } }
if ($inc -gt $vals.Count * 0.8 -or $dec -gt $vals.Count * 0.8) {
Write-Host (" u16@0x{0:x2}: 首={1} 尾={2} 增={3} 减={4}" -f $off, $vals[0], $vals[-1], $inc, $dec)
}
}
# --- 3. 通道数据区 0x3C-0x43B (1024B): 非零分布 ---
Write-Host "`n=== 通道数据区非零统计 (0x3C 起 1024B) ==="
$nz = @{}
foreach ($f in $dmx) {
for ($i = 0x3C; $i -lt 0x43C; $i++) { if ($f.b[$i] -ne 0) { $rel = $i - 0x3C; if (-not $nz.ContainsKey($rel)) { $nz[$rel] = 0 }; $nz[$rel]++ } }
}
$nzKeys = $nz.Keys | Sort-Object
Write-Host (" 非零字节位置数: {0}" -f $nzKeys.Count)
if ($nzKeys.Count -gt 0) {
Write-Host (" 最小相对偏移: {0} (CH{1}) 最大相对偏移: {2} (CH{3})" -f $nzKeys[0], ($nzKeys[0]+1), $nzKeys[-1], ($nzKeys[-1]+1))
$ranges = @(); $rs = $nzKeys[0]; $rp = $nzKeys[0]
foreach ($k in $nzKeys) { if ($k -gt $rp + 1) { $ranges += "$rs-$rp"; $rs = $k }; $rp = $k }
$ranges += "$rs-$rp"
Write-Host (" 连续非零区间 (相对0x3C): " + ($ranges -join ", "))
}
# 首帧前 32 通道值
Write-Host (" 首帧 CH1-32: " + (($first[0x3C..0x5B] | ForEach-Object { $_.ToString("x2") }) -join " "))
# --- 4. 速率与时间隔 ---
$tg = New-Object System.Collections.ArrayList
for ($i = 1; $i -lt $dmx.Count; $i++) { [void]$tg.Add([int](($dmx[$i].t - $dmx[$i-1].t) * 1000)) }
$tgs = $tg | Sort-Object
Write-Host ("`n=== 帧间隔ms: min={0} p50={1} p90={2} max={3} 率={4:N1}/s ===" -f $tgs[0], $tgs[[int]($tgs.Count/2)], $tgs[[int]($tgs.Count*0.9)], $tgs[-1], ($dmx.Count / ($dmx[-1].t - $dmx[0].t)))

View File

@ -0,0 +1,82 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\ss123.tsv")
$dmx = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.113" -or $p[2] -ne "192.168.123.1") { continue }
$b = H2B $p[4]; if ($b.Length -ne 1084) { continue }
if ([int]$b[4] -ne 4) { continue }
if (-not ($b[0x20] -eq 1 -and $b[0x21] -eq 1)) { continue }
[void]$dmx.Add(@{ t=[double]$p[0]; b=$b })
}
Write-Host ("DMX-A 帧数: {0}" -f $dmx.Count)
# --- 0x22-0x29 作为 LE u64 检验 ---
function GU64($b, $off) {
$v = [uint64]0
for ($i = 7; $i -ge 0; $i--) { $v = $v * 256 + [uint64]$b[$off+$i] }
return $v
}
$inc64 = 0; $dec64 = 0; $eq = 0
$deltas = New-Object System.Collections.ArrayList
for ($i = 1; $i -lt $dmx.Count; $i++) {
$v0 = GU64 $dmx[$i-1].b 0x22; $v1 = GU64 $dmx[$i].b 0x22
if ($v1 -gt $v0) { $inc64++; [void]$deltas.Add($v1 - $v0) } elseif ($v1 -lt $v0) { $dec64++ } else { $eq++ }
}
Write-Host ("u64@0x22: 增={0} 减={1} 等={2}" -f $inc64, $dec64, $eq)
if ($deltas.Count -gt 0) {
$ds = $deltas | Sort-Object
Write-Host (" 增量: min={0} p50={1} p90={2} max={3}" -f $ds[0], $ds[[int]($ds.Count/2)], $ds[[int]($ds.Count*0.9)], $ds[-1])
$top = $deltas | Group-Object | Sort-Object { [int]$_.Name } | Select-Object -First 8
Write-Host (" 常见增量: " + (($top | ForEach-Object { "{0}×{1}" -f $_.Name, $_.Count }) -join ", "))
}
Write-Host (" 首帧 u64={0} (0x{0:x}) 尾帧 u64={1} (0x{1:x})" -f (GU64 $dmx[0].b 0x22), (GU64 $dmx[-1].b 0x22))
# --- u64 增量 vs 实际时间差: 是否微秒时间戳 ---
Write-Host "`n=== u64 增量 vs 时间差相关性 (前 12 帧) ==="
for ($i = 1; $i -lt 13; $i++) {
$dt = ($dmx[$i].t - $dmx[$i-1].t) * 1000000 # us
$dv = [int64]((GU64 $dmx[$i].b 0x22) - (GU64 $dmx[$i-1].b 0x22))
Write-Host (" dt={0,8:N0}us du64={1,8}" -f $dt, $dv)
}
# --- 0x1A-0x1B: 与通道数据的校验和关系? ---
Write-Host "`n=== 0x1A-0x1B 检验 (是否通道数据校验) ==="
$matchSum = 0; $matchCrc = 0
foreach ($f in ($dmx | Select-Object -First 200)) {
$stored = [int]$f.b[0x1A] + [int]$f.b[0x1B]*256
# 简单和 (0x3C 起 1024B)
$sum = 0; foreach ($v in $f.b[0x3C..0x43B]) { $sum += $v }
if (($sum -band 0xFFFF) -eq $stored) { $matchSum++ }
# CRC16-IBM
$crc = 0
foreach ($v in $f.b[0x3C..0x43B]) {
$crc = $crc -bxor $v
for ($j = 0; $j -lt 8; $j++) { if ($crc -band 1) { $crc = ($crc -shr 1) -bxor 0xA001 } else { $crc = $crc -shr 1 } }
}
if ($crc -eq $stored) { $matchCrc++ }
}
Write-Host (" 简单和匹配: {0}/200 CRC16-IBM匹配: {1}/200" -f $matchSum, $matchCrc)
# 打印几个 stored 值与帧序关系
Write-Host (" 0x1A-1B 前 10 帧: " + ((($dmx | Select-Object -First 10) | ForEach-Object { "{0:x2}{1:x2}" -f $_.b[0x1B], $_.b[0x1A] }) -join " "))
# --- 0x22-0x29 是否某种位打包 seq? 试 u32@0x22 / u32@0x26 ---
foreach ($off in @(0x22, 0x24, 0x26)) {
$v0 = (GU64 $dmx[0].b $off) -band 0xFFFFFFFF
$vN = (GU64 $dmx[-1].b $off) -band 0xFFFFFFFF
$inc = 0; $dec = 0
for ($i = 1; $i -lt $dmx.Count; $i++) {
$a = (GU64 $dmx[$i-1].b $off) -band 0xFFFFFFFF
$b2 = (GU64 $dmx[$i].b $off) -band 0xFFFFFFFF
if ($b2 -gt $a) { $inc++ } elseif ($b2 -lt $a) { $dec++ }
}
Write-Host ("u32@0x{0:x2}: 首={1} 尾={2} 增={3} 减={4}" -f $off, $v0, $vN, $inc, $dec)
}

View File

@ -0,0 +1,96 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
function HexR($b, $o, $l) { return (($b[$o..($o+$l-1)] | ForEach-Object { $_.ToString("x2") }) -join "") }
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\ss123.tsv")
$dmx = New-Object System.Collections.ArrayList
$keys = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]
if ($p[1] -eq "192.168.123.113" -and $p[2] -eq "192.168.123.1" -and $b.Length -eq 1084 -and [int]$b[4] -eq 4 -and $b[0x20] -eq 1 -and $b[0x21] -eq 1) {
[void]$dmx.Add(@{ t=[double]$p[0]; b=$b })
}
# 密钥报文: 主机→广播 1947B 帧
if ($p[1] -eq "192.168.123.113" -and $p[2] -match "255" -and $b.Length -ge 1900) {
[void]$keys.Add(@{ t=[double]$p[0]; b=$b })
}
}
Write-Host ("DMX-A={0} 密钥广播={1}" -f $dmx.Count, $keys.Count)
# === 1. 相同通道数据的帧, 可变字节是否相同? ===
Write-Host "`n=== 相同通道数据帧的可变字节对比 ==="
$byData = @{}
foreach ($f in $dmx) {
$dh = HexR $f.b 0x3C 64 # 前64通道作指纹
if (-not $byData.ContainsKey($dh)) { $byData[$dh] = New-Object System.Collections.ArrayList }
[void]$byData[$dh].Add($f)
}
$groups = $byData.GetEnumerator() | Where-Object { $_.Value.Count -ge 2 } | Select-Object -First 3
$gcount = 0
foreach ($g in $groups) {
$gcount++
Write-Host (" 组{0} ({1}帧):" -f $gcount, $g.Value.Count)
foreach ($f in ($g.Value | Select-Object -First 4)) {
Write-Host (" t={0,8:F3} 0x1A={1} 0x22={2}" -f $f.t, (HexR $f.b 0x1A 2), (HexR $f.b 0x22 8))
}
}
if ($gcount -eq 0) { Write-Host " (120s 内通道数据无重复 — 效果持续变化)" }
# === 2. 可变字节是否随帧序平滑变化? 连续 8 帧的 0x22-0x29 ===
Write-Host "`n=== 连续 8 帧 0x1A/0x22 原始值 ==="
foreach ($f in ($dmx | Select-Object -First 8)) {
Write-Host (" t={0,8:F3} 0x1A={1} 0x22-0x29={2}" -f $f.t, (HexR $f.b 0x1A 2), (HexR $f.b 0x22 8))
}
# === 3. 密钥广播结构 ===
if ($keys.Count -gt 0) {
Write-Host "`n=== 密钥广播帧 (前2个) ==="
foreach ($k in ($keys | Select-Object -First 2)) {
Write-Host (" t={0,8:F3} len={1}" -f $k.t, $k.b.Length)
Write-Host (" 0x00-0x2F: " + (HexR $k.b 0 0x30))
Write-Host (" 0x30-0x5F: " + (HexR $k.b 0x30 0x30))
}
# 密钥帧之间是否相同
if ($keys.Count -ge 2) {
$same = 0; $diff = 0
for ($i = 0; $i -lt [Math]::Min(0x100, $keys[0].b.Length); $i++) {
if ($keys[0].b[$i] -eq $keys[1].b[$i]) { $same++ } else { $diff++ }
}
Write-Host (" 帧1vs帧2 前256B: 同={0} 异={1}" -f $same, $diff)
}
# 密钥帧间隔
$kg = New-Object System.Collections.ArrayList
for ($i = 1; $i -lt $keys.Count; $i++) { [void]$kg.Add([int](($keys[$i].t - $keys[$i-1].t) * 1000)) }
Write-Host (" 间隔ms: " + (($kg | Select-Object -First 12) -join ", "))
}
# === 4. 0x22-0x29 是否 = f(通道数据)? 试常见简单哈希 ===
Write-Host "`n=== 0x22-0x29 vs 通道数据简单函数 ==="
$m_add=0; $m_xor=0; $m_crc32=0
foreach ($f in ($dmx | Select-Object -First 100)) {
$d = $f.b[0x3C..0x43B]
# CRC32 (IEEE)
$crc = [uint32]0xFFFFFFFF
foreach ($v in $d) {
$crc = $crc -bxor $v
for ($j=0; $j -lt 8; $j++) { if ($crc -band 1) { $crc = ($crc -shr 1) -bxor 0xEDB88320 } else { $crc = $crc -shr 1 } }
}
$crc = $crc -bxor 0xFFFFFFFF
$s0 = [uint32]$f.b[0x22] + [uint32]$f.b[0x23]*256 + [uint32]$f.b[0x24]*65536 + [uint32]$f.b[0x25]*16777216
$s1 = [uint32]$f.b[0x26] + [uint32]$f.b[0x27]*256 + [uint32]$f.b[0x28]*65536 + [uint32]$f.b[0x29]*16777216
if ($crc -eq $s0 -or $crc -eq $s1) { $m_crc32++ }
}
Write-Host (" CRC32(通道数据) 匹配 0x22或0x26: {0}/100" -f $m_crc32)
# === 5. 常量字段 0x2C-0x3B 的稳定性 (是否随 universe 变化) ===
Write-Host "`n=== 0x2C-0x3B 常量确认 ==="
$var34 = @{}
foreach ($f in $dmx) { $h = HexR $f.b 0x2C 16; if (-not $var34.ContainsKey($h)) { $var34[$h] = 0 }; $var34[$h]++ }
foreach ($kv in $var34.GetEnumerator()) { Write-Host (" {0} : {1}帧" -f $kv.Key, $kv.Value) }

View File

@ -0,0 +1,81 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
function UidStr($b, $o) { if ($b.Length -lt $o+6) { return "?" }; return ($b[$o..($o+5)] | ForEach-Object { $_.ToString("x2") }) -join "" }
function Analyze2($name, $tsv, $devIp, $hostIp) {
$lines = [IO.File]::ReadAllLines($tsv)
Write-Host "`n############ $name ($devIp) ############"
# 真实 RDM 查询: 0x20-0x21 == 0200 (事件B帧), flag 0x30==0
$rdmCmd = @{}; $rdmPid = @{}; $rdmPidUid = @{}
$falsePos = 0
$bFrames = New-Object System.Collections.ArrayList # 所有事件B帧 (时间, 0x22序号, 0x3C, 0x3D)
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne $hostIp -or $p[2] -ne $devIp) { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x52 -or $b.Length -gt 2000) { continue }
if ([int]$b[4] -ne 4) { continue }
$t = [double]$p[0]
$isB = ($b[0x20] -eq 2 -and $b[0x21] -eq 0)
$isA = ($b[0x20] -eq 1 -and $b[0x21] -eq 1)
if ($isB -and $b.Length -gt 0x44) {
[void]$bFrames.Add(@{ t=$t; seq=([int]$b[0x22]+[int]$b[0x23]*256); c3c=$b[0x3C]; c3d=$b[0x3D]; flag=$b[0x30] })
}
# RDM 判定: 必须 0x3C==01 且 cmd 匹配
if ($b.Length -gt 0x44 -and $b[0x3C] -eq 1 -and ($b[0x3D] -eq 0x24 -or $b[0x3D] -eq 0x18 -or $b[0x3D] -eq 0x19 -or $b[0x3D] -eq 0x1a)) {
if ($b[0x30] -ne 0) { continue }
if ($isA) { $falsePos++; continue } # DMX-A 帧通道数据巧合 = 假阳性
$cmd = $b[0x3D]
$ck = "cmd={0:x2}" -f $cmd
if (-not $rdmCmd.ContainsKey($ck)) { $rdmCmd[$ck] = 0 }; $rdmCmd[$ck]++
if ($cmd -ne 0x24 -and $b.Length -gt 0x51) {
$pidKey = "{0:x2}{1:x2}" -f $b[0x50], $b[0x51]
$uid = UidStr $b 0x3E
if (-not $rdmPid.ContainsKey($pidKey)) { $rdmPid[$pidKey] = 0 }; $rdmPid[$pidKey]++
$k2 = "$pidKey->$uid"; if (-not $rdmPidUid.ContainsKey($k2)) { $rdmPidUid[$k2] = 0 }; $rdmPidUid[$k2]++
}
}
}
Write-Host ("--- 真实 RDM 查询 (排除 DMX-A 假阳性 {0} 条) ---" -f $falsePos)
$rdmCmd.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
Write-Host "--- PID 分布 ---"
$rdmPid.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 12 | ForEach-Object { Write-Host (" PID={0} : {1}" -f $_.Key, $_.Value) }
Write-Host "--- PID->UID 细分 (0002/0003/0060/00f0/0201) ---"
$rdmPidUid.GetEnumerator() | Where-Object { $_.Name -match "^(0002|0003|0060|00f0|0201)" } | Sort-Object Name | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Name, $_.Value) }
# 42B ACK 相关性: 每个 42B 帧之前 200ms 内最近的 B 帧特征
$acks = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne $devIp -or $p[2] -ne $hostIp) { continue }
$b = H2B $p[4]; if ($b.Length -ne 42 -or [int]$b[4] -ne 4) { continue }
[void]$acks.Add(@{ t=([double]$p[0]); seq=([int]$b[0x20]+[int]$b[0x21]*256) })
}
$ackByB = @{}
$ackNoB = 0
foreach ($a in $acks) {
# 找 seq == ack.seq+1 的 B 帧 (§4.3: ACK seq = 事件 seq - 1)
$found = $null
foreach ($bf in $bFrames) {
if ($bf.seq -eq $a.seq + 1) { $found = $bf; break }
}
if ($found) {
$k = "0x3C={0:x2} 0x3D={1:x2} flag={2:x2}" -f $found.c3c, $found.c3d, $found.flag
if (-not $ackByB.ContainsKey($k)) { $ackByB[$k] = 0 }; $ackByB[$k]++
} else { $ackNoB++ }
}
Write-Host ("--- 42B ACK ({0}个) 对应的 B 帧特征 ---" -f $acks.Count)
$ackByB.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 8 | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
Write-Host (" (无配对 B 帧: {0})" -f $ackNoB)
}
Analyze2 "真机 .1" "$env:TEMP\opencode\ss123.tsv" "192.168.123.1" "192.168.123.113"
Analyze2 "虚拟 .7 新代码" "$env:TEMP\opencode\fix2.tsv" "192.168.58.7" "192.168.58.107"
Write-Host "`n分析完成。"

View File

@ -0,0 +1,69 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\ss123.tsv")
# 事件提取: 0003广播MUTE / 0002定向UNMUTE / 脉冲
$events = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x30) { continue }
$t = [double]$p[0]
if ($p[1] -eq "192.168.123.113" -and $p[2] -eq "192.168.123.1" -and [int]$b[4] -eq 4 -and $b.Length -gt 0x51 -and $b.Length -lt 2000 -and $b[0x20] -eq 2 -and $b[0x21] -eq 0 -and $b[0x30] -eq 0 -and $b[0x3C] -eq 1 -and $b[0x3D] -eq 0x18) {
$pid2 = "{0:x2}{1:x2}" -f $b[0x50], $b[0x51]
$uid = ($b[0x3E..0x43] | ForEach-Object { $_.ToString("x2") }) -join ""
if ($pid2 -eq "0003") { [void]$events.Add(@{ t=$t; kind="MUTE"; uid=$uid }) }
if ($pid2 -eq "0002") { [void]$events.Add(@{ t=$t; kind="UNMUTE"; uid=$uid }) }
}
if ($p[1] -eq "192.168.123.1" -and $p[2] -eq "192.168.123.113" -and $b.Length -lt 200 -and $b[0x2A] -eq 0xAA) {
# 解码脉冲 UID
$uid = ""
for ($i = 0; $i -lt 6; $i++) {
$b1 = $b[0x2B + 2*$i]; $b2 = $b[0x2C + 2*$i]
$byte = ([int]$b1 -band 0x55) -bor ([int]$b2 -band 0xAA)
$uid += $byte.ToString("x2")
}
[void]$events.Add(@{ t=$t; kind="PULSE"; uid=$uid })
}
}
$mutes = @($events | Where-Object { $_.kind -eq "MUTE" })
$unmutes = @($events | Where-Object { $_.kind -eq "UNMUTE" })
$pulses = @($events | Where-Object { $_.kind -eq "PULSE" })
Write-Host ("MUTE={0} UNMUTE={1} 脉冲={2}" -f $mutes.Count, $unmutes.Count, $pulses.Count)
# MUTE 时间点与脉冲静默窗口: 每个 MUTE 后 3s 内脉冲数 vs 3-15s 脉冲数
Write-Host "`n--- 广播 MUTE(0003) 时间点 vs 脉冲密度 ---"
foreach ($m in $mutes) {
$before = @($pulses | Where-Object { $_.t -ge $m.t - 3 -and $_.t -lt $m.t }).Count
$after3 = @($pulses | Where-Object { $_.t -ge $m.t -and $_.t -lt $m.t + 3 }).Count
$after12 = @($pulses | Where-Object { $_.t -ge $m.t + 3 -and $_.t -lt $m.t + 15 }).Count
Write-Host (" MUTE t={0,7:F2} 前3s脉冲={1,-3} 后0-3s={2,-3} 后3-15s={3}" -f $m.t, $before, $after3, $after12)
}
# UNMUTE 时间点 vs 其后脉冲 (同 UID)
Write-Host "`n--- 定向 UNMUTE(0002) vs 其后 1s 内同 UID 脉冲 ---"
foreach ($u in ($unmutes | Select-Object -First 10)) {
$same = @($pulses | Where-Object { $_.t -ge $u.t -and $_.t -lt $u.t + 1 -and $_.uid -eq $u.uid }).Count
$any = @($pulses | Where-Object { $_.t -ge $u.t -and $_.t -lt $u.t + 1 }).Count
Write-Host (" UNMUTE t={0,7:F2} UID={1} 后1s同UID脉冲={2} 任意={3}" -f $u.t, $u.uid, $same, $any)
}
# 脉冲的 UID 分布随时间 (每 15s 桶)
Write-Host "`n--- 脉冲 UID×15s 桶 ---"
$buckets = @{}
foreach ($pu in $pulses) {
$bk = [int]($pu.t / 15)
if (-not $buckets.ContainsKey($bk)) { $buckets[$bk] = @{} }
if (-not $buckets[$bk].ContainsKey($pu.uid)) { $buckets[$bk][$pu.uid] = 0 }
$buckets[$bk][$pu.uid]++
}
foreach ($bk in ($buckets.Keys | Sort-Object)) {
$s = ($buckets[$bk].GetEnumerator() | ForEach-Object { "$($_.Key.Substring(8))×$($_.Value)" }) -join " "
Write-Host (" t={0,3}-{1,-3}s: {2}" -f ($bk*15), ($bk*15+15), $s)
}

View File

@ -0,0 +1,57 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
function HexR($b, $o, $l) { if ($o + $l -gt $b.Length) { $l = $b.Length - $o }; return (($b[$o..($o+$l-1)] | ForEach-Object { $_.ToString("x2") }) -join " ") }
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\set123.tsv")
$sets = New-Object System.Collections.ArrayList
$resps = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x40) { continue }
$t = [double]$p[0]
# 主机→设备 事件帧, CC=0x30 (SET) @0x4F
if ($p[1] -eq "192.168.123.113" -and $b.Length -eq 1084 -and [int]$b[4] -eq 4 -and $b[0x20] -eq 2 -and $b[0x21] -eq 0 -and $b.Length -gt 0x52 -and $b[0x4F] -eq 0x30) {
[void]$sets.Add(@{ t=$t; b=$b })
}
# 设备→主机 应答, CC=0x31 (SET_RESPONSE) @0x2A... 应答帧 RDM 区在 0x2A 起, CC@0x2A, msgLen@0x2B, ... CC字段在 0x2A
if ($p[1] -eq "192.168.123.1" -and $b.Length -lt 300 -and $b.Length -gt 0x30 -and $b[0x2A] -eq 0xCC -and $b[0x3A] -eq 0x31) {
[void]$resps.Add(@{ t=$t; b=$b })
}
}
Write-Host ("SET 请求 (CC=0x30): {0} SET 应答 (CC=0x31): {1}" -f $sets.Count, $resps.Count)
Write-Host "`n=== SET 请求逐帧解析 ==="
foreach ($s in $sets) {
$b = $s.b
$flag = $b[0x30]
$rdmlen = [int]$b[0x34] + [int]$b[0x35]*256
$uid = (HexR $b 0x3E 6) -replace " ",""
$tn = $b[0x4A]
$cc = $b[0x4F]
$pidH = "{0:x2}{1:x2}" -f $b[0x50], $b[0x51]
$pdl = [int]$b[0x52]
$pd = HexR $b 0x53 ([Math]::Min($pdl, 24))
Write-Host ("t={0,8:F3} flag={1:x2} rdmlen={2} UID={3} TN={4:x2} CC={5:x2} PID={6} PDL={7} PD=[{8}]" -f $s.t, $flag, $rdmlen, $uid, $tn, $cc, $pidH, $pdl, $pd)
}
Write-Host "`n=== SET 应答逐帧解析 (CC=0x31) ==="
foreach ($r in $resps) {
$b = $r.b
$ml = [int]$b[0x2B]
$uid = (HexR $b 0x33 6) -replace " ",""
$tn = $b[0x39]
$rt = $b[0x3A]
$pidH = "{0:x2}{1:x2}" -f $b[0x3F], $b[0x40]
$pdl = [int]$b[0x41]
$pd = ""
if ($pdl -gt 0 -and $b.Length -gt 0x42) { $pd = HexR $b 0x42 ([Math]::Min($pdl, 16)) }
# checksum 校验
$sum = 0; for ($i = 0x2A; $i -lt 0x2A + $ml; $i++) { $sum += $b[$i] }
$cs = ([int]$b[0x2A+$ml] -shl 8) -bor [int]$b[0x2A+$ml+1]
$ok = if (($sum -band 0xFFFF) -eq $cs) { "OK" } else { "BAD" }
Write-Host ("t={0,8:F3} len={1} UID={2} TN={3:x2} RT={4:x2} PID={5} PDL={6} PD=[{7}] cs={8}" -f $r.t, $b.Length, $uid, $tn, $rt, $pidH, $pdl, $pd, $ok)
}

View File

@ -0,0 +1,57 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
function HexR($b, $o, $l) { if ($o + $l -gt $b.Length) { $l = $b.Length - $o }; return (($b[$o..($o+$l-1)] | ForEach-Object { $_.ToString("x2") }) -join " ") }
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\set123b.tsv")
$sets = New-Object System.Collections.ArrayList
$resps = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x40) { continue }
$t = [double]$p[0]
# 主机→设备 事件帧, CC=0x30 (SET) @0x4F
if ($p[1] -eq "192.168.123.113" -and $b.Length -eq 1084 -and [int]$b[4] -eq 4 -and $b[0x20] -eq 2 -and $b[0x21] -eq 0 -and $b.Length -gt 0x52 -and $b[0x4F] -eq 0x30) {
[void]$sets.Add(@{ t=$t; b=$b })
}
# 设备→主机 应答, CC=0x31 (SET_RESPONSE) @0x2A... 应答帧 RDM 区在 0x2A 起, CC@0x2A, msgLen@0x2B, ... CC字段在 0x2A
if ($p[1] -eq "192.168.123.1" -and $b.Length -lt 300 -and $b.Length -gt 0x30 -and $b[0x2A] -eq 0xCC -and $b[0x3A] -eq 0x31) {
[void]$resps.Add(@{ t=$t; b=$b })
}
}
Write-Host ("SET 请求 (CC=0x30): {0} SET 应答 (CC=0x31): {1}" -f $sets.Count, $resps.Count)
Write-Host "`n=== SET 请求逐帧解析 ==="
foreach ($s in $sets) {
$b = $s.b
$flag = $b[0x30]
$rdmlen = [int]$b[0x34] + [int]$b[0x35]*256
$uid = (HexR $b 0x3E 6) -replace " ",""
$tn = $b[0x4A]
$cc = $b[0x4F]
$pidH = "{0:x2}{1:x2}" -f $b[0x50], $b[0x51]
$pdl = [int]$b[0x52]
$pd = HexR $b 0x53 ([Math]::Min($pdl, 24))
Write-Host ("t={0,8:F3} flag={1:x2} rdmlen={2} UID={3} TN={4:x2} CC={5:x2} PID={6} PDL={7} PD=[{8}]" -f $s.t, $flag, $rdmlen, $uid, $tn, $cc, $pidH, $pdl, $pd)
}
Write-Host "`n=== SET 应答逐帧解析 (CC=0x31) ==="
foreach ($r in $resps) {
$b = $r.b
$ml = [int]$b[0x2B]
$uid = (HexR $b 0x33 6) -replace " ",""
$tn = $b[0x39]
$rt = $b[0x3A]
$pidH = "{0:x2}{1:x2}" -f $b[0x3F], $b[0x40]
$pdl = [int]$b[0x41]
$pd = ""
if ($pdl -gt 0 -and $b.Length -gt 0x42) { $pd = HexR $b 0x42 ([Math]::Min($pdl, 16)) }
# checksum 校验
$sum = 0; for ($i = 0x2A; $i -lt 0x2A + $ml; $i++) { $sum += $b[$i] }
$cs = ([int]$b[0x2A+$ml] -shl 8) -bor [int]$b[0x2A+$ml+1]
$ok = if (($sum -band 0xFFFF) -eq $cs) { "OK" } else { "BAD" }
Write-Host ("t={0,8:F3} len={1} UID={2} TN={3:x2} RT={4:x2} PID={5} PDL={6} PD=[{7}] cs={8}" -f $r.t, $b.Length, $uid, $tn, $rt, $pidH, $pdl, $pd, $ok)
}

View File

@ -0,0 +1,157 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
function UidStr($b, $o) { if ($b.Length -lt $o+6) { return "?" }; return ($b[$o..($o+5)] | ForEach-Object { $_.ToString("x2") }) -join "" }
function Analyze($name, $tsv, $devIp, $hostIp) {
$lines = [IO.File]::ReadAllLines($tsv)
$t0 = 1e9; $t1 = 0
foreach ($l in $lines) { $p = $l -split "`t"; if ($p.Count -lt 1 -or -not $p[0]) { continue }; $t = [double]$p[0]; if ($t -lt $t0) { $t0 = $t }; if ($t -gt $t1) { $t1 = $t } }
$dur = $t1 - $t0
$h2d = @{} # host->dev: 帧型
$d2h = @{} # dev->host: 帧型
$rdmCmd = @{} # host->dev RDM cmd 直方图 (flag=00)
$rdmPid = @{}
$queries = New-Object System.Collections.ArrayList # RDM 查询 (flag=00, cmd 18/19/1a/24)
$answers = New-Object System.Collections.ArrayList # dev->host RDM 短帧
$dmxA = 0; $evtB = 0; $ack42 = 0
$devUnsolicited = New-Object System.Collections.ArrayList # dev->host 42B 帧时间
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x1A) { continue }
$t = [double]$p[0]
$typ = [int]$b[4]
$isH2D = ($p[1] -eq $hostIp -and $p[2] -eq $devIp)
$isD2H = ($p[1] -eq $devIp -and $p[2] -eq $hostIp)
if (-not $isH2D -and -not $isD2H) { continue }
$len = $b.Length
if ($isH2D) {
$k = "t$typ|$len"
if (-not $h2d.ContainsKey($k)) { $h2d[$k] = 0 }; $h2d[$k]++
if ($typ -eq 4 -and $len -gt 1000) {
if ($b[0x20] -eq 1 -and $b[0x21] -eq 1) { $dmxA++ } else { $evtB++ }
}
if ($typ -eq 4 -and $len -lt 2000 -and $len -gt 0x44 -and $b[0x30] -eq 0) {
$cmd = $b[0x3D]
if ($cmd -eq 0x24 -or $cmd -eq 0x18 -or $cmd -eq 0x19 -or $cmd -eq 0x1a) {
$ck = "cmd={0:x2}" -f $cmd
if (-not $rdmCmd.ContainsKey($ck)) { $rdmCmd[$ck] = 0 }; $rdmCmd[$ck]++
$pidKey = ""
if ($cmd -ne 0x24 -and $b.Length -gt 0x51) { $pidKey = "{0:x2}{1:x2}" -f $b[0x50], $b[0x51]
if (-not $rdmPid.ContainsKey($pidKey)) { $rdmPid[$pidKey] = 0 }; $rdmPid[$pidKey]++ }
[void]$queries.Add(@{ t=$t; cmd=$cmd; pid=$pidKey; uid=(UidStr $b 0x3E); seq=([int]$b[0x22] + [int]$b[0x23]*256); tn=$b[0x4A]; len=$len })
}
}
} else {
$k = "t$typ|$len"
if (-not $d2h.ContainsKey($k)) { $d2h[$k] = 0 }; $d2h[$k]++
if ($typ -eq 4 -and $len -eq 42) { $ack42++; [void]$devUnsolicited.Add($t) }
if ($typ -eq 4 -and $len -lt 200 -and $len -gt 0x2C -and ($b[0x2A] -eq 0xCC -or $b[0x2A] -eq 0xAA)) {
[void]$answers.Add(@{ t=$t; b=$b; len=$len; seq=([int]$b[0x20] + [int]$b[0x21]*256); pulse=($b[0x2A] -eq 0xAA) })
}
}
}
Write-Host "`n############ $name ($devIp) 时长 $([int]$dur)s ############"
Write-Host "--- host→dev 帧型 (Top12) ---"
$h2d.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 12 | ForEach-Object { Write-Host (" {0} : {1} ({2:F1}/s)" -f $_.Key, $_.Value, ($_.Value / $dur)) }
Write-Host "--- dev→host 帧型 (Top12) ---"
$d2h.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 12 | ForEach-Object { Write-Host (" {0} : {1} ({2:F1}/s)" -f $_.Key, $_.Value, ($_.Value / $dur)) }
Write-Host ("--- DMX-A={0} ({1:F1}/s) 事件B={2} ({3:F1}/s) 42B回帧={4} ({5:F1}/s) ACK/事件B比={6:F2}" -f $dmxA, ($dmxA/$dur), $evtB, ($evtB/$dur), $ack42, ($ack42/$dur), $(if ($evtB -gt 0) { $ack42/$evtB } else { 0 }))
Write-Host "--- RDM 查询 cmd (flag=00) ---"
$rdmCmd.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
Write-Host "--- RDM 查询 PID (cmd!=24) ---"
$rdmPid.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 15 | ForEach-Object { Write-Host (" PID={0} : {1}" -f $_.Key, $_.Value) }
# 应答时延: cmd=18/19/1a 查询 → 200ms 内 seq 回显应答
$lat = New-Object System.Collections.ArrayList
$paired = 0; $unpaired = 0
foreach ($q in $queries) {
if ($q.cmd -eq 0x24) { continue }
$best = $null
foreach ($a in $answers) {
if ($a.pulse) { continue }
if ($a.t -lt $q.t -or $a.t -gt $q.t + 0.2) { continue }
if ($a.seq -eq $q.seq) { $best = $a; break }
}
if ($best) { $paired++; [void]$lat.Add([int](($best.t - $q.t) * 1000)) } else { $unpaired++ }
}
if ($lat.Count -gt 0) {
$ls = $lat | Sort-Object
Write-Host ("--- 参数应答时延: 配对={0} 未配对={1} min={2}ms 中位={3}ms max={4}ms" -f $paired, $unpaired, $ls[0], $ls[[int]($ls.Count/2)], $ls[-1])
} else {
Write-Host ("--- 参数应答时延: 配对={0} 未配对={1} (无样本)" -f $paired, $unpaired)
}
# 脉冲时延
$plat = New-Object System.Collections.ArrayList
$discQs = @($queries | Where-Object { $_.cmd -eq 0x24 })
$pulses = @($answers | Where-Object { $_.pulse })
foreach ($pu in $pulses) {
$best = $null
for ($i = $discQs.Count - 1; $i -ge 0; $i--) { if ($discQs[$i].t -le $pu.t) { $best = $discQs[$i]; break } }
if ($best -and ($pu.t - $best.t) -lt 0.5) { [void]$plat.Add([int](($pu.t - $best.t) * 1000)) }
}
if ($plat.Count -gt 0) {
$ps = $plat | Sort-Object
Write-Host ("--- 脉冲: {0} 个 (应答率 {1:F0}% of cmd24) 时延 min={2}ms 中位={3}ms max={4}ms" -f $pulses.Count, $(if ($discQs.Count -gt 0) { 100.0*$pulses.Count/$discQs.Count } else { 0 }), $ps[0], $ps[[int]($ps.Count/2)], $ps[-1])
}
return @{ queries=$queries; answers=$answers }
}
$r1 = Analyze "真机 .1" "$env:TEMP\opencode\ss123.tsv" "192.168.123.1" "192.168.123.113"
$r7 = Analyze "虚拟 .7" "$env:TEMP\opencode\ss58.tsv" "192.168.58.7" "192.168.58.107"
# ============ cmd=19/1a 深挖: 布局解析 (用 .1 的样本) ============
Write-Host "`n############ cmd=19/1a 布局深挖 (.1 样本) ############"
$cnt19 = 0; $cnt1a = 0
foreach ($q in $r1.queries) {
if ($q.cmd -eq 0x19 -and $cnt19 -lt 2) {
Write-Host (" cmd=19 t={0:F3} UID={1} PID={2} TN={3:x2} len={4}" -f $q.t, $q.uid, $q.pid, $q.tn, $q.len)
$cnt19++
}
if ($q.cmd -eq 0x1a -and $cnt1a -lt 2) {
Write-Host (" cmd=1a t={0:F3} UID={1} PID={2} TN={3:x2} len={4}" -f $q.t, $q.uid, $q.pid, $q.tn, $q.len)
$cnt1a++
}
}
# cmd=19/1a 的 PID 分布
$p19 = @{}; $p1a = @{}
foreach ($q in $r1.queries) {
if ($q.cmd -eq 0x19) { if (-not $p19.ContainsKey($q.pid)) { $p19[$q.pid] = 0 }; $p19[$q.pid]++ }
if ($q.cmd -eq 0x1a) { if (-not $p1a.ContainsKey($q.pid)) { $p1a[$q.pid] = 0 }; $p1a[$q.pid]++ }
}
Write-Host "--- cmd=19 PID 分布 (.1) ---"
$p19.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 10 | ForEach-Object { Write-Host (" PID={0} : {1}" -f $_.Key, $_.Value) }
Write-Host "--- cmd=1a PID 分布 (.1) ---"
$p1a.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 10 | ForEach-Object { Write-Host (" PID={0} : {1}" -f $_.Key, $_.Value) }
# cmd=19 查询的完整 hex 样本 (从原始 tsv 找一条)
Write-Host "--- cmd=19 查询完整 hex (1 条) ---"
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\ss123.tsv")
$done = 0
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.113" -or $p[2] -ne "192.168.123.1") { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x60 -or $b.Length -gt 2000) { continue }
if ($b[0x30] -ne 0 -or $b[0x3D] -ne 0x19) { continue }
Write-Host (" len={0} hex[0x20..0x60]={1}" -f $b.Length, (($b[0x20..([Math]::Min($b.Length-1, 0x60))] | ForEach-Object { $_.ToString("x2") }) -join ""))
# 找其应答
$seq = [int]$b[0x22] + [int]$b[0x23]*256
foreach ($a in $r1.answers) {
if (-not $a.pulse -and $a.seq -eq $seq -and $a.t -ge [double]$p[0] -and $a.t -le [double]$p[0] + 0.2) {
Write-Host (" 应答 len={0}: {1}" -f $a.len, (($a.b | ForEach-Object { $_.ToString("x2") }) -join ""))
break
}
}
$done++; if ($done -ge 2) { break }
}
Write-Host "`n分析完成。"

View File

@ -0,0 +1,81 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
function H2B($hex) {
if (-not $hex) { return $null }
$n = $hex.Length / 2
$b = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }
return $b
}
function UidStr($b, $o) { if ($b.Length -lt $o+6) { return "?" }; return ($b[$o..($o+5)] | ForEach-Object { $_.ToString("x2") }) -join "" }
function Analyze2($name, $tsv, $devIp, $hostIp) {
$lines = [IO.File]::ReadAllLines($tsv)
Write-Host "`n############ $name ($devIp) ############"
# 真实 RDM 查询: 0x20-0x21 == 0200 (事件B帧), flag 0x30==0
$rdmCmd = @{}; $rdmPid = @{}; $rdmPidUid = @{}
$falsePos = 0
$bFrames = New-Object System.Collections.ArrayList # 所有事件B帧 (时间, 0x22序号, 0x3C, 0x3D)
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne $hostIp -or $p[2] -ne $devIp) { continue }
$b = H2B $p[4]; if ($b.Length -lt 0x52 -or $b.Length -gt 2000) { continue }
if ([int]$b[4] -ne 4) { continue }
$t = [double]$p[0]
$isB = ($b[0x20] -eq 2 -and $b[0x21] -eq 0)
$isA = ($b[0x20] -eq 1 -and $b[0x21] -eq 1)
if ($isB -and $b.Length -gt 0x44) {
[void]$bFrames.Add(@{ t=$t; seq=([int]$b[0x22]+[int]$b[0x23]*256); c3c=$b[0x3C]; c3d=$b[0x3D]; flag=$b[0x30] })
}
# RDM 判定: 必须 0x3C==01 且 cmd 匹配
if ($b.Length -gt 0x44 -and $b[0x3C] -eq 1 -and ($b[0x3D] -eq 0x24 -or $b[0x3D] -eq 0x18 -or $b[0x3D] -eq 0x19 -or $b[0x3D] -eq 0x1a)) {
if ($b[0x30] -ne 0) { continue }
if ($isA) { $falsePos++; continue } # DMX-A 帧通道数据巧合 = 假阳性
$cmd = $b[0x3D]
$ck = "cmd={0:x2}" -f $cmd
if (-not $rdmCmd.ContainsKey($ck)) { $rdmCmd[$ck] = 0 }; $rdmCmd[$ck]++
if ($cmd -ne 0x24 -and $b.Length -gt 0x51) {
$pidKey = "{0:x2}{1:x2}" -f $b[0x50], $b[0x51]
$uid = UidStr $b 0x3E
if (-not $rdmPid.ContainsKey($pidKey)) { $rdmPid[$pidKey] = 0 }; $rdmPid[$pidKey]++
$k2 = "$pidKey->$uid"; if (-not $rdmPidUid.ContainsKey($k2)) { $rdmPidUid[$k2] = 0 }; $rdmPidUid[$k2]++
}
}
}
Write-Host ("--- 真实 RDM 查询 (排除 DMX-A 假阳性 {0} 条) ---" -f $falsePos)
$rdmCmd.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
Write-Host "--- PID 分布 ---"
$rdmPid.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 12 | ForEach-Object { Write-Host (" PID={0} : {1}" -f $_.Key, $_.Value) }
Write-Host "--- PID->UID 细分 (0002/0003/0060/00f0/0201) ---"
$rdmPidUid.GetEnumerator() | Where-Object { $_.Name -match "^(0002|0003|0060|00f0|0201)" } | Sort-Object Name | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Name, $_.Value) }
# 42B ACK 相关性: 每个 42B 帧之前 200ms 内最近的 B 帧特征
$acks = New-Object System.Collections.ArrayList
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne $devIp -or $p[2] -ne $hostIp) { continue }
$b = H2B $p[4]; if ($b.Length -ne 42 -or [int]$b[4] -ne 4) { continue }
[void]$acks.Add(@{ t=([double]$p[0]); seq=([int]$b[0x20]+[int]$b[0x21]*256) })
}
$ackByB = @{}
$ackNoB = 0
foreach ($a in $acks) {
# 找 seq == ack.seq+1 的 B 帧 (§4.3: ACK seq = 事件 seq - 1)
$found = $null
foreach ($bf in $bFrames) {
if ($bf.seq -eq $a.seq + 1) { $found = $bf; break }
}
if ($found) {
$k = "0x3C={0:x2} 0x3D={1:x2} flag={2:x2}" -f $found.c3c, $found.c3d, $found.flag
if (-not $ackByB.ContainsKey($k)) { $ackByB[$k] = 0 }; $ackByB[$k]++
} else { $ackNoB++ }
}
Write-Host ("--- 42B ACK ({0}个) 对应的 B 帧特征 ---" -f $acks.Count)
$ackByB.GetEnumerator() | Sort-Object { [int]$_.Value } -Descending | Select-Object -First 8 | ForEach-Object { Write-Host (" {0} : {1}" -f $_.Key, $_.Value) }
Write-Host (" (无配对 B 帧: {0})" -f $ackNoB)
}
Analyze2 "真机 .1" "$env:TEMP\opencode\ss123.tsv" "192.168.123.1" "192.168.123.113"
Analyze2 "虚拟 .7" "$env:TEMP\opencode\ss58.tsv" "192.168.58.7" "192.168.58.107"
Write-Host "`n分析完成。"

View File

@ -0,0 +1,14 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\sym_startup.pcap"
$ts = "C:\Program Files\Wireshark\tshark.exe"
Write-Host "=== claim frames (ident 110 / 47B 55 / AECH 560) ==="
& $ts -r $cap -Y "udp.length==110 or udp.length==55 or udp.length==560" -T fields -e frame.time_relative -e ip.src -e ip.dst -e udp.length 2>$null | Select-Object -First 12
Write-Host ""
Write-Host "=== host-sent frame types (all, until +30s after claim window end) ==="
$all = @(& $ts -r $cap -Y "ip.src==192.168.123.113" -T fields -e frame.time_relative -e ip.dst -e udp.length 2>$null)
$g = @{}
foreach ($l in $all) { $p = $l -split "`t"; $k = $p[1] + " len=" + $p[2]; if (-not $g.ContainsKey($k)) { $g[$k] = New-Object System.Collections.ArrayList }; [void]$g[$k].Add([double]$p[0]) }
foreach ($k in ($g.Keys | Sort-Object)) {
$arr = $g[$k]
Write-Host ("{0,-30} n={1,-6} first={2:N1}s last={3:N1}s" -f $k, $arr.Count, $arr[0], $arr[$arr.Count-1])
}

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\hd1.pcap"
Remove-Item $cap -ErrorAction SilentlyContinue
$ts = "C:\Program Files\Wireshark\tshark.exe"
$proc = Start-Process -FilePath $ts -ArgumentList "-i `"\Device\NPF_{E7165B1C-BAF6-49CF-9A3D-EF12DBC17329}`" -f `"udp port 31772`" -a duration:40 -w `"$cap`"" -PassThru -WindowStyle Hidden -RedirectStandardError "$env:TEMP\opencode\tshark_err.txt"
Start-Sleep -Seconds 3
Set-Location "C:\Users\tonycao\work\light\enet-validator"
& .\target\release\enet-validator.exe hostdemo 192.168.123.1 0
Start-Sleep -Seconds 30
Write-Host "`n=== 抓包全部帧 (time src dst udplen) ==="
& $ts -r $cap -T fields -e frame.time_relative -e ip.src -e ip.dst -e udp.length 2>$null | Select-Object -First 60

View File

@ -0,0 +1,9 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$r = Invoke-SSHCommand -SSHSession $s -Command "cat ~/run_butler.sh"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,25 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\ss123.tsv")
$checked = 0; $allDesc = 0
foreach ($ln in $lines) {
$p = $ln -split "`t"
if ($p.Count -lt 5 -or $p[4].Length -lt 100) { continue }
$hex = $p[4] -replace '[^0-9a-fA-F]',''
if ($hex.Length -ne 2168) { continue }
$b = New-Object byte[] 1084
for ($i = 0; $i -lt 1084; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring($i*2,2),16) }
if ($b[0x20] -ne 1 -or $b[0x21] -ne 1) { continue }
$checked++
if ($checked -gt 200) { break }
$ok = $true
foreach ($base in @(0x3C, 0x23C)) {
for ($i = 0; $i -lt 511; $i++) {
$d = ($b[$base+$i] - $b[$base+$i+1]) -band 0xFF
if ($d -ne 1) { $ok = $false; break }
}
if (-not $ok) { break }
}
if ($ok) { $allDesc++ }
}
echo "检查帧: $checked, 全 512B 严格递减(mod256): $allDesc"
# 若成立 → 数据=加密后的全零, rot 可从首字节推出

View File

@ -0,0 +1,12 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$raw = [IO.File]::ReadAllBytes("$env:TEMP\opencode\fix1.tsv")
$text = if ($raw[0] -eq 0xFF -and $raw[1] -eq 0xFE) { [Text.Encoding]::Unicode.GetString($raw) } else { [Text.Encoding]::UTF8.GetString($raw) }
$lines = $text -split "`r?`n"
foreach ($l in $lines) {
$p = $l -split "`t"
if ($p.Count -lt 5 -or -not $p[4]) { continue }
$h = $p[4] -replace " ", ""
if ($h.Length -lt 100) { continue }
if ($h.Substring(0x28 * 2, 8) -eq "41454348") { Write-Host ("AECH t={0,9} src={1} len={2}" -f $p[0], $p[1], ($h.Length/2)) }
if ($h.Substring(0x28 * 2, 8) -eq "41455245") { Write-Host ("AERE t={0,9} src={1} len={2} c0={3}" -f $p[0], $p[1], ($h.Length/2), $h.Substring(0xC0*2,8)) }
}

View File

@ -0,0 +1,9 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$r = Invoke-SSHCommand -SSHSession $s -Command "tail -15 ~/butler.log"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,9 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$r = Invoke-SSHCommand -SSHSession $s -Command "grep -E 'AECH|注册|RDM|心跳' ~/butler.log | tail -20"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,9 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$r = Invoke-SSHCommand -SSHSession $s -Command "pgrep -af 'enet-validator device' ; echo '---'; tail -8 ~/butler.log ; echo '---'; grep -c 'RDM-REQ' ~/butler.log"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,9 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$r = Invoke-SSHCommand -SSHSession $s -Command "wc -l ~/butler.log; echo '---'; grep -vE '发现请求 type1' ~/butler.log | tail -30"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,9 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$r = Invoke-SSHCommand -SSHSession $s -Command "wc -l ~/butler.log; echo '---'; grep -vE 'DMX #' ~/butler.log | tail -25"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$r = Invoke-SSHCommand -SSHSession $s -Command "pgrep -af enet; echo ---; ls -la ~/butler.log; echo ---; tail -n 3 ~/butler.log; echo ---; ss -lunp 2>/dev/null | grep 31772"
$r.Output
Remove-SSHSession -SSHSession $s | Out-Null

View File

@ -0,0 +1,9 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$r = Invoke-SSHCommand -SSHSession $s -Command "pgrep -af 'enet-validator device'; echo '---'; head -5 ~/butler.log"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,9 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$r = Invoke-SSHCommand -SSHSession $s -Command "pgrep -af enet-validator; echo '---'; ss -unp | grep 31772"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,9 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$r = Invoke-SSHCommand -SSHSession $s -Command "grep -E '\[RDM\] SET' ~/butler.log | tail -10; echo '---'; grep -E 'PID=00f0' ~/butler.log | tail -5"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$r = Invoke-SSHCommand -SSHSession $s -Command "pgrep -af enet-validator; echo ---; tail -n 8 ~/butler.log"
$r.Output
Remove-SSHSession -SSHSession $s | Out-Null

View File

@ -0,0 +1,10 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$r = Invoke-SSHCommand -SessionId $s.SessionId -Command "which git && git --version && ls ~/enet-validator 2>/dev/null | head -5"
$r.Output
Remove-SSHSession -SessionId $s.SessionId | Out-Null

View File

@ -0,0 +1,24 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$f = "$env:TEMP\opencode\cold2.tsv"
$lines = [IO.File]::ReadAllLines($f)
function H2B($hex) { $n = $hex.Length / 2; $b = New-Object byte[] $n; for ($i = 0; $i -lt $n; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring(2*$i, 2), 16) }; return $b }
$firstAECH = $null; $firstDMX = $null; $first42 = $null; $firstRDMreq = $null; $firstRDMresp = $null; $firstTele = $null
foreach ($l in $lines) {
$p = $l -split "`t"; if ($p.Count -lt 5) { continue }
$b = H2B $p[4]; $t = [double]$p[0]
$from113 = $p[1] -eq "192.168.123.113"
$from1 = $p[1] -eq "192.168.123.1"
if ($from113 -and $b.Length -eq 552 -and -not $firstAECH) { $firstAECH = $t; Write-Host ("first AECH challenge t=$t") }
if ($from113 -and $b.Length -eq 1084) {
$isRDM = ($b[0x3C] -eq 1 -and $b[0x30] -eq 0)
$isDMX = ($b[0x30] -eq 0 -and $b[0x2A] -eq 0) # 0x2A=00 => DMX data frame? from earlier: type4 flag=00 0x2A=00
if ($isRDM -and -not $firstRDMreq) { $firstRDMreq = $t; Write-Host ("first RDM req(flag00) t=$t") }
if (-not $isRDM -and -not $firstDMX) { $firstDMX = $t; Write-Host ("first DMX frame t=$t (0x2A=$($b[0x2A].ToString('x2')) flag=$($b[0x30].ToString('x2')))") }
}
if ($from1 -and $b.Length -eq 42 -and -not $first42) { $first42 = $t; Write-Host ("first 42B telemetry t=$t") }
if ($from1 -and $b.Length -ge 0x2E -and $b.Length -lt 200 -and ($b[0x2A] -eq 0xCC -or $b[0x2A] -eq 0xAA) -and -not $firstRDMresp) { $firstRDMresp = $t; Write-Host ("first RDM response t=$t len=$($b.Length)") }
if ($firstAECH -and $firstDMX -and $first42 -and $firstRDMreq -and $firstRDMresp) { break }
}
Write-Host ""
Write-Host ("order: AECH=$firstAECH RDMreq=$firstRDMreq DMX=$firstDMX 42B=$first42 RDMresp=$firstRDMresp")

View File

@ -0,0 +1,38 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$files = @(
@("C:\Users\tonycao\work\light\enet-validator\src\tests.rs", "~/enet-validator/src/tests.rs"),
@("C:\Users\tonycao\work\light\enet-validator\src\aech.rs", "~/enet-validator/src/aech.rs"),
@("C:\Users\tonycao\work\light\enet-validator\src\main.rs", "~/enet-validator/src/main.rs"),
@("C:\Users\tonycao\work\light\enet-validator\src\dmxhash.rs", "~/enet-validator/src/dmxhash.rs")
)
foreach ($f in $files) {
$src = $f[0]; $dst = $f[1]
$b64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($src))
$chunkSize = 6000
$chunks = [Math]::Ceiling($b64.Length / $chunkSize)
$r = Invoke-SSHCommand -SSHSession $s -Command "rm -f /tmp/up.b64 && touch /tmp/up.b64"
for ($i = 0; $i -lt $chunks; $i++) {
$part = $b64.Substring($i * $chunkSize, [Math]::Min($chunkSize, $b64.Length - $i * $chunkSize))
$r = Invoke-SSHCommand -SSHSession $s -Command "echo '$part' >> /tmp/up.b64"
}
$r = Invoke-SSHCommand -SSHSession $s -Command "base64 -d /tmp/up.b64 > $dst && md5sum $dst"
$localMd5 = (Get-FileHash $src -Algorithm MD5).Hash.ToLower()
$remoteMd5 = ($r.Output -join "").Split(" ")[0]
Write-Host ("{0}: {1}" -f (Split-Path $src -Leaf), $(if ($localMd5 -eq $remoteMd5) { "OK" } else { "MISMATCH!" }))
}
Write-Host "--- build on .7 ---"
$r = Invoke-SSHCommand -SSHSession $s -Command "cd ~/enet-validator && ~/.cargo/bin/cargo build --release 2>&1 | tail -2" -TimeOut 300
Write-Host ($r.Output -join "`n")
Write-Host "--- restart butler (NO relay - local G) ---"
$r = Invoke-SSHCommand -SSHSession $s -Command "pkill -f 'enet-validator device'; sleep 1; cd ~/enet-validator && nohup ./target/release/enet-validator device 192.168.58.7 00161cf12e88 86400 > ~/butler.log 2>&1 & sleep 2; pgrep -af 'enet-validator device' | head -1"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,39 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH 连接失败" }
$files = @(
@("C:\Users\tonycao\work\light\enet-validator\src\tests.rs", "~/enet-validator/src/tests.rs"),
@("C:\Users\tonycao\work\light\enet-validator\src\enet.rs", "~/enet-validator/src/enet.rs"),
@("C:\Users\tonycao\work\light\enet-validator\samples\rdm_setack_68.hex", "~/enet-validator/samples/rdm_setack_68.hex")
)
foreach ($f in $files) {
$src = $f[0]; $dst = $f[1]
$b64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($src))
$chunkSize = 6000
$chunks = [Math]::Ceiling($b64.Length / $chunkSize)
$r = Invoke-SSHCommand -SSHSession $s -Command "rm -f /tmp/up.b64 && touch /tmp/up.b64"
for ($i = 0; $i -lt $chunks; $i++) {
$part = $b64.Substring($i * $chunkSize, [Math]::Min($chunkSize, $b64.Length - $i * $chunkSize))
$r = Invoke-SSHCommand -SSHSession $s -Command "echo '$part' >> /tmp/up.b64"
}
$r = Invoke-SSHCommand -SSHSession $s -Command "base64 -d /tmp/up.b64 > $dst && md5sum $dst"
$localMd5 = (Get-FileHash $src -Algorithm MD5).Hash.ToLower()
$remoteMd5 = ($r.Output -join "").Split(" ")[0]
$ok = if ($localMd5 -eq $remoteMd5) { "OK" } else { "MISMATCH!" }
Write-Host ("{0} -> {1}: {2} ({3})" -f (Split-Path $src -Leaf), $dst, $ok, $localMd5)
}
Write-Host "--- .7 编译 ---"
$r = Invoke-SSHCommand -SSHSession $s -Command "cd ~/enet-validator && ~/.cargo/bin/cargo build --release 2>&1 | tail -3" -TimeOut 300
Write-Host ($r.Output -join "`n")
Write-Host "--- 重启 butler ---"
$r = Invoke-SSHCommand -SSHSession $s -Command "bash ~/run_butler.sh device 2>&1 | tail -5; sleep 2; pgrep -af enet-validator | head -3"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,31 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$src = "C:\Users\tonycao\work\light\enet-validator\src\tests.rs"
$b64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($src))
$chunkSize = 6000
$chunks = [Math]::Ceiling($b64.Length / $chunkSize)
$r = Invoke-SSHCommand -SSHSession $s -Command "rm -f /tmp/up.b64 && touch /tmp/up.b64"
for ($i = 0; $i -lt $chunks; $i++) {
$part = $b64.Substring($i * $chunkSize, [Math]::Min($chunkSize, $b64.Length - $i * $chunkSize))
$r = Invoke-SSHCommand -SSHSession $s -Command "echo '$part' >> /tmp/up.b64"
}
$r = Invoke-SSHCommand -SSHSession $s -Command "base64 -d /tmp/up.b64 > ~/enet-validator/src/tests.rs && md5sum ~/enet-validator/src/tests.rs"
$localMd5 = (Get-FileHash $src -Algorithm MD5).Hash.ToLower()
$remoteMd5 = ($r.Output -join "").Split(" ")[0]
Write-Host ("tests.rs sync: {0} ({1})" -f $(if ($localMd5 -eq $remoteMd5) { "OK" } else { "MISMATCH!" }), $localMd5)
Write-Host "--- build on .7 ---"
$r = Invoke-SSHCommand -SSHSession $s -Command "cd ~/enet-validator && ~/.cargo/bin/cargo build --release 2>&1 | tail -2" -TimeOut 300
Write-Host ($r.Output -join "`n")
Write-Host "--- restart butler ---"
$r = Invoke-SSHCommand -SSHSession $s -Command "bash ~/run_butler.sh device 2>&1 | tail -3; sleep 2; pgrep -af 'enet-validator device' | head -2"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,29 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH 连接失败" }
$src = "C:\Users\tonycao\work\light\enet-validator\src\tests.rs"
$b64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($src))
Write-Host ("本地文件 {0} 字节, base64 {1} 字符" -f (Get-Item $src).Length, $b64.Length)
# 分块传输
$chunkSize = 6000
$chunks = [Math]::Ceiling($b64.Length / $chunkSize)
$r = Invoke-SSHCommand -SSHSession $s -Command "rm -f /tmp/tests.b64 && touch /tmp/tests.b64 && echo OK"
for ($i = 0; $i -lt $chunks; $i++) {
$start = $i * $chunkSize
$len = [Math]::Min($chunkSize, $b64.Length - $start)
$part = $b64.Substring($start, $len)
$r = Invoke-SSHCommand -SSHSession $s -Command "echo '$part' >> /tmp/tests.b64 && wc -c < /tmp/tests.b64"
if (($i % 3) -eq 0) { Write-Host (" chunk {0}/{1}: {2}" -f ($i+1), $chunks, ($r.Output -join "")) }
}
$r = Invoke-SSHCommand -SSHSession $s -Command "base64 -d /tmp/tests.b64 > ~/enet-validator/src/tests.rs && wc -c < ~/enet-validator/src/tests.rs && md5sum ~/enet-validator/src/tests.rs"
Write-Host ("解码: " + ($r.Output -join " | "))
$localMd5 = (Get-FileHash $src -Algorithm MD5).Hash.ToLower()
Write-Host ("本地MD5: $localMd5")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,49 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\sym_startup.pcap"
$ts = "C:\Program Files\Wireshark\tshark.exe"
$tpl = (Get-Content "C:\Users\tonycao\work\light\enet-validator\samples\announce_686.hex" -Raw).Trim() -replace '\s',''
# 取两帧 live announce (不同时刻)
$anns = @(& $ts -r $cap -Y "udp.length==652" -T fields -e frame.time_relative -e udp.payload 2>$null | Select-Object -First 2)
$h0 = ($anns[0] -split "`t")[1]; $t0 = ($anns[0] -split "`t")[0]
$h1 = ($anns[1] -split "`t")[1]; $t1 = ($anns[1] -split "`t")[0]
Write-Host ("template len=" + $tpl.Length/2 + " live len=" + $h0.Length/2)
Write-Host ""
Write-Host "=== template vs liveA(t=$t0) 差异区域 ==="
$n = [Math]::Min($tpl.Length, $h0.Length)
$regions = @()
for ($i = 0; $i -lt $n; $i += 2) {
if ($tpl.Substring($i,2) -ne $h0.Substring($i,2)) {
$off = $i/2
if ($regions.Count -eq 0 -or ($off - $regions[$regions.Count-1][1]) -gt 4) {
[void]$regions.Add(@($off, $off))
} else { $regions[$regions.Count-1][1] = $off }
}
}
foreach ($r in $regions) {
$s = [Math]::Max(0, $r[0]-3); $e = [Math]::Min($tpl.Length/2-1, $r[1]+3)
$st=''; $sl=''; $sr=''
for ($i=$s; $i -le $e; $i++) {
$st += $tpl.Substring(2*$i,2)
$sl += $h0.Substring(2*$i,2)
if ($i -le $h1.Length/2-1) { $sr += $h1.Substring(2*$i,2) } else { $sr += '??' }
}
Write-Host ("0x{0:x4}-0x{1:x4}: tpl={2} liveA={3} liveB={4}" -f $s, $e, $st, $sl, $sr)
}
# liveA vs liveB (两帧实时宣告间的差异 = 每次宣告都变的字段)
Write-Host ""
Write-Host "=== liveA(t=$t0) vs liveB(t=$t1) 差异 (每次宣告变化的字段) ==="
$d2 = @()
for ($i = 0; $i -lt $n; $i += 2) {
if ($h0.Substring($i,2) -ne $h1.Substring($i,2)) {
$off = $i/2
if ($d2.Count -eq 0 -or ($off - $d2[$d2.Count-1][1]) -gt 4) { [void]$d2.Add(@($off,$off)) } else { $d2[$d2.Count-1][1] = $off }
}
}
foreach ($r in $d2) {
$s = [Math]::Max(0, $r[0]-3); $e = [Math]::Min($h0.Length/2-1, $r[1]+3)
$sl=''; $sr=''
for ($i=$s; $i -le $e; $i++) { $sl += $h0.Substring(2*$i,2); $sr += $h1.Substring(2*$i,2) }
Write-Host ("0x{0:x4}-0x{1:x4}: A={2} B={3}" -f $s, $e, $sl, $sr)
}
Set-Content -Encoding ascii "$env:TEMP\opencode\announce_live.hex" $h0
Write-Host "saved: announce_live.hex"

View File

@ -0,0 +1,24 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$tpl = (Get-Content "C:\Users\tonycao\work\light\enet-validator\samples\announce_686.hex" -Raw).Trim() -replace '\s',''
$h0 = (Get-Content "$env:TEMP\opencode\announce_live.hex" -Raw).Trim() -replace '\s',''
Write-Host ("template len=" + $tpl.Length/2 + " live len=" + $h0.Length/2)
$diffs = New-Object 'System.Collections.Generic.List[int]'
$n = [Math]::Min($tpl.Length, $h0.Length)
for ($i = 0; $i -lt $n; $i += 2) {
if ($tpl.Substring($i,2) -ne $h0.Substring($i,2)) { $diffs.Add([int]($i/2)) }
}
Write-Host ("differing bytes: " + $diffs.Count)
# 分组连续区域
$groups = New-Object 'System.Collections.Generic.List[object]'
foreach ($d in $diffs) {
if ($groups.Count -gt 0 -and ($d - $groups[$groups.Count-1][1]) -le 6) { $groups[$groups.Count-1][1] = $d }
else { $null = $groups.Add([pscustomobject]@{S=$d; E=$d}) }
}
foreach ($g in $groups) {
$s = [Math]::Max(0, $g.S-4); $e = [Math]::Min($n/2-1, $g.E+4)
$st=''; $sl=''
for ($i=$s; $i -le $e; $i++) { $st += $tpl.Substring(2*$i,2); $sl += $h0.Substring(2*$i,2) }
Write-Host ("0x{0:x4}-0x{1:x4}:" -f $s, $e)
Write-Host (" tpl = " + $st)
Write-Host (" live= " + $sl)
}

View File

@ -0,0 +1,31 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\sym_claim2.pcap"
$ts = "C:\Program Files\Wireshark\tshark.exe"
# 提取两个不同时刻的 announce (644B payload = 652 udplen) hex
$anns = @(& $ts -r $cap -Y "udp.length==652" -T fields -e frame.time_relative -e udp.payload 2>$null | Select-Object -First 2)
Write-Host ("announce count sample: " + $anns.Count)
if ($anns.Count -ge 2) {
$t0 = ($anns[0] -split "`t")[0]; $h0 = ($anns[0] -split "`t")[1]
$t1 = ($anns[1] -split "`t")[0]; $h1 = ($anns[1] -split "`t")[1]
Write-Host ("A t=" + $t0 + " len=" + ($h0.Length/2))
Write-Host ("B t=" + $t1 + " len=" + ($h1.Length/2))
# 找差异字节
$diffs = @()
$n = [Math]::Min($h0.Length, $h1.Length)
for ($i = 0; $i -lt $n; $i += 2) {
if ($h0.Substring($i,2) -ne $h1.Substring($i,2)) { $diffs += ($i/2) }
}
Write-Host ("differing byte offsets: " + (($diffs | ForEach-Object { '0x{0:x}' -f $_ }) -join ' '))
# 打印模板与实时帧在这些区域的内容
$tpl = (Get-Content "C:\Users\tonycao\work\light\enet-validator\samples\announce_686.hex" -Raw).Trim() -replace '\s',''
Write-Host ""
foreach ($d in $diffs) {
$s = [Math]::Max(0, $d - 4); $e = [Math]::Min($h0.Length/2 - 1, $d + 7)
$seg0 = ''; $seg1 = ''; $segt = ''
for ($i = $s; $i -le $e; $i++) { $seg0 += $h0.Substring(2*$i,2); $seg1 += $h1.Substring(2*$i,2); if (2*$i+2 -le $tpl.Length) { $segt += $tpl.Substring(2*$i,2) } else { $segt += '??????' } }
Write-Host ('0x{0:x}: liveA={1} liveB={2} tpl={3}' -f $d, $seg0, $seg1, $segt)
}
# 存下完整实时 announce 供修补用
Set-Content -Encoding ascii "$env:TEMP\opencode\announce_live.hex" $h0
Write-Host "saved live announce to announce_live.hex"
}

View File

@ -0,0 +1,20 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\sym_claim2.pcap"
$ts = "C:\Program Files\Wireshark\tshark.exe"
Write-Host "=== ALL host-sent frames before claim (t<354.8): dst x udp.len x count + first/last t ==="
$all = @(& $ts -r $cap -Y "ip.src==192.168.123.113 and frame.time_relative<354.5" -T fields -e frame.time_relative -e ip.dst -e udp.length 2>$null)
$groups = @{}
foreach ($l in $all) { $p = $l -split "`t"; $k = $p[1] + " len=" + $p[2]; if (-not $groups.ContainsKey($k)) { $groups[$k] = New-Object System.Collections.ArrayList }; [void]$groups[$k].Add([double]$p[0]) }
foreach ($k in ($groups.Keys | Sort-Object)) {
$arr = $groups[$k]
Write-Host ("{0,-30} n={1,-5} first={2:N1}s last={3:N1}s" -f $k, $arr.Count, $arr[0], $arr[$arr.Count-1])
}
Write-Host ""
Write-Host "=== ALL device-sent frames before claim (t<354.8) ==="
$dev = @(& $ts -r $cap -Y "ip.src==192.168.123.1 and frame.time_relative<354.5" -T fields -e frame.time_relative -e ip.dst -e udp.length 2>$null)
$g2 = @{}
foreach ($l in $dev) { $p = $l -split "`t"; $k = $p[1] + " len=" + $p[2]; if (-not $g2.ContainsKey($k)) { $g2[$k] = 0 }; $g2[$k]++ }
foreach ($k in ($g2.Keys | Sort-Object)) { Write-Host ("{0,-30} n={1}" -f $k, $g2[$k]) }
Write-Host ""
Write-Host "=== EUCE (548) timing before claim ==="
& $ts -r $cap -Y "udp.length==548" -T fields -e frame.time_relative 2>$null | Select-Object -First 20

View File

@ -0,0 +1,13 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\sym_claim2.pcap"
$ts = "C:\Program Files\Wireshark\tshark.exe"
# 抓 ident 和第一个 AECH 质询的完整 hex (frame.time_epoch 精确定位)
Write-Host "=== ident102 (t=354.810) full hex ==="
& $ts -r $cap -Y "udp.length==110 and frame.time_epoch>=1789049091.467 and frame.time_epoch<=1789049091.468" -T fields -e udp.payload 2>$null
Write-Host ""
Write-Host "=== AECH challenge #1 (t=354.824) full hex ==="
& $ts -r $cap -Y "udp.length==560 and frame.time_epoch>=1789049091.481 and frame.time_epoch<=1789049091.482" -T fields -e udp.payload 2>$null
Write-Host ""
Write-Host "=== AERE #1 (t=354.826) 0xC0 前 96B ==="
$h = & $ts -r $cap -Y "udp.length==560 and frame.time_epoch>=1789049091.483 and frame.time_epoch<=1789049091.484" -T fields -e udp.payload 2>$null
if ($h) { $h.Substring(0, [Math]::Min(300, $h.Length)) }

View File

@ -0,0 +1,8 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\sym_claim2.pcap"
$ts = "C:\Program Files\Wireshark\tshark.exe"
Write-Host "=== ident(110) / 47B(55) / AECH-AERE(560) frames ==="
& $ts -r $cap -Y "udp.length==110 or udp.length==55 or udp.length==560" -T fields -e frame.time_relative -e frame.time_epoch -e ip.src -e ip.dst -e udp.length 2>$null
Write-Host ""
Write-Host "=== last discovery(188) + announce(652) before claim (restart boundary) ==="
& $ts -r $cap -Y "udp.length==188 or udp.length==652" -T fields -e frame.time_relative -e ip.src -e ip.dst -e udp.length 2>$null | Select-Object -Last 8

View File

@ -0,0 +1,9 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ts = "C:\Program Files\Wireshark\tshark.exe"
# sub=07 特征: type=02 帧 0x18-0x19 = 07 00。用 payload 前缀过滤: ENet + 02 + ...+ 0700
# udp.payload hex 的 0x18-0x19 字节 = 第 48-51 字符
foreach ($cap in @("$env:TEMP\opencode\sym_claim2.pcap", "$env:TEMP\opencode\cold2.pcap", "$env:TEMP\opencode\sym_close.pcap")) {
Write-Host ("=== " + (Split-Path $cap -Leaf) + " : type=02 sub=07 frames ===")
& $ts -r $cap -Y "udp[16:2]==0x0700 and ip.src==192.168.123.113 and udp.length<200" -T fields -e frame.time_relative -e udp.length -e udp.payload 2>$null | ForEach-Object { $p = $_ -split "`t"; "{0,9:N1}s len={1} {2}" -f [double]$p[0], $p[1], $p[2].Substring(0, [Math]::Min(104, $p[2].Length)) } | Select-Object -First 12
Write-Host ""
}

View File

@ -0,0 +1,7 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ts = "C:\Program Files\Wireshark\tshark.exe"
foreach ($cap in @("$env:TEMP\opencode\sym_claim2.pcap", "$env:TEMP\opencode\cold2.pcap", "$env:TEMP\opencode\sym_close.pcap")) {
Write-Host ("=== " + (Split-Path $cap -Leaf) + " : type=02 sub=07 (udp[32:2]==0x0700) host-sent ===")
& $ts -r $cap -Y "udp[32:2]==0x0700 and ip.src==192.168.123.113 and udp.length<300" -T fields -e frame.time_relative -e udp.length -e udp.payload 2>$null | ForEach-Object { $p = $_ -split "`t"; "{0,9:N1}s len={1} {2}" -f [double]$p[0], $p[1], $p[2].Substring(0, [Math]::Min(104, $p[2].Length)) } | Select-Object -First 10
Write-Host ""
}

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$r = Invoke-SSHCommand -SSHSession $s -Command "grep -nE 'AECH|质询|应答|claim' ~/butler.log | tail -n 15; echo ---; wc -l ~/butler.log"
$r.Output
Remove-SSHSession -SSHSession $s | Out-Null

View File

@ -0,0 +1,18 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$u = New-Object Net.Sockets.UdpClient
$u.Client.SetSocketOption([Net.Sockets.SocketOptionLevel]::Socket, [Net.Sockets.SocketOptionName]::ReuseAddress, $true)
$u.Client.Bind((New-Object Net.IPEndPoint([Net.IPAddress]::Parse("192.168.123.113"), 31772)))
$u.Client.ReceiveTimeout = 1000
$sw = [Diagnostics.Stopwatch]::StartNew()
$remote = New-Object Net.IPEndPoint([Net.IPAddress]::Any, 0)
$last = Get-Date
while ($sw.Elapsed.TotalSeconds -lt 120) {
try {
$d = $u.Receive([ref]$remote)
$tag = ""
if ($d.Length -ge 4) { $tag = [Text.Encoding]::ASCII.GetString($d[0..3]) }
Write-Host ("{0,7:N1}s len={1,-5} from={2} magic={3} 0x18-0x19={4:x2}{5:x2}" -f $sw.Elapsed.TotalSeconds, $d.Length, $remote.Address, $tag, $(if($d.Length -gt 0x19){$d[0x19]}else{0}), $(if($d.Length -gt 0x18){$d[0x18]}else{0}))
$last = Get-Date
} catch {}
}
Write-Host "监听结束 (120s)"

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\sym_claim2.pcap"
$ts = "C:\Program Files\Wireshark\tshark.exe"
Write-Host "=== len=60 payload hex (mystery pre-claim frames) ==="
& $ts -r $cap -Y "udp.length==60 and ip.src==192.168.123.113" -T fields -e frame.time_relative -e udp.srcport -e udp.payload 2>$null
Write-Host ""
Write-Host "=== source ports of host frames (pre-claim) ==="
& $ts -r $cap -Y "ip.src==192.168.123.113 and frame.time_relative<354.8" -T fields -e udp.srcport 2>$null | Group-Object | Select-Object Count, Name
Write-Host ""
Write-Host "=== source port of ident + AECH ==="
& $ts -r $cap -Y "(udp.length==110 or udp.length==560) and frame.time_relative>=354" -T fields -e frame.time_relative -e udp.srcport 2>$null | Select-Object -First 5

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$r = Invoke-SSHCommand -SSHSession $s -Command "awk '/5767.0s/,0' ~/butler.log | grep -vE 'DMX #|发现请求' | head -n 30"
$r.Output
Remove-SSHSession -SSHSession $s | Out-Null

View File

@ -0,0 +1,20 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$exe = "C:\Users\tonycao\work\light\enet-validator\target\release\enet-validator.exe"
$log = "$env:TEMP\opencode\probe_loop.log"
Remove-Item $log -ErrorAction SilentlyContinue
for ($i = 0; $i -lt 14; $i++) {
$ts = Get-Date -Format "HH:mm:ss"
$env:HOSTDEMO_MODE = "claimonly"
$out = & $exe hostdemo 192.168.123.1 0 2>&1 | Out-String
Add-Content -Encoding utf8 $log ("=== " + $ts + " attempt " + $i + " ===`n" + $out)
if ($out -match "CLAIM_PROBE.*PASS") {
Add-Content -Encoding utf8 $log ((Get-Date -Format "HH:mm:ss") + " CLAIM UNLOCKED - full hostdemo starting")
Remove-Item Env:\HOSTDEMO_MODE -ErrorAction SilentlyContinue
$out2 = & $exe hostdemo 192.168.123.1 30 2>&1 | Out-String
Add-Content -Encoding utf8 $log $out2
Add-Content -Encoding utf8 $log "=== full hostdemo done ==="
break
}
Start-Sleep -Seconds 300
}
Add-Content -Encoding utf8 $log "probe loop exit"

View File

@ -0,0 +1,20 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$exe = "C:\Users\tonycao\work\light\enet-validator\target\release\enet-validator.exe"
$log = "$env:TEMP\opencode\probe_loop2.log"
Remove-Item $log -ErrorAction SilentlyContinue
for ($i = 0; $i -lt 45; $i++) {
$ts = Get-Date -Format "HH:mm:ss"
$env:HOSTDEMO_MODE = "claimonly"
$out = & $exe hostdemo 192.168.123.1 0 2>&1 | Out-String
Add-Content -Encoding utf8 $log ("=== " + $ts + " attempt " + $i + " ===`n" + $out)
if ($out -match "CLAIM_PROBE.*PASS") {
Add-Content -Encoding utf8 $log ((Get-Date -Format "HH:mm:ss") + " CLAIM UNLOCKED - full hostdemo starting (RDM discovery + 60s DMX)")
Remove-Item Env:\HOSTDEMO_MODE -ErrorAction SilentlyContinue
$out2 = & $exe hostdemo 192.168.123.1 60 2>&1 | Out-String
Add-Content -Encoding utf8 $log $out2
Add-Content -Encoding utf8 $log "=== full hostdemo done ==="
break
}
Start-Sleep -Seconds 240
}
Add-Content -Encoding utf8 $log "probe loop2 exit"

View File

@ -0,0 +1,8 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$r = Invoke-SSHCommand -SessionId $s.SessionId -Command "cd ~/enet-validator && GIT_TERMINAL_PROMPT=0 git push -u origin master 2>&1 | head -10" -TimeOut 120
$r.Output
Remove-SSHSession -SessionId $s.SessionId | Out-Null

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$r = Invoke-SSHCommand -SSHSession $s -Command "grep -vE 'DMX #|发现请求' ~/butler.log | tail -n 15"
$r.Output
Remove-SSHSession -SSHSession $s | Out-Null

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$r = Invoke-SSHCommand -SSHSession $s -Command "grep -c 'RDM' ~/butler.log; grep 'RDM' ~/butler.log | tail -n 12"
$r.Output
Remove-SSHSession -SSHSession $s | Out-Null

View File

@ -0,0 +1,16 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\sym_close.pcap"
$ts = "C:\Program Files\Wireshark\tshark.exe"
Write-Host "=== stats by type (src dst udplen count) ==="
& $ts -r $cap -T fields -e ip.src -e ip.dst -e udp.length 2>$null | Group-Object { $p = $_ -split "`t"; "$($p[0]) -> $($p[1]) len=$($p[2])" } | Sort-Object Count -Descending | Select-Object -First 12 Count, Name | Format-Table -AutoSize
Write-Host "=== DMX stream (udplen=1092) per-5s buckets ==="
$dmx = @(& $ts -r $cap -Y "udp.length==1092" -T fields -e frame.time_relative 2>$null)
Write-Host ("total DMX frames: " + $dmx.Count)
$buckets = @{}
foreach ($t in $dmx) { $b = [int]([double]$t / 5); $buckets[$b] = 1 + [int]($buckets[$b] | ForEach-Object { $_ }) }
foreach ($k in ($buckets.Keys | Sort-Object)) { Write-Host (" t={0,4}-{1,-4}s : {2} frames" -f ($k*5), ($k*5+5), $buckets[$k]) }
if ($dmx.Count -gt 0) { Write-Host (" last DMX at t=" + $dmx[$dmx.Count-1]) }
Write-Host "=== AECH/AERE (udplen=560) ==="
& $ts -r $cap -Y "udp.length==560" -T fields -e frame.time_relative -e ip.src -e ip.dst 2>$null
Write-Host "=== last 15 frames overall ==="
& $ts -r $cap -T fields -e frame.time_relative -e ip.src -e ip.dst -e udp.length 2>$null | Select-Object -Last 15

View File

@ -0,0 +1,16 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\sym_close.pcap"
$ts = "C:\Program Files\Wireshark\tshark.exe"
Write-Host "=== DMX stream (1092) per-10s buckets ==="
$dmx = @(& $ts -r $cap -Y "udp.length==1092" -T fields -e frame.time_relative 2>$null)
Write-Host ("total: " + $dmx.Count)
if ($dmx.Count -gt 0) {
$buckets = @{}
foreach ($t in $dmx) { $b = [int]([double]$t / 10); if ($buckets.ContainsKey($b)) { $buckets[$b]++ } else { $buckets[$b] = 1 } }
foreach ($k in ($buckets.Keys | Sort-Object)) { Write-Host (" t={0,4}-{1,-4}s : {2}" -f ($k*10), ($k*10+10), $buckets[$k]) }
Write-Host (" last DMX t=" + $dmx[$dmx.Count-1])
}
Write-Host "=== last 25 frames overall ==="
& $ts -r $cap -T fields -e frame.time_relative -e ip.src -e ip.dst -e udp.length 2>$null | Select-Object -Last 25
Write-Host "=== any AECH/AERE (560) ==="
& $ts -r $cap -Y "udp.length==560" -T fields -e frame.time_relative -e ip.src 2>$null

View File

@ -0,0 +1,9 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$r = Invoke-SSHCommand -SSHSession $s -Command "bash ~/run_butler.sh device 2>&1 | tail -2; sleep 2; pgrep -af 'enet-validator device' | head -1"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\hd_claim3.pcap"
Remove-Item $cap -ErrorAction SilentlyContinue
$ts = "C:\Program Files\Wireshark\tshark.exe"
$proc = Start-Process -FilePath $ts -ArgumentList "-i `"\Device\NPF_{E7165B1C-BAF6-49CF-9A3D-EF12DBC17329}`" -f `"udp port 31772`" -a duration:420 -w `"$cap`"" -PassThru -WindowStyle Hidden -RedirectStandardError "$env:TEMP\opencode\tshark_err5.txt"
Start-Sleep -Seconds 2
Set-Location "C:\Users\tonycao\work\light\enet-validator"
& .\target\release\enet-validator.exe hostdemo 192.168.123.1 30
Start-Sleep -Seconds 5
Write-Host "`n=== claim window in capture ==="
& $ts -r $cap -Y "udp.length==110 or udp.length==55 or udp.length==560" -T fields -e frame.time_relative -e ip.src -e ip.dst -e udp.length 2>$null | Select-Object -First 12

View File

@ -0,0 +1,4 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-Location "C:\Users\tonycao\work\light\enet-validator"
& .\target\release\enet-validator.exe hostdemo 192.168.123.1 30 2>&1 | Out-File -Encoding utf8 "$env:TEMP\opencode\hd3_run.log"
Write-Host "DONE"

View File

@ -0,0 +1,13 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$r = Invoke-SSHCommand -SSHSession $s -Command "cat ~/run_butler_local.sh"
$r.Output
$r = Invoke-SSHCommand -SSHSession $s -Command "nohup ~/run_butler_local.sh > /dev/null 2>&1 & sleep 3; pgrep -af enet; echo ---; tail -n 4 ~/butler.log"
$r.Output
Remove-SSHSession -SSHSession $s | Out-Null

View File

@ -0,0 +1,23 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$cmd = @'
cat > ~/run_butler_local.sh << 'EOF'
#!/bin/bash
pkill -f "release/enet-validator" 2>/dev/null
sleep 1
cd /home/tonycao/enet-validator
nohup ./target/release/enet-validator device 192.168.58.7 00161cf12e88 86400 > /home/tonycao/butler.log 2>&1 &
disown
sleep 4
echo "PID=$(pgrep -f 'release/enet-validator' | head -1)"
EOF
chmod +x ~/run_butler_local.sh
bash ~/run_butler_local.sh
'@
$r = Invoke-SSHCommand -SSHSession $s -Command $cmd
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,52 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
function Copy-Up($local, $destDir) {
Set-SCPItem -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -Path $local -Destination $destDir
}
# 1) docs
Invoke-Command -ScriptBlock { } | Out-Null
$s0 = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
Invoke-SSHCommand -SessionId $s0.SessionId -Command "mkdir -p ~/enet-validator/docs ~/enet-validator/scripts/session ~/enet-validator/tools" | Out-Null
Remove-SSHSession -SessionId $s0.SessionId | Out-Null
Copy-Up "C:\Users\tonycao\work\light\enet_protocol.md" "~/enet-validator/docs/"
Copy-Up "C:\Users\tonycao\work\light\rust_verification_plan.md" "~/enet-validator/docs/"
Write-Host "sync docs"
# 2) scripts (root)
foreach ($f in @("enet_lib.ps1","Verify-EnetProtocol.ps1","Verify-Rdm.ps1")) {
Copy-Up "C:\Users\tonycao\work\light\$f" "~/enet-validator/scripts/"
}
Write-Host "sync root scripts"
# 3) scripts/session (全部 75 个会话脚本)
$sess = Get-ChildItem "$env:TEMP\opencode\*.ps1"
foreach ($f in $sess) {
Copy-Up $f.FullName "~/enet-validator/scripts/session/"
}
Write-Host ("sync session scripts: " + $sess.Count)
# 4) tools (.cs)
foreach ($f in @("IlDump.cs","GetTable.cs","Scan.cs")) {
Copy-Up "$env:TEMP\opencode\$f" "~/enet-validator/tools/"
}
Write-Host "sync tools"
# 5) README
Copy-Up "C:\Users\tonycao\work\light\enet-validator\README.md" "~/enet-validator/"
Write-Host "sync README"
# 6) commit + push
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$cmds = @'
cd ~/enet-validator && git add -A && git commit -m "docs+scripts: enet_protocol.md, verification plan, PS verify suite, session analysis scripts, DLL tools, README" | tail -2; git push origin master 2>&1 | tail -3; echo ---; git log --oneline | head -3
'@
$r = Invoke-SSHCommand -SessionId $s.SessionId -Command $cmds -TimeOut 180
$r.Output
if ($r.Error) { Write-Host "STDERR:"; $r.Error | Select-Object -First 6 }
Remove-SSHSession -SessionId $s.SessionId | Out-Null

View File

@ -0,0 +1,42 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$sid = $s.SessionId
# 1) 同步源码
$srcFiles = @("main.rs","enet.rs","tests.rs","aech.rs","hostdemo.rs","dmxhash.rs")
foreach ($f in $srcFiles) {
Set-SCPItem -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -Path "C:\Users\tonycao\work\light\enet-validator\src\$f" -Destination "~/enet-validator/src/"
}
Set-SCPItem -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -Path "C:\Users\tonycao\work\light\enet-validator\Cargo.toml" -Destination "~/enet-validator/"
Set-SCPItem -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -Path "C:\Users\tonycao\work\light\enet-validator\Cargo.lock" -Destination "~/enet-validator/"
Set-SCPItem -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -Path "C:\Users\tonycao\work\light\enet-validator\.gitignore" -Destination "~/enet-validator/"
# 2) 同步 samples
Invoke-SSHCommand -SessionId $sid -Command "mkdir -p ~/enet-validator/samples" | Out-Null
Get-ChildItem "C:\Users\tonycao\work\light\enet-validator\samples\*.hex" | ForEach-Object {
Set-SCPItem -SessionId $sid -Path $_.FullName -Destination "~/enet-validator/samples/"
}
Write-Host "sync done"
# 3) git init + commit + push
$cmds = @"
cd ~/enet-validator
git init -b master 2>/dev/null || true
git config user.email "tonycao@local" || true
git config user.name "tonycao" || true
git add -A
git commit -m "enet-validator: e:net protocol reverse + host-side emulation (AECH/DMX/RDM, hostdemo)" || true
git remote remove origin 2>/dev/null || true
git remote add origin http://www.kanghexin.work:3000/tonycao/enet-validator.git
git log --oneline | head -3
git remote -v
"@
$r = Invoke-SSHCommand -SessionId $sid -Command $cmds -TimeOut 60
$r.Output
if ($r.Error) { $r.Error | Select-Object -First 5 }
Remove-SSHSession -SessionId $sid | Out-Null

View File

@ -0,0 +1,36 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
function Copy-Up($local, $destDir) {
Set-SCPItem -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -Path $local -Destination $destDir
}
# 1) 源码
foreach ($f in @("main.rs","enet.rs","tests.rs","aech.rs","hostdemo.rs","dmxhash.rs")) {
Copy-Up "C:\Users\tonycao\work\light\enet-validator\src\$f" "~/enet-validator/src/"
Write-Host "sync src/$f"
}
Copy-Up "C:\Users\tonycao\work\light\enet-validator\Cargo.toml" "~/enet-validator/"
Copy-Up "C:\Users\tonycao\work\light\enet-validator\Cargo.lock" "~/enet-validator/"
Copy-Up "C:\Users\tonycao\work\light\enet-validator\.gitignore" "~/enet-validator/"
Write-Host "sync root done"
# 2) samples
$hexes = Get-ChildItem "C:\Users\tonycao\work\light\enet-validator\samples\*.hex"
foreach ($h in $hexes) {
Copy-Up $h.FullName "~/enet-validator/samples/"
}
Write-Host ("sync samples done: " + $hexes.Count + " files")
# 3) git init + commit
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
$cmds = @'
cd ~/enet-validator && git init -b master 2>/dev/null; git config user.email "tonycao@local"; git config user.name "tonycao"; git add -A; git commit -m "enet-validator: e:net protocol reverse + host-side emulation (AECH claim / encrypted DMX / RDM discovery, hostdemo)" | tail -2; git remote remove origin 2>/dev/null; git remote add origin http://www.kanghexin.work:3000/tonycao/enet-validator.git; echo ---; git log --oneline 2>/dev/null | head -3; git remote -v
'@
$r = Invoke-SSHCommand -SessionId $s.SessionId -Command $cmds -TimeOut 120
$r.Output
if ($r.Error) { Write-Host "STDERR:"; $r.Error | Select-Object -First 6 }
Remove-SSHSession -SessionId $s.SessionId | Out-Null

View File

@ -0,0 +1,47 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
# Craft AECH with fresh random nonce -> .7 (local G) -> verify resp32 == G(nonce)
$TABLE = @(0x1008de00,0xe3b77ff6,0x31183e1c,0x90fb53c8,0xff10ba5c,0x030bc4b6,0xe22935c8,0x10e8ede1,
0x55f409a0,0x3eb0b9a3,0xf7333308,0xedf07810,0x8abf4078,0x60ec0545,0x1f4645c8,0x54094148,
0x04c0f500,0xfabe50bb,0xf3f3e54c,0x6c2a02cb,0xa2546e60,0x4f6ae10e,0x51dfe8d8,0x2eedb0ec,
0x74006928,0x48fade70,0xe5346e9c,0x1c3fe0bc,0xf6700700,0x6b7f1ba0,0xa1143cf4,0x3be8426c)
function G([uint32]$x) {
$big = [int64]$x * 13
$m = [uint32]($big % 4294967296)
$idx = [int]((($x % 32) + 1 + ($m % 30)) % 32)
return $x -bxor $TABLE[$idx]
}
$raw = [IO.File]::ReadAllBytes("$env:TEMP\opencode\cold2.tsv")
$text = if ($raw[0] -eq 0xFF -and $raw[1] -eq 0xFE) { [Text.Encoding]::Unicode.GetString($raw) } else { [Text.Encoding]::UTF8.GetString($raw) }
$hex = $null
foreach ($l in ($text -split "`r?`n")) {
$p = $l -split "`t"
if ($p.Count -lt 5 -or -not $p[4]) { continue }
$h = $p[4] -replace " ", ""
if ($h.Length -eq 1104 -and $h.Substring(0x28 * 2, 8) -eq "41454348") { $hex = $h; break }
}
if (-not $hex) { throw "no AECH template" }
$n = $hex.Length / 2
$req = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $req[$i] = [Convert]::ToByte($hex.Substring(2 * $i, 2), 16) }
$rnd = New-Object Random
$udp = New-Object System.Net.Sockets.UdpClient
$udp.Client.ReceiveTimeout = 3000
$ep = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Parse("192.168.58.7"), 31772)
$pass = 0
for ($t = 0; $t -lt 5; $t++) {
$nonce = [uint32]($rnd.NextDouble() * 4294967295)
$nb = [BitConverter]::GetBytes($nonce)
[Array]::Copy($nb, 0, $req, 0x88, 4)
$expect = G $nonce
[void]$udp.Send($req, $req.Length, $ep)
try {
$rep = $udp.Receive([ref]$ep)
$got = [BitConverter]::ToUInt32($rep, 0xC0)
$ok = $got -eq $expect
if ($ok) { $pass++ }
Write-Host ("#{0} nonce={1:x8} expect={2:x8} got={3:x8} {4} len={5}" -f ($t+1), $nonce, $expect, $got, $(if ($ok) {"PASS"} else {"FAIL"}), $rep.Length)
} catch { Write-Host ("#{0} no reply" -f ($t+1)) }
}
Write-Host "== $pass/5 PASS =="
$udp.Close()

View File

@ -0,0 +1,14 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
# restart butler with NEW MAC (new device identity), NO relay -> host must re-claim, local AERE
$r = Invoke-SSHCommand -SSHSession $s -Command "pkill -f 'enet-validator device'; sleep 1; cd ~/enet-validator && nohup ./target/release/enet-validator device 192.168.58.7 00161cf12e89 300 > ~/butler.log 2>&1 & sleep 2; pgrep -af 'enet-validator device' | head -1"
Write-Host ($r.Output -join "`n")
Write-Host "--- waiting 90s for host auto-claim of new device ---"
Start-Sleep -Seconds 90
$r = Invoke-SSHCommand -SSHSession $s -Command "echo AECH次数:; grep -c 'AECH' ~/butler.log; echo 注册次数:; grep -c 'sub=01' ~/butler.log; echo DMX行数:; grep -c 'DMX #' ~/butler.log; echo '---'; grep -E 'AECH|注册|DMX #1 ' ~/butler.log | head -10"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,14 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
# restart butler WITHOUT relay arg (local AERE answers)
$r = Invoke-SSHCommand -SSHSession $s -Command "pkill -f 'enet-validator device'; sleep 1; cd ~/enet-validator && nohup ./target/release/enet-validator device 192.168.58.7 00161cf12e88 300 > ~/butler.log 2>&1 & sleep 2; pgrep -af 'enet-validator device'"
Write-Host ($r.Output -join "`n")
Write-Host "--- waiting 75s for host re-claim ---"
Start-Sleep -Seconds 75
$r = Invoke-SSHCommand -SSHSession $s -Command "grep -cE 'AECH' ~/butler.log; echo '---'; grep -E 'AECH|DMX #' ~/butler.log | head -12"
Write-Host ($r.Output -join "`n")
Remove-SSHSession $s | Out-Null

View File

@ -0,0 +1,52 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\set123b.tsv")
$hex = $null
foreach ($l in $lines) {
$p = $l -split "`t"
if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.113") { continue }
$h = $p[4] -replace " ", ""
if ($h.Length -ne 2168) { continue }
if ($h.Substring(0x20 * 2, 4) -ne "0200") { continue } # subtype event
if ($h.Substring(0x3C * 2, 2) -ne "01") { continue } # SubSC=01
if ($h.Substring(0x3D * 2, 2) -ne "1a") { continue } # msgLen=26
if ($h.Substring(0x4F * 2, 2) -ne "30") { continue }
if ($h.Substring(0x30 * 2, 2) -ne "00") { continue }
$hex = $h; break
}
if (-not $hex) { throw "no SET request frame found" }
$n = $hex.Length / 2
$req = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $req[$i] = [Convert]::ToByte($hex.Substring(2 * $i, 2), 16) }
$uidS = ($req[0x3E..0x43] | ForEach-Object { $_.ToString("x2") }) -join ""
$pidS = $req[0x50].ToString("x2") + $req[0x51].ToString("x2")
Write-Host "SET req: ${n}B UID=$uidS PID=$pidS"
$udp = New-Object System.Net.Sockets.UdpClient
$udp.Client.ReceiveTimeout = 3000
$ep = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Parse("192.168.58.7"), 31772)
$sw = [Diagnostics.Stopwatch]::StartNew()
$sent = $udp.Send($req, $req.Length, $ep)
Write-Host "sent $sent bytes from $($udp.Client.LocalEndPoint)"
while ($true) {
try {
$rep = $udp.Receive([ref]$ep)
if ($rep.Length -lt 0x44) {
Write-Host ("* reply {0}B latency {1}ms (42B telemetry)" -f $rep.Length, $sw.ElapsedMilliseconds)
continue
}
$srcS = ($rep[0x33..0x38] | ForEach-Object { $_.ToString("x2") }) -join ""
Write-Host ("* reply {0}B latency {1}ms srcUID={2} TN={3:x2} RT={4:x2} CC={5:x2} PID={6:x2}{7:x2} PDL={8}" -f `
$rep.Length, $sw.ElapsedMilliseconds, $srcS, $rep[0x39], $rep[0x3A], $rep[0x3E], $rep[0x3F], $rep[0x40], $rep[0x41])
$mlen = [int]$rep[0x2C]; $sum = 0
for ($i = 0x2A; $i -lt 0x2A + $mlen; $i++) { $sum += $rep[$i] }
$ckv = ([int]$rep[0x2A + $mlen] -shl 8) -bor $rep[0x2A + $mlen + 1]
$v = if (($sum -band 0xFFFF) -eq $ckv) { "OK" } else { "FAIL" }
Write-Host (" checksum calc={0:x4} frame={1:x4} {2}" -f ($sum -band 0xFFFF), $ckv, $v)
break
} catch {
Write-Host "no reply within 3s"
break
}
}
$udp.Close()

View File

@ -0,0 +1,60 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
# E2E: replay SET frame patched to virtual fixture 130 (addr 200), then GET verify
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\set123b.tsv")
$hex = $null
foreach ($l in $lines) {
$p = $l -split "`t"
if ($p.Count -lt 5 -or -not $p[4]) { continue }
if ($p[1] -ne "192.168.123.113") { continue }
$h = $p[4] -replace " ", ""
if ($h.Length -ne 2168) { continue }
if ($h.Substring(0x20 * 2, 4) -ne "0200") { continue }
if ($h.Substring(0x3C * 2, 2) -ne "01") { continue }
if ($h.Substring(0x3D * 2, 2) -ne "1a") { continue }
if ($h.Substring(0x4F * 2, 2) -ne "30") { continue }
if ($h.Substring(0x30 * 2, 2) -ne "00") { continue }
$hex = $h; break
}
if (-not $hex) { throw "no SET frame" }
$n = $hex.Length / 2
$req = New-Object byte[] $n
for ($i = 0; $i -lt $n; $i++) { $req[$i] = [Convert]::ToByte($hex.Substring(2 * $i, 2), 16) }
# patch: dest UID @0x3E = 010734670130, PD @0x53 = 00 C8 (addr 200)
$vuid = [byte[]](0x01, 0x07, 0x34, 0x67, 0x01, 0x30)
[Array]::Copy($vuid, 0, $req, 0x3E, 6)
$req[0x53] = 0x00; $req[0x54] = 0xC8
$udp = New-Object System.Net.Sockets.UdpClient
$udp.Client.ReceiveTimeout = 3000
$ep = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Parse("192.168.58.7"), 31772)
function Send-Recv($frame, $label) {
$sw = [Diagnostics.Stopwatch]::StartNew()
[void]$udp.Send($frame, $frame.Length, $ep)
while ($true) {
try {
$rep = $udp.Receive([ref]$ep)
if ($rep.Length -lt 0x44) { continue } # skip 42B telemetry
$srcS = ($rep[0x33..0x38] | ForEach-Object { $_.ToString("x2") }) -join ""
$pdl = [int]$rep[0x41]
$pdS = ""
if ($pdl -gt 0 -and $rep.Length -ge 0x42 + $pdl) { $pdS = ($rep[0x42..(0x41 + $pdl)] | ForEach-Object { $_.ToString("x2") }) -join "" }
$mlen = [int]$rep[0x2C]; $sum = 0
for ($i = 0x2A; $i -lt 0x2A + $mlen; $i++) { $sum += $rep[$i] }
$ckv = ([int]$rep[0x2A + $mlen] -shl 8) -bor $rep[0x2A + $mlen + 1]
$v = if (($sum -band 0xFFFF) -eq $ckv) { "OK" } else { "FAIL" }
Write-Host ("{0}: {1}B {2}ms srcUID={3} CC={4:x2} PID={5:x2}{6:x2} PDL={7} PD={8} ck={9}" -f `
$label, $rep.Length, $sw.ElapsedMilliseconds, $srcS, $rep[0x3E], $rep[0x3F], $rep[0x40], $pdl, $pdS, $v)
return
} catch { Write-Host "${label}: no reply"; return }
}
}
Send-Recv $req "SET 130 addr=200"
Start-Sleep -Milliseconds 100
# GET: same frame, msgLen=0x18, CC=0x20, PDL=0
$get = [byte[]]$req.Clone()
$get[0x3D] = 0x18; $get[0x4F] = 0x20; $get[0x52] = 0x00
$get[0x22] = $get[0x22] + 1 # bump query seq for clarity
Send-Recv $get "GET 130 addr "
$udp.Close()

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$r = Invoke-SSHCommand -SSHSession $s -Command "grep 'aa5501' ~/butler.log | tail -n 5; echo ---; grep -c 'DMX #' ~/butler.log; tail -n 4 ~/butler.log"
$r.Output
Remove-SSHSession -SSHSession $s | Out-Null

View File

@ -0,0 +1,27 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$lines = [IO.File]::ReadAllLines("$env:TEMP\opencode\ss123.tsv")
$n = 0; $guardHit = 0; $msbZero = 0; $msbZero2 = 0; $first = $null
foreach ($ln in $lines) {
$p = $ln -split "`t"
if ($p.Count -lt 5 -or $p[4].Length -lt 100) { continue }
$hex = $p[4] -replace '[^0-9a-fA-F]',''
if ($hex.Length -ne 2168) { continue } # 1084B = 2168 hex chars
$b = New-Object byte[] 1084
for ($i = 0; $i -lt 1084; $i++) { $b[$i] = [Convert]::ToByte($hex.Substring($i*2,2),16) }
if ($b[0x20] -ne 1 -or $b[0x21] -ne 1) { continue } # DMX subtype
$n++
if ($null -eq $first) { $first = $b }
$ch1 = $b[0x3C]; $ch513 = $b[0x23C]
if ($b[0x22] -eq (($ch1 - 1) -bxor 0x70)) { $guardHit++ }
if ($b[0x25] -lt 0x80) { $msbZero++ }
if ($b[0x29] -lt 0x80) { $msbZero2++ }
}
echo "DMX frames: $n"
echo "g1==(CH1-1)^0x70: $guardHit/$n"
echo "byte0x25<0x80 (MSB=0, Random.Next 特征): $msbZero/$n"
echo "byte0x29<0x80: $msbZero2/$n"
if ($first) {
$hdr = ($first[0x18..0x3F] | ForEach-Object { $_.ToString("x2") }) -join ' '
echo "首帧 0x18-0x3F: $hdr"
echo "首帧 CH1-8: $(($first[0x3C..0x43] | ForEach-Object { $_.ToString('x2') }) -join ' ')"
}

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process Bypass -Force
Import-Module Posh-SSH
$sec = ConvertTo-SecureString "0" -AsPlainText -Force
$cred = New-Object PSCredential("tonycao", $sec)
$s = New-SSHSession -ComputerName 192.168.58.7 -Credential $cred -AcceptKey -ConnectionTimeout 15
if (-not $s) { throw "SSH connect failed" }
$r = Invoke-SSHCommand -SSHSession $s -Command "tail -n 400 ~/butler.log | grep -nE 'AECH|认领|注册|claim|register|89B' | tail -n 20; echo ---TAIL---; tail -n 5 ~/butler.log"
$r.Output
Remove-SSHSession -SSHSession $s | Out-Null

View File

@ -0,0 +1,16 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
$cap = "$env:TEMP\opencode\sym_claim.pcap"
Remove-Item $cap -ErrorAction SilentlyContinue
$ts = "C:\Program Files\Wireshark\tshark.exe"
$proc = Start-Process -FilePath $ts -ArgumentList "-i `"\Device\NPF_{E7165B1C-BAF6-49CF-9A3D-EF12DBC17329}`" -f `"udp port 31772`" -a duration:90 -w `"$cap`"" -PassThru -WindowStyle Hidden -RedirectStandardError "$env:TEMP\opencode\tshark_err2.txt"
Start-Sleep -Seconds 2
Start-Process -FilePath "C:\Program Files\ecue\Sympholight 5.5\Sympholight.exe"
Write-Host "SymphoLight started, capturing 90s..."
Start-Sleep -Seconds 88
Write-Host "=== frame type stats (src dst udplen count) ==="
& $ts -r $cap -T fields -e ip.src -e ip.dst -e udp.length 2>$null | Group-Object { $p = $_ -split "`t"; "$($p[0]) -> $($p[1]) len=$($p[2])" } | Sort-Object Count -Descending | Select-Object Count, Name | Format-Table -AutoSize
Write-Host "=== AERE replies (udplen=560, .1->113) times ==="
& $ts -r $cap -Y "ip.src==192.168.123.1 && udp.length==560" -T fields -e frame.time_relative 2>$null
Write-Host "=== DMX stream (udplen=1092, 113->.1) first/last ==="
$dmx = @(& $ts -r $cap -Y "ip.dst==192.168.123.1 && udp.length==1092" -T fields -e frame.time_relative 2>$null)
if ($dmx.Count -gt 0) { Write-Host (" first t=" + $dmx[0] + " last t=" + $dmx[$dmx.Count-1] + " total " + $dmx.Count) } else { Write-Host " no DMX stream" }

View File

@ -0,0 +1,11 @@
[Console]::OutputEncoding = [Text.Encoding]::UTF8
foreach ($u in @("setup.htm","config.htm","reboot.htm","reset.htm","status.htm")) {
try {
$r = Invoke-WebRequest -Uri "http://192.168.123.1/$u" -TimeoutSec 4 -UseBasicParsing
Write-Host ("== " + $u + " (" + $r.StatusCode + ") ==")
[regex]::Matches($r.Content, '<(form|input|select|a)[^>]*>') | ForEach-Object { $_.Value } | Select-Object -First 20
Write-Host ""
} catch {
Write-Host ("== " + $u + " : " + $_.Exception.Message)
}
}

28
tools/GetTable.cs Normal file
View File

@ -0,0 +1,28 @@
using System;
using System.Linq;
using Mono.Cecil;
class GetTable {
static void Main(string[] args) {
string dll = args[0];
string fname = args[1]; // e.g. 0A8F2B10...
var asm = AssemblyDefinition.ReadAssembly(dll);
foreach (var mod in asm.Modules) {
foreach (var t in mod.Types) {
if (!t.FullName.Contains("PrivateImplementationDetails")) continue;
foreach (var f in t.Fields) {
if (f.Name != fname) continue;
byte[] iv = f.InitialValue;
Console.WriteLine("field " + f.Name + " len=" + iv.Length);
for (int i = 0; i + 4 <= iv.Length; i += 4) {
uint v = BitConverter.ToUInt32(iv, i);
Console.WriteLine(" [{0,2}] = 0x{1:x8}", i / 4, v);
}
Console.WriteLine("hex: " + BitConverter.ToString(iv).Replace("-", ""));
return;
}
}
}
Console.WriteLine("NOT FOUND");
}
}

71
tools/IlDump.cs Normal file
View File

@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
class IlDump {
static void Main(string[] args) {
string dll = args[0];
string mode = args.Length > 1 ? args[1] : "types";
string pat = args.Length > 2 ? args[2].ToLower() : "";
var rp = new ReaderParameters { ReadSymbols = false };
var asm = AssemblyDefinition.ReadAssembly(dll, rp);
foreach (var mod in asm.Modules) {
foreach (var t in mod.Types) {
DumpType(t, mode, pat, 0);
}
}
}
static void DumpType(TypeDefinition t, string mode, string pat, int depth) {
bool tmatch = pat.Length == 0 || t.FullName.ToLower().Contains(pat);
if (mode == "types") {
bool anyMethod = t.Methods.Any(m => m.Name.ToLower().Contains(pat));
if (tmatch || anyMethod) {
Console.WriteLine("TYPE " + t.FullName);
foreach (var m in t.Methods) {
if (tmatch || m.Name.ToLower().Contains(pat))
Console.WriteLine(" M " + m.FullName);
}
foreach (var f in t.Fields) {
if (tmatch) Console.WriteLine(" F " + f.FieldType.Name + " " + f.Name);
}
}
} else if (mode == "il") {
foreach (var m in t.Methods) {
if (!(t.FullName.ToLower().Contains(pat) || m.Name.ToLower().Contains(pat))) continue;
if (!m.HasBody) continue;
Console.WriteLine("=== " + m.FullName + " ===");
foreach (var v in m.Body.Variables)
Console.WriteLine(" .local " + v.VariableType.Name + " V_" + v.Index);
foreach (var ins in m.Body.Instructions) {
string op = ins.Operand == null ? "" : " " + OpText(ins.Operand);
Console.WriteLine(string.Format(" IL_{0:x4}: {1}{2}", ins.Offset, ins.OpCode.Name, op));
}
}
} else if (mode == "xref") {
// 找所有引用了匹配名称的方法/字段的方法体
foreach (var m in t.Methods) {
if (!m.HasBody) continue;
bool hit = false;
foreach (var ins in m.Body.Instructions) {
string s = ins.Operand == null ? "" : ins.Operand.ToString();
if (s.ToLower().Contains(pat)) { hit = true; break; }
}
if (hit) Console.WriteLine("XREF " + m.FullName);
}
}
foreach (var nt in t.NestedTypes) DumpType(nt, mode, pat, depth + 1);
}
static string OpText(object o) {
var mr = o as MethodReference; if (mr != null) return mr.FullName;
var fr = o as FieldReference; if (fr != null) return fr.FullName;
var s = o as string; if (s != null) return "\"" + s + "\"";
var ins = o as Instruction; if (ins != null) return "IL_" + ins.Offset.ToString("x4");
var arr = o as Instruction[]; if (arr != null) return string.Join(",", arr.Select(i => "IL_" + i.Offset.ToString("x4")).ToArray());
var p = o as ParameterReference; if (p != null) return "arg" + p.Index;
var vr = o as VariableReference; if (vr != null) return "V_" + vr.Index;
var tr = o as TypeReference; if (tr != null) return tr.FullName;
return o == null ? "" : o.ToString();
}
}

30
tools/Scan.cs Normal file
View File

@ -0,0 +1,30 @@
using System;
using System.IO;
class Scan {
static int Count(byte[] hay, byte[] needle) {
int n = 0;
for (int i = 0; i + needle.Length <= hay.Length; i++) {
bool m = true;
for (int j = 0; j < needle.Length; j++) if (hay[i + j] != needle[j]) { m = false; break; }
if (m) n++;
}
return n;
}
static void Main(string[] args) {
string dir = args[0];
string[] pats = { "AECH", "AERE", "AEREP", "Claim", "challenge" };
foreach (string f in Directory.GetFiles(dir, "*.dll")) {
byte[] b = File.ReadAllBytes(f);
string hits = "";
foreach (string p in pats) {
byte[] a = System.Text.Encoding.ASCII.GetBytes(p);
byte[] u = System.Text.Encoding.Unicode.GetBytes(p);
int ca = Count(b, a), cu = Count(b, u);
if (ca > 0 || cu > 0) hits += string.Format(" {0}(ascii={1},utf16={2})", p, ca, cu);
}
if (hits.Length > 0) Console.WriteLine(Path.GetFileName(f) + ":" + hits);
}
Console.WriteLine("scan done");
}
}