docs+scripts: enet_protocol.md, verification plan, PS verify suite, session analysis scripts, DLL tools, README
This commit is contained in:
parent
e248a979e8
commit
a98930aa71
69
README.md
Normal file
69
README.md
Normal 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
710
docs/enet_protocol.md
Normal 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 状态机。
|
||||
85
docs/rust_verification_plan.md
Normal file
85
docs/rust_verification_plan.md
Normal 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 实测修正/确认各字段)
|
||||
1902
scripts/Verify-EnetProtocol.ps1
Normal file
1902
scripts/Verify-EnetProtocol.ps1
Normal file
File diff suppressed because it is too large
Load Diff
38
scripts/Verify-Rdm.ps1
Normal file
38
scripts/Verify-Rdm.ps1
Normal 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
47
scripts/enet_lib.ps1
Normal 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 '' }
|
||||
55
scripts/session/analyze_ack.ps1
Normal file
55
scripts/session/analyze_ack.ps1
Normal 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])
|
||||
48
scripts/session/analyze_aech.ps1
Normal file
48
scripts/session/analyze_aech.ps1
Normal 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"
|
||||
74
scripts/session/analyze_branch.ps1
Normal file
74
scripts/session/analyze_branch.ps1
Normal 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 }
|
||||
}
|
||||
74
scripts/session/analyze_branch2.ps1
Normal file
74
scripts/session/analyze_branch2.ps1
Normal 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 }
|
||||
}
|
||||
81
scripts/session/analyze_ck.ps1
Normal file
81
scripts/session/analyze_ck.ps1
Normal 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 }
|
||||
}
|
||||
}
|
||||
56
scripts/session/analyze_claim_seq.ps1
Normal file
56
scripts/session/analyze_claim_seq.ps1
Normal 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
|
||||
}
|
||||
}
|
||||
240
scripts/session/analyze_cold.ps1
Normal file
240
scripts/session/analyze_cold.ps1
Normal 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分析完成。"
|
||||
142
scripts/session/analyze_cold2.ps1
Normal file
142
scripts/session/analyze_cold2.ps1
Normal 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分析完成。"
|
||||
31
scripts/session/analyze_coldstart_seq.ps1
Normal file
31
scripts/session/analyze_coldstart_seq.ps1
Normal 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 }
|
||||
}
|
||||
70
scripts/session/analyze_dmx1.ps1
Normal file
70
scripts/session/analyze_dmx1.ps1
Normal 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)))
|
||||
82
scripts/session/analyze_dmx2.ps1
Normal file
82
scripts/session/analyze_dmx2.ps1
Normal 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)
|
||||
}
|
||||
96
scripts/session/analyze_dmx3.ps1
Normal file
96
scripts/session/analyze_dmx3.ps1
Normal 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) }
|
||||
81
scripts/session/analyze_fix2.ps1
Normal file
81
scripts/session/analyze_fix2.ps1
Normal 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分析完成。"
|
||||
69
scripts/session/analyze_mute.ps1
Normal file
69
scripts/session/analyze_mute.ps1
Normal 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)
|
||||
}
|
||||
57
scripts/session/analyze_set.ps1
Normal file
57
scripts/session/analyze_set.ps1
Normal 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)
|
||||
}
|
||||
57
scripts/session/analyze_setb.ps1
Normal file
57
scripts/session/analyze_setb.ps1
Normal 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)
|
||||
}
|
||||
157
scripts/session/analyze_ss.ps1
Normal file
157
scripts/session/analyze_ss.ps1
Normal 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分析完成。"
|
||||
81
scripts/session/analyze_ss2.ps1
Normal file
81
scripts/session/analyze_ss2.ps1
Normal 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分析完成。"
|
||||
14
scripts/session/analyze_startup.ps1
Normal file
14
scripts/session/analyze_startup.ps1
Normal 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])
|
||||
}
|
||||
11
scripts/session/cap_hostdemo.ps1
Normal file
11
scripts/session/cap_hostdemo.ps1
Normal 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
|
||||
9
scripts/session/cat_runbutler.ps1
Normal file
9
scripts/session/cat_runbutler.ps1
Normal 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
|
||||
25
scripts/session/check_desc.ps1
Normal file
25
scripts/session/check_desc.ps1
Normal 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 可从首字节推出
|
||||
12
scripts/session/check_fix1_aech.ps1
Normal file
12
scripts/session/check_fix1_aech.ps1
Normal 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)) }
|
||||
}
|
||||
9
scripts/session/check_log.ps1
Normal file
9
scripts/session/check_log.ps1
Normal 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
|
||||
9
scripts/session/check_log2.ps1
Normal file
9
scripts/session/check_log2.ps1
Normal 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
|
||||
9
scripts/session/check_log3.ps1
Normal file
9
scripts/session/check_log3.ps1
Normal 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
|
||||
9
scripts/session/check_log4.ps1
Normal file
9
scripts/session/check_log4.ps1
Normal 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
|
||||
9
scripts/session/check_log5.ps1
Normal file
9
scripts/session/check_log5.ps1
Normal 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
|
||||
11
scripts/session/check_proc.ps1
Normal file
11
scripts/session/check_proc.ps1
Normal 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
|
||||
9
scripts/session/check_proc2.ps1
Normal file
9
scripts/session/check_proc2.ps1
Normal 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
|
||||
9
scripts/session/check_procs.ps1
Normal file
9
scripts/session/check_procs.ps1
Normal 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
|
||||
9
scripts/session/check_set.ps1
Normal file
9
scripts/session/check_set.ps1
Normal 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
|
||||
11
scripts/session/check_status.ps1
Normal file
11
scripts/session/check_status.ps1
Normal 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
|
||||
10
scripts/session/check_ubuntu_git.ps1
Normal file
10
scripts/session/check_ubuntu_git.ps1
Normal 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
|
||||
24
scripts/session/cold2_order.ps1
Normal file
24
scripts/session/cold2_order.ps1
Normal 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")
|
||||
38
scripts/session/deploy_g.ps1
Normal file
38
scripts/session/deploy_g.ps1
Normal 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
|
||||
39
scripts/session/deploy_set.ps1
Normal file
39
scripts/session/deploy_set.ps1
Normal 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
|
||||
31
scripts/session/deploy_set2.ps1
Normal file
31
scripts/session/deploy_set2.ps1
Normal 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
|
||||
29
scripts/session/deploy_tests.ps1
Normal file
29
scripts/session/deploy_tests.ps1
Normal 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
|
||||
49
scripts/session/diff_ann2.ps1
Normal file
49
scripts/session/diff_ann2.ps1
Normal 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"
|
||||
24
scripts/session/diff_ann3.ps1
Normal file
24
scripts/session/diff_ann3.ps1
Normal 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)
|
||||
}
|
||||
31
scripts/session/diff_announce.ps1
Normal file
31
scripts/session/diff_announce.ps1
Normal 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"
|
||||
}
|
||||
20
scripts/session/enum_preclaim.ps1
Normal file
20
scripts/session/enum_preclaim.ps1
Normal 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
|
||||
13
scripts/session/extract_symclaim.ps1
Normal file
13
scripts/session/extract_symclaim.ps1
Normal 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)) }
|
||||
8
scripts/session/find_claim2.ps1
Normal file
8
scripts/session/find_claim2.ps1
Normal 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
|
||||
9
scripts/session/find_sub07.ps1
Normal file
9
scripts/session/find_sub07.ps1
Normal 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 ""
|
||||
}
|
||||
7
scripts/session/find_sub07b.ps1
Normal file
7
scripts/session/find_sub07b.ps1
Normal 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 ""
|
||||
}
|
||||
11
scripts/session/grep_aech.ps1
Normal file
11
scripts/session/grep_aech.ps1
Normal 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
|
||||
18
scripts/session/listen_113.ps1
Normal file
18
scripts/session/listen_113.ps1
Normal 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)"
|
||||
11
scripts/session/mystery60.ps1
Normal file
11
scripts/session/mystery60.ps1
Normal 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
|
||||
11
scripts/session/post_claim.ps1
Normal file
11
scripts/session/post_claim.ps1
Normal 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
|
||||
20
scripts/session/probe_loop.ps1
Normal file
20
scripts/session/probe_loop.ps1
Normal 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"
|
||||
20
scripts/session/probe_loop2.ps1
Normal file
20
scripts/session/probe_loop2.ps1
Normal 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"
|
||||
8
scripts/session/push_try.ps1
Normal file
8
scripts/session/push_try.ps1
Normal 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
|
||||
11
scripts/session/rdm_tail.ps1
Normal file
11
scripts/session/rdm_tail.ps1
Normal 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
|
||||
11
scripts/session/rdm_tail2.ps1
Normal file
11
scripts/session/rdm_tail2.ps1
Normal 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
|
||||
16
scripts/session/read_symclose.ps1
Normal file
16
scripts/session/read_symclose.ps1
Normal 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
|
||||
16
scripts/session/read_symclose2.ps1
Normal file
16
scripts/session/read_symclose2.ps1
Normal 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
|
||||
9
scripts/session/restore_butler.ps1
Normal file
9
scripts/session/restore_butler.ps1
Normal 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
|
||||
11
scripts/session/run_hd3.ps1
Normal file
11
scripts/session/run_hd3.ps1
Normal 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
|
||||
4
scripts/session/run_hd4.ps1
Normal file
4
scripts/session/run_hd4.ps1
Normal 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"
|
||||
13
scripts/session/start_butler.ps1
Normal file
13
scripts/session/start_butler.ps1
Normal 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
|
||||
23
scripts/session/start_local.ps1
Normal file
23
scripts/session/start_local.ps1
Normal 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
|
||||
52
scripts/session/sync_docs.ps1
Normal file
52
scripts/session/sync_docs.ps1
Normal 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
|
||||
42
scripts/session/sync_push.ps1
Normal file
42
scripts/session/sync_push.ps1
Normal 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
|
||||
36
scripts/session/sync_push2.ps1
Normal file
36
scripts/session/sync_push2.ps1
Normal 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
|
||||
47
scripts/session/test_g.ps1
Normal file
47
scripts/session/test_g.ps1
Normal 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()
|
||||
14
scripts/session/test_newmac.ps1
Normal file
14
scripts/session/test_newmac.ps1
Normal 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
|
||||
14
scripts/session/test_norelay.ps1
Normal file
14
scripts/session/test_norelay.ps1
Normal 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
|
||||
52
scripts/session/test_setack.ps1
Normal file
52
scripts/session/test_setack.ps1
Normal 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()
|
||||
60
scripts/session/test_setget.ps1
Normal file
60
scripts/session/test_setget.ps1
Normal 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()
|
||||
11
scripts/session/verify_dmxsend.ps1
Normal file
11
scripts/session/verify_dmxsend.ps1
Normal 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
|
||||
27
scripts/session/verify_frame.ps1
Normal file
27
scripts/session/verify_frame.ps1
Normal 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 ' ')"
|
||||
}
|
||||
11
scripts/session/watch_claim.ps1
Normal file
11
scripts/session/watch_claim.ps1
Normal 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
|
||||
16
scripts/session/watch_sym_claim.ps1
Normal file
16
scripts/session/watch_sym_claim.ps1
Normal 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" }
|
||||
11
scripts/session/webui_probe.ps1
Normal file
11
scripts/session/webui_probe.ps1
Normal 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
28
tools/GetTable.cs
Normal 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
71
tools/IlDump.cs
Normal 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
30
tools/Scan.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user