Compare commits
No commits in common. "3056a6974c8841c727cb695c2c8a521553169bbb" and "a98930aa7153a01e2922536e2dbf941041cc86ba" have entirely different histories.
3056a6974c
...
a98930aa71
|
|
@ -637,44 +637,29 @@ mute 亦无碍);cmd=19/1a 中 SET 类厂商 PID(0x0200 族)写路径未
|
||||||
全链路(认领→注册→加密→解密)闭环自证。对真机 .1 仿冒的唯一额外约束:
|
全链路(认领→注册→加密→解密)闭环自证。对真机 .1 仿冒的唯一额外约束:
|
||||||
AECH 质询须源自 192.168.123.113(真机固件过滤,§3.7)。
|
AECH 质询须源自 192.168.123.113(真机固件过滤,§3.7)。
|
||||||
|
|
||||||
### 11.7 hostdemo:真机 .1 主机侧仿真(2026-09-10 晚)★★✅ 全链路完成(RDM 发现 + DMX 控制)
|
### 11.7 hostdemo:真机 .1 主机侧仿真(2026-09-10 晚)★进行中
|
||||||
|
|
||||||
`hostdemo [目标IP] [秒]` 子命令(src/hostdemo.rs)。当晚会话历经多次假设-证伪,
|
`hostdemo [目标IP] [秒]` 子命令(src/hostdemo.rs):完整主机等效流程——发现 →
|
||||||
**最终工作配方(HOSTDEMO_MODE=slfull,2026-09-11 00:3x 灯光实测确认)**:
|
冷启动前奏(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 呼吸控制。
|
||||||
|
|
||||||
```
|
**实测进展**:
|
||||||
1. 发现(type=1 → 217B)
|
- ✅ 发现应答(217B)每次都正常——设备在任何状态下都答发现
|
||||||
2. 认领: announce144(102B 身份帧) 单播 → 47B 注册应答 → AECH 质询 → AERE=G(nonce) ✓
|
- ✅ **AECH 认领真机 3 次完整通过**(3 轮 G(nonce) 验证 PASS,nonce/B12 自选随机)
|
||||||
(★announce144 即 ident102 同帧; 完整冷启动前奏/绑定帧/EUCE 反而无效)
|
- ⏳ RDM 发现/控制段:尚未跑到(受下方认领门控阻断)
|
||||||
3. verbatim 重放 SL 真实会话帧流(replay_merged.txt, 32540 帧/126.7s 循环):
|
- cold2 时序佐证:首个 RDM 请求在认领后 54ms 发出;首个 RDM 应答与首个 42B
|
||||||
- DMX 数据帧: 保留 SL 原帧结构, 仅替换 r1/r2 + 密文数据段
|
遥测同步出现于认领后 1.37s(此前主机已连发 ~14 次 UNIQUE_BRANCH——
|
||||||
(用本会话认领的 B12 重加密呼吸图案 → Butler 解密上灯)
|
冷启动应答延迟大属正常,hostdemo 每分支按 4 次 ×250ms 重试)
|
||||||
- RDM 请求: SL 原帧(UN_MUTE/UNIQUE_BRANCH/GET, 含 ff/fe 成对与可变偏移结构)
|
|
||||||
4. 被动解码应答: DISC 脉冲→UID, DEVICE_INFO/START_ADDRESS→DMX 地址
|
|
||||||
```
|
|
||||||
|
|
||||||
**实测结果(灯光肉眼确认呼吸)**:
|
**★新发现:真机认领门控/锁死行为(§3.7 补遗)**
|
||||||
- ✅ RDM 发现 3/3: `010734670125`(NFS1K2D)@101/2ch、`010734880900`(NFM680S)@123、
|
连续 3 次成功认领但**不发任何会话流量**(无 DMX/心跳)后,.1 进入锁定态:
|
||||||
`0899500026c9`(HMKJ)@1/32ch(应答 15.5k 帧)
|
- 对 ident102 不回 47B 注册应答、对 AECH 质询完全不答——
|
||||||
- ✅ DMX 控制: ch101/ch123/ch1 异相呼吸(fade-in 渐入)**灯光确认**
|
**连 cold2 原始 ident/质询的逐字节重放也一并忽略**(设备端状态,非帧构造问题)
|
||||||
|
- 发现请求(type=1)仍正常应答(225B)
|
||||||
**★会话机制深层发现(本晚核心)**:
|
- SymphoLight 于锁定约 55-70 分钟后认领成功(19:2x,期间它先广播了 >90s 才发起)
|
||||||
- **AECH 握手成功 ≠ 会话数据通道打通**——认领(47B/AERE 全过)后,自构造的
|
- SymphoLight 关闭(20:04,无释放帧、无 AECH,DMX 流直接消失)后 15 分钟仍锁定
|
||||||
type=4 帧(DMX/RDM)仍被静默丢弃;而 verbatim 重放 SL 的历史会话帧立即全通
|
- 暂定模型:**会话保持 ≈ 55-60 分钟**(自最后一次会话流量起算),期间拒答一切新认领
|
||||||
(遥测/RDM 应答恢复)→ Butler 在帧级校验某种我们尚未破解的会话一致性
|
|
||||||
- **RDM 请求帧的消息起始位置可变**(UN_MUTE@0x3A vs UNIQUE_BRANCH@0x3C,
|
|
||||||
前+len16 字段位置随之浮动 2B)——固定偏移构造的 RDM 帧必被拒;变长规则未破解
|
|
||||||
(实用绕过: 直接重放 SL 原帧)
|
|
||||||
- 1947 端口"密钥报文"实为 **40B ASCII 许可令牌**(`BlZRQphA…`,恒定值,双目标
|
|
||||||
21 帧 @200ms 每 ~34s 一轮);EUCE 实为全程每 6s 的许可信标
|
|
||||||
- 真机认领门控行为(当晚早段观察): 灯具 MUTE 态跨会话持久(SL 遗留的 mute
|
|
||||||
挡住后续 branch 查询);SymphoLight 关闭无释放帧,DMX 流直接消失
|
|
||||||
|
|
||||||
**遗留**:RDM 帧变长结构规则(破解后可用纯自构造帧完成发现,摆脱对 SL 原帧的依赖)。
|
|
||||||
|
|
||||||
**历史过程记录(假设-证伪链,供复盘)**:完整冷启动前奏(45s)/绑定帧×4/EUCE 6s 节奏/
|
|
||||||
新鲜时间戳宣告/1947 密钥/会话保持 2h 模型——均单因素排除;最终由 verbatim 重放
|
|
||||||
二分(阶段 A 无认领 vs 阶段 B 先认领)锁定"认领 + 帧级会话一致性"双条件。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
449
src/hostdemo.rs
449
src/hostdemo.rs
|
|
@ -158,442 +158,6 @@ pub fn run(target: &str, bind_ip: &str, secs: u64) {
|
||||||
}
|
}
|
||||||
if !dev_ok { println!("[1] 发现无应答, 终止"); return; }
|
if !dev_ok { println!("[1] 发现无应答, 终止"); return; }
|
||||||
|
|
||||||
// replay: verbatim 重放 SL 的完整会话帧流 (sym_startup t=101.3..136), 双阶段定位 RDM 门控
|
|
||||||
if std::env::var("HOSTDEMO_MODE").unwrap_or_default() == "replay" {
|
|
||||||
let path = std::env::var("REPLAY_FILE")
|
|
||||||
.unwrap_or_else(|_| "C:/Users/tonycao/AppData/Local/Temp/opencode/replay_frames.txt".into());
|
|
||||||
let data = std::fs::read_to_string(&path).expect("replay file");
|
|
||||||
let mut frames: Vec<(f32, String, u16, Vec<u8>)> = Vec::new();
|
|
||||||
for line in data.lines() {
|
|
||||||
let p: Vec<&str> = line.split('|').collect();
|
|
||||||
if p.len() < 4 { continue; }
|
|
||||||
let t: f32 = p[0].parse().unwrap();
|
|
||||||
let dst = p[1].to_string();
|
|
||||||
let port: u16 = p[2].parse().unwrap();
|
|
||||||
let hex = &p[3];
|
|
||||||
let mut b = Vec::with_capacity(hex.len() / 2);
|
|
||||||
for i in (0..hex.len()).step_by(2) { b.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
|
||||||
frames.push((t, dst, port, b));
|
|
||||||
}
|
|
||||||
let t0 = frames[0].0;
|
|
||||||
for phase in ["A(无认领 verbatim)", "B(先认领再 verbatim)"] {
|
|
||||||
println!("[R] === 阶段 {}: 重放 {} 帧 / {:.1}s ===", phase, frames.len(), frames[frames.len() - 1].0 - t0);
|
|
||||||
if phase.starts_with('B') {
|
|
||||||
// 快速认领
|
|
||||||
let o2: Vec<u8> = bind_ip.split('.').map(|x| x.parse().unwrap()).collect();
|
|
||||||
let ident = load_template("announce144");
|
|
||||||
sock.send_to(&ident, &dst).unwrap();
|
|
||||||
std::thread::sleep(Duration::from_millis(300));
|
|
||||||
let nonce = rng.next();
|
|
||||||
let bc = rng.next();
|
|
||||||
let mut ch = load_template("claim_h112");
|
|
||||||
ch[0x1A..0x1E].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]);
|
|
||||||
ch[0x88..0x8C].copy_from_slice(&nonce.to_le_bytes());
|
|
||||||
ch[0x8C..0x90].copy_from_slice(&bc.to_le_bytes());
|
|
||||||
sock.send_to(&ch, &dst).unwrap();
|
|
||||||
std::thread::sleep(Duration::from_millis(500));
|
|
||||||
println!("[R] 认领已发 (B12=0x{:08x})", bc);
|
|
||||||
}
|
|
||||||
let start = Instant::now();
|
|
||||||
let mut recv_n = 0u64;
|
|
||||||
let mut fi = 0;
|
|
||||||
while start.elapsed().as_secs_f32() < frames[frames.len() - 1].0 - t0 + 3.0 {
|
|
||||||
let now = start.elapsed().as_secs_f32();
|
|
||||||
while fi < frames.len() && frames[fi].0 - t0 <= now {
|
|
||||||
let (_, d, port, b) = &frames[fi];
|
|
||||||
let target_addr = format!("{}:{}", d, port);
|
|
||||||
let _ = sock.send_to(b, &target_addr);
|
|
||||||
fi += 1;
|
|
||||||
}
|
|
||||||
while let Ok((n, src)) = sock.recv_from(&mut buf) {
|
|
||||||
recv_n += 1;
|
|
||||||
let b = &buf[..n];
|
|
||||||
println!(" [R] ← len={} src={} head={}", n, src, hexs(&b[..std::cmp::min(20, n)]));
|
|
||||||
}
|
|
||||||
std::thread::sleep(Duration::from_millis(2));
|
|
||||||
}
|
|
||||||
println!("[R] 阶段 {} 结束: 收到 {} 帧", phase, recv_n);
|
|
||||||
std::thread::sleep(Duration::from_millis(1500));
|
|
||||||
// 排空残留
|
|
||||||
while sock.recv_from(&mut buf).is_ok() {}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sls: session-continue — 不认领! 直接续用 SL 会话 (B12=0x4366937a), SL 实帧做模板
|
|
||||||
if std::env::var("HOSTDEMO_MODE").unwrap_or_default() == "sls" {
|
|
||||||
let path = std::env::var("REPLAY_FILE")
|
|
||||||
.unwrap_or_else(|_| "C:/Users/tonycao/AppData/Local/Temp/opencode/replay_frames.txt".into());
|
|
||||||
let data = std::fs::read_to_string(&path).expect("replay file");
|
|
||||||
let sb12: u32 = 0x4366937a; // SL 22:52 会话 B12 (Butler 会话状态绑定的值)
|
|
||||||
// 从重放清单提取模板: DMX 数据帧 / RDM branch 请求
|
|
||||||
let mut base_dmx: Option<Vec<u8>> = None;
|
|
||||||
let mut base_rdm: Option<Vec<u8>> = None;
|
|
||||||
let mut max_ctr: u32 = 0;
|
|
||||||
for line in data.lines() {
|
|
||||||
let p: Vec<&str> = line.split('|').collect();
|
|
||||||
if p.len() < 4 { continue; }
|
|
||||||
let hex = p[3];
|
|
||||||
if hex.len() < 0x50 * 2 { continue; }
|
|
||||||
let b = |o: usize| u8::from_str_radix(&hex[o * 2..o * 2 + 2], 16).unwrap();
|
|
||||||
if hex.len() == 1084 * 2 && b(0x18) == 1 && b(0x20) == 1 && b(0x21) == 1 && b(0x3c) != 1 {
|
|
||||||
let ctr = g32(&hex.as_bytes(), 0); // placeholder never used
|
|
||||||
let _ = ctr;
|
|
||||||
let mut fb = Vec::with_capacity(1084);
|
|
||||||
for i in (0..hex.len()).step_by(2) { fb.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
|
||||||
let c = u32::from_le_bytes([fb[0x1A], fb[0x1B], fb[0x1C], fb[0x1D]]);
|
|
||||||
if c > max_ctr { max_ctr = c; }
|
|
||||||
if base_dmx.is_none() { base_dmx = Some(fb); }
|
|
||||||
}
|
|
||||||
if hex.len() == 1084 * 2 && b(0x3c) == 1 && b(0x30) == 0 && b(0x50) == 0 && b(0x51) == 1 && base_rdm.is_none() {
|
|
||||||
let mut fb = Vec::with_capacity(1084);
|
|
||||||
for i in (0..hex.len()).step_by(2) { fb.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
|
||||||
base_rdm = Some(fb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let dmx_t = base_dmx.expect("no DMX template in replay file");
|
|
||||||
let rdm_t = base_rdm.expect("no RDM template in replay file");
|
|
||||||
let ctr0 = max_ctr + 10;
|
|
||||||
println!("[SLS] 会话续用: B12=0x{:08x}, DMX模板 counter 基线 {}", sb12, ctr0);
|
|
||||||
println!("[SLS] DMX模板 0x2E-0x3B: {}", hexs(&dmx_t[0x2E..0x3C]));
|
|
||||||
let known: [[u8; 6]; 3] = [
|
|
||||||
[0x01, 0x07, 0x34, 0x67, 0x01, 0x25],
|
|
||||||
[0x01, 0x07, 0x34, 0x88, 0x09, 0x00],
|
|
||||||
[0x08, 0x99, 0x50, 0x00, 0x26, 0xc9],
|
|
||||||
];
|
|
||||||
let start = Instant::now();
|
|
||||||
let mut ctr = ctr0;
|
|
||||||
let mut last_hb = Instant::now() - Duration::from_secs(10);
|
|
||||||
let mut last_rdm = Instant::now() - Duration::from_secs(10);
|
|
||||||
let mut step = 0usize; // 0=UN_MUTE, 1..=3=窄分支, 4+=GET
|
|
||||||
let mut addr_list: Vec<u16> = Vec::new();
|
|
||||||
let mut resp_n = 0u64;
|
|
||||||
while start.elapsed() < Duration::from_secs(secs.max(60)) {
|
|
||||||
let t = start.elapsed().as_secs_f32();
|
|
||||||
if last_hb.elapsed() >= Duration::from_secs(6) {
|
|
||||||
let _ = sock.send_to(&load_template("heartbeat"), &dst);
|
|
||||||
last_hb = Instant::now();
|
|
||||||
}
|
|
||||||
// RDM 任务每 ~700ms 一步
|
|
||||||
if t > 1.0 && last_rdm.elapsed() >= Duration::from_millis(700) {
|
|
||||||
last_rdm = Instant::now();
|
|
||||||
let mut f = rdm_t.clone();
|
|
||||||
f[0x1A..0x1E].copy_from_slice(&ctr.to_le_bytes());
|
|
||||||
let mut pd: Vec<u8> = Vec::new();
|
|
||||||
let mut pid: u16 = 0x0001;
|
|
||||||
let mut dest = [0xffu8; 6];
|
|
||||||
match step {
|
|
||||||
0 => { pid = 0x0003; } // UN_MUTE 广播
|
|
||||||
1..=3 => {
|
|
||||||
let u = known[step - 1];
|
|
||||||
let v = uid_u64(&u);
|
|
||||||
pd.extend_from_slice(&u64_uid(v - 1));
|
|
||||||
pd.extend_from_slice(&u64_uid(v + 1));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// GET DEVICE_INFO 轮询已发现地址
|
|
||||||
if (step - 4) < known.len() {
|
|
||||||
dest = known[step - 4];
|
|
||||||
pid = 0x0060;
|
|
||||||
} else { step = 3; } // 循环重试 GET
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let msg_len = 24 + pd.len();
|
|
||||||
f[0x3C] = 0x01;
|
|
||||||
f[0x3D] = msg_len as u8;
|
|
||||||
f[0x3E..0x44].copy_from_slice(&dest);
|
|
||||||
f[0x4A] = (step * 7 + 1) as u8;
|
|
||||||
f[0x4F] = if pid == 0x0001 { 0x10 } else if pid == 0x0060 { 0x20 } else { 0x10 };
|
|
||||||
f[0x50] = (pid >> 8) as u8;
|
|
||||||
f[0x51] = (pid & 0xFF) as u8;
|
|
||||||
f[0x52] = pd.len() as u8;
|
|
||||||
f[0x53..0x53 + pd.len()].copy_from_slice(&pd);
|
|
||||||
let mut sum: u16 = 0xCC;
|
|
||||||
for i in 0x3C..0x3C + msg_len { sum = sum.wrapping_add(f[i] as u16); }
|
|
||||||
let cs = 0x53 + pd.len();
|
|
||||||
f[cs] = (sum >> 8) as u8;
|
|
||||||
f[cs + 1] = (sum & 0xFF) as u8;
|
|
||||||
let mut f2 = f.clone();
|
|
||||||
f2[0x30] = 0x01;
|
|
||||||
f2[0x49] = f2[0x49].wrapping_sub(1);
|
|
||||||
let _ = sock.send_to(&f, &dst);
|
|
||||||
let _ = sock.send_to(&f2, &dst);
|
|
||||||
println!("[SLS] RDM step={} pid={:04x} 已发 (ctr={})", step, pid, ctr);
|
|
||||||
step += 1;
|
|
||||||
}
|
|
||||||
// 收包
|
|
||||||
while let Ok((n, src)) = sock.recv_from(&mut buf) {
|
|
||||||
if src.ip().to_string() != target { continue; }
|
|
||||||
let b = &buf[..n];
|
|
||||||
resp_n += 1;
|
|
||||||
if let Some((is_pulse, d)) = rdm_region(b) {
|
|
||||||
if is_pulse {
|
|
||||||
if let Some(uid) = decode_pulse(d) {
|
|
||||||
println!(" [SLS] ★脉冲 UID={}", uid_s(&uid));
|
|
||||||
} else {
|
|
||||||
println!(" [SLS] 脉冲(冲突/损坏)");
|
|
||||||
}
|
|
||||||
} else if d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0x60] {
|
|
||||||
let a = u16::from_be_bytes([d[0x18 + 14], d[0x18 + 15]]);
|
|
||||||
let fp = u16::from_be_bytes([d[0x18 + 10], d[0x18 + 11]]);
|
|
||||||
println!(" [SLS] ★DEVICE_INFO srcUID={} DMX地址={} 通道={}", uid_s(&[d[9], d[10], d[11], d[12], d[13], d[14]]), a, fp);
|
|
||||||
if !addr_list.contains(&a) { addr_list.push(a); }
|
|
||||||
} else {
|
|
||||||
println!(" [SLS] RDM应答 len={}", d.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// DMX @30fps: 发现完成前黑场, 之后呼吸
|
|
||||||
let mut p1 = [0u8; 512];
|
|
||||||
if !addr_list.is_empty() {
|
|
||||||
for (k, &a) in addr_list.iter().enumerate() {
|
|
||||||
let phase = t * 1.2 + k as f32 * 2.1;
|
|
||||||
let v = (((phase.sin() * 0.5 + 0.5) * 255.0) * ((t - 1.0) * 0.5).min(1.0).max(0.0)) as u8;
|
|
||||||
let ch = a.max(1) - 1;
|
|
||||||
if ch < 512 { p1[ch as usize] = v; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let r1 = rng.next();
|
|
||||||
let r2 = rng.next();
|
|
||||||
let mut f = dmx_t.clone();
|
|
||||||
f[0x1A..0x1E].copy_from_slice(&ctr.to_le_bytes());
|
|
||||||
f[0x22..0x26].copy_from_slice(&r1.to_le_bytes());
|
|
||||||
f[0x26..0x2A].copy_from_slice(&r2.to_le_bytes());
|
|
||||||
f[0x3C..0x23C].copy_from_slice(&agios_dmx_encrypt(&p1, r1, sb12));
|
|
||||||
f[0x23C..0x43C].copy_from_slice(&agios_dmx_encrypt(&[0u8; 512], r2, sb12));
|
|
||||||
let _ = sock.send_to(&f, &dst);
|
|
||||||
ctr += 1;
|
|
||||||
std::thread::sleep(Duration::from_millis(33));
|
|
||||||
}
|
|
||||||
println!("[SLS] 结束: 应答 {} 帧, 控制地址 {:?}", resp_n, addr_list);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// hybrid: 认领 + verbatim SL DMX 帧 + 我们构造的 RDM 帧 — 二分定位毒帧
|
|
||||||
if std::env::var("HOSTDEMO_MODE").unwrap_or_default() == "hybrid" {
|
|
||||||
let path = std::env::var("REPLAY_FILE")
|
|
||||||
.unwrap_or_else(|_| "C:/Users/tonycao/AppData/Local/Temp/opencode/replay_frames.txt".into());
|
|
||||||
let data = std::fs::read_to_string(&path).expect("replay file");
|
|
||||||
let mut dmxf: Vec<Vec<u8>> = Vec::new(); // verbatim DMX 帧
|
|
||||||
let mut base_rdm: Option<Vec<u8>> = None;
|
|
||||||
for line in data.lines() {
|
|
||||||
let p: Vec<&str> = line.split('|').collect();
|
|
||||||
if p.len() < 4 { continue; }
|
|
||||||
let hex = p[3];
|
|
||||||
if hex.len() != 1084 * 2 { continue; }
|
|
||||||
let b = |o: usize| u8::from_str_radix(&hex[o * 2..o * 2 + 2], 16).unwrap();
|
|
||||||
if b(0x18) == 1 && b(0x20) == 1 && b(0x21) == 1 && b(0x3c) != 1 {
|
|
||||||
let mut fb = Vec::new();
|
|
||||||
for i in (0..hex.len()).step_by(2) { fb.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
|
||||||
dmxf.push(fb);
|
|
||||||
}
|
|
||||||
if base_rdm.is_none() && b(0x3c) == 1 && b(0x30) == 0 && b(0x50) == 0 && b(0x51) == 1 {
|
|
||||||
let mut fb = Vec::new();
|
|
||||||
for i in (0..hex.len()).step_by(2) { fb.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
|
||||||
base_rdm = Some(fb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 认领 (announce144 → AECH)
|
|
||||||
let o2: Vec<u8> = bind_ip.split('.').map(|x| x.parse().unwrap()).collect();
|
|
||||||
sock.send_to(&load_template("announce144"), &dst).unwrap();
|
|
||||||
std::thread::sleep(Duration::from_millis(300));
|
|
||||||
let nonce = rng.next();
|
|
||||||
let bc = rng.next();
|
|
||||||
let mut ch = load_template("claim_h112");
|
|
||||||
ch[0x1A..0x1E].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]);
|
|
||||||
ch[0x88..0x8C].copy_from_slice(&nonce.to_le_bytes());
|
|
||||||
ch[0x8C..0x90].copy_from_slice(&bc.to_le_bytes());
|
|
||||||
sock.send_to(&ch, &dst).unwrap();
|
|
||||||
std::thread::sleep(Duration::from_millis(400));
|
|
||||||
println!("[H] 认领完成 B12=0x{:08x}; verbatim DMX {} 帧 + 我们的 RDM", bc, dmxf.len());
|
|
||||||
let rdm_t = base_rdm.expect("no RDM template");
|
|
||||||
let known: [[u8; 6]; 3] = [
|
|
||||||
[0x01, 0x07, 0x34, 0x67, 0x01, 0x25],
|
|
||||||
[0x01, 0x07, 0x34, 0x88, 0x09, 0x00],
|
|
||||||
[0x08, 0x99, 0x50, 0x00, 0x26, 0xc9],
|
|
||||||
];
|
|
||||||
let start = Instant::now();
|
|
||||||
let mut di = 0usize;
|
|
||||||
let mut last_rdm = Instant::now() - Duration::from_secs(10);
|
|
||||||
let mut step = 0usize;
|
|
||||||
let mut addr_list: Vec<u16> = Vec::new();
|
|
||||||
let mut resp_n = 0u64;
|
|
||||||
while start.elapsed() < Duration::from_secs(secs.max(90)) {
|
|
||||||
let t = start.elapsed().as_secs_f32();
|
|
||||||
// verbatim DMX 循环
|
|
||||||
let f = &dmxf[di % dmxf.len()];
|
|
||||||
di += 1;
|
|
||||||
let _ = sock.send_to(f, &dst);
|
|
||||||
if di % 30 == 0 { let _ = sock.send_to(&load_template("heartbeat"), &dst); }
|
|
||||||
// RDM 任务
|
|
||||||
if t > 2.0 && last_rdm.elapsed() >= Duration::from_millis(700) {
|
|
||||||
last_rdm = Instant::now();
|
|
||||||
let mut f = rdm_t.clone();
|
|
||||||
let mut pd: Vec<u8> = Vec::new();
|
|
||||||
let mut pid: u16 = 0x0001;
|
|
||||||
let mut dest = [0xffu8; 6];
|
|
||||||
let mut cc = 0x10u8;
|
|
||||||
match step {
|
|
||||||
0 => { pid = 0x0003; }
|
|
||||||
1..=3 => {
|
|
||||||
let v = uid_u64(&known[step - 1]);
|
|
||||||
pd.extend_from_slice(&u64_uid(v - 1));
|
|
||||||
pd.extend_from_slice(&u64_uid(v + 1));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if (step - 4) < known.len() { dest = known[step - 4]; pid = 0x0060; cc = 0x20; }
|
|
||||||
else { step = 3; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let msg_len = 24 + pd.len();
|
|
||||||
f[0x1A..0x1E].copy_from_slice(&(di as u32).to_le_bytes());
|
|
||||||
f[0x3D] = msg_len as u8;
|
|
||||||
f[0x3E..0x44].copy_from_slice(&dest);
|
|
||||||
f[0x4A] = (step * 7 + 1) as u8;
|
|
||||||
f[0x4F] = cc;
|
|
||||||
f[0x50] = (pid >> 8) as u8;
|
|
||||||
f[0x51] = (pid & 0xFF) as u8;
|
|
||||||
f[0x52] = pd.len() as u8;
|
|
||||||
f[0x53..0x53 + pd.len()].copy_from_slice(&pd);
|
|
||||||
let mut sum: u16 = 0xCC;
|
|
||||||
for i in 0x3C..0x3C + msg_len { sum = sum.wrapping_add(f[i] as u16); }
|
|
||||||
let cs = 0x53 + pd.len();
|
|
||||||
f[cs] = (sum >> 8) as u8;
|
|
||||||
f[cs + 1] = (sum & 0xFF) as u8;
|
|
||||||
let mut f2 = f.clone();
|
|
||||||
f2[0x30] = 0x01;
|
|
||||||
f2[0x49] = f2[0x49].wrapping_sub(1);
|
|
||||||
let _ = sock.send_to(&f, &dst);
|
|
||||||
let _ = sock.send_to(&f2, &dst);
|
|
||||||
println!("[H] RDM step={} pid={:04x}", step, pid);
|
|
||||||
step += 1;
|
|
||||||
}
|
|
||||||
while let Ok((n, src)) = sock.recv_from(&mut buf) {
|
|
||||||
if src.ip().to_string() != target { continue; }
|
|
||||||
let b = &buf[..n];
|
|
||||||
resp_n += 1;
|
|
||||||
if let Some((is_pulse, d)) = rdm_region(b) {
|
|
||||||
if is_pulse {
|
|
||||||
if let Some(uid) = decode_pulse(d) { println!(" [H] ★脉冲 UID={}", uid_s(&uid)); }
|
|
||||||
else { println!(" [H] 脉冲(冲突)"); }
|
|
||||||
} else if d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0x60] {
|
|
||||||
let a = u16::from_be_bytes([d[0x18 + 14], d[0x18 + 15]]);
|
|
||||||
let fp = u16::from_be_bytes([d[0x18 + 10], d[0x18 + 11]]);
|
|
||||||
println!(" [H] ★DEVICE_INFO srcUID={} 地址={} 通道={}", uid_s(&[d[9], d[10], d[11], d[12], d[13], d[14]]), a, fp);
|
|
||||||
if !addr_list.contains(&a) { addr_list.push(a); }
|
|
||||||
} else {
|
|
||||||
println!(" [H] RDM应答 len={}", d.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::thread::sleep(Duration::from_millis(33));
|
|
||||||
}
|
|
||||||
println!("[H] 结束: 应答 {} 帧, 地址 {:?}", resp_n, addr_list);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// slfull: 认领 + verbatim 重放 SL 会话(循环) + 被动解码 RDM 应答 → 发现灯具并打印地址
|
|
||||||
// (绕过未破解的变长 RDM 帧结构: 用 SL 原帧当查询, 我们解码应答)
|
|
||||||
if std::env::var("HOSTDEMO_MODE").unwrap_or_default() == "slfull" {
|
|
||||||
let path = std::env::var("REPLAY_FILE")
|
|
||||||
.unwrap_or_else(|_| "C:/Users/tonycao/AppData/Local/Temp/opencode/replay_frames.txt".into());
|
|
||||||
let data = std::fs::read_to_string(&path).expect("replay file");
|
|
||||||
let mut frames: Vec<(f32, String, u16, Vec<u8>)> = Vec::new();
|
|
||||||
for line in data.lines() {
|
|
||||||
let p: Vec<&str> = line.split('|').collect();
|
|
||||||
if p.len() < 4 { continue; }
|
|
||||||
let t: f32 = p[0].parse().unwrap();
|
|
||||||
let dst = p[1].to_string();
|
|
||||||
let port: u16 = p[2].parse().unwrap();
|
|
||||||
let hex = p[3];
|
|
||||||
let mut b = Vec::new();
|
|
||||||
for i in (0..hex.len()).step_by(2) { b.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
|
||||||
frames.push((t, dst, port, b));
|
|
||||||
}
|
|
||||||
let t0 = frames[0].0;
|
|
||||||
let dur = frames[frames.len() - 1].0 - t0;
|
|
||||||
// 认领
|
|
||||||
let o2: Vec<u8> = bind_ip.split('.').map(|x| x.parse().unwrap()).collect();
|
|
||||||
sock.send_to(&load_template("announce144"), &dst).unwrap();
|
|
||||||
std::thread::sleep(Duration::from_millis(300));
|
|
||||||
let nonce = rng.next();
|
|
||||||
let bc = rng.next();
|
|
||||||
let mut ch = load_template("claim_h112");
|
|
||||||
ch[0x1A..0x1E].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]);
|
|
||||||
ch[0x88..0x8C].copy_from_slice(&nonce.to_le_bytes());
|
|
||||||
ch[0x8C..0x90].copy_from_slice(&bc.to_le_bytes());
|
|
||||||
sock.send_to(&ch, &dst).unwrap();
|
|
||||||
std::thread::sleep(Duration::from_millis(500));
|
|
||||||
println!("[F] 认领完成; 开始循环重放 {} 帧 ({:.1}s/轮), 被动解码 RDM 应答", frames.len(), dur);
|
|
||||||
let start = Instant::now();
|
|
||||||
let mut seen_uids: Vec<[u8; 6]> = Vec::new();
|
|
||||||
let mut addr_map: Vec<([u8; 6], u16)> = Vec::new();
|
|
||||||
let mut resp_n = 0u64;
|
|
||||||
let mut pat_ctr: u32 = 0;
|
|
||||||
while start.elapsed() < Duration::from_secs(secs.max(120)) {
|
|
||||||
let play_start = Instant::now();
|
|
||||||
for (t, d, port, b) in frames.iter() {
|
|
||||||
let target_t = Duration::from_secs_f32(t - t0);
|
|
||||||
let now = play_start.elapsed();
|
|
||||||
if target_t > now { std::thread::sleep(target_t - now); }
|
|
||||||
let addr = format!("{}:{}", d, port);
|
|
||||||
// ★DMX 数据帧: 替换为我们自己的呼吸图案 (用认领 B12 加密 → Butler 解密后上灯)
|
|
||||||
let mut fb = b.clone();
|
|
||||||
if fb.len() == 1084 && fb[0x18] == 1 && fb[0x20] == 1 && fb[0x21] == 1 && fb[0x3C] != 1 {
|
|
||||||
pat_ctr += 1;
|
|
||||||
let tt = pat_ctr as f32 / 30.0;
|
|
||||||
let mut p1 = [0u8; 512];
|
|
||||||
for (k, &a) in [101u16, 123, 1].iter().enumerate() {
|
|
||||||
let phase = tt * 1.0 + k as f32 * 2.1;
|
|
||||||
let v = (((phase.sin() * 0.5 + 0.5) * 220.0) * (tt * 0.3).min(1.0)) as u8;
|
|
||||||
p1[(a - 1) as usize] = v;
|
|
||||||
}
|
|
||||||
let r1 = rng.next();
|
|
||||||
let r2 = rng.next();
|
|
||||||
fb[0x22..0x26].copy_from_slice(&r1.to_le_bytes());
|
|
||||||
fb[0x26..0x2A].copy_from_slice(&r2.to_le_bytes());
|
|
||||||
fb[0x3C..0x23C].copy_from_slice(&agios_dmx_encrypt(&p1, r1, bc));
|
|
||||||
fb[0x23C..0x43C].copy_from_slice(&agios_dmx_encrypt(&[0u8; 512], r2, bc));
|
|
||||||
}
|
|
||||||
let _ = sock.send_to(&fb, &addr);
|
|
||||||
// 收包
|
|
||||||
while let Ok((n, src)) = sock.recv_from(&mut buf) {
|
|
||||||
if src.ip().to_string() != target { continue; }
|
|
||||||
let bb = &buf[..n];
|
|
||||||
resp_n += 1;
|
|
||||||
if let Some((is_pulse, dd)) = rdm_region(bb) {
|
|
||||||
if is_pulse {
|
|
||||||
if let Some(uid) = decode_pulse(dd) {
|
|
||||||
if !seen_uids.contains(&uid) {
|
|
||||||
seen_uids.push(uid);
|
|
||||||
println!(" [F] ★发现灯具 UID={}", uid_s(&uid));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if dd.len() >= 0x18 + 19 && dd[0] == 0xCC && dd[0x15..0x17] == [0x00, 0x60] {
|
|
||||||
let src_uid = [dd[9], dd[10], dd[11], dd[12], dd[13], dd[14]];
|
|
||||||
let a = u16::from_be_bytes([dd[0x18 + 14], dd[0x18 + 15]]);
|
|
||||||
let fp = u16::from_be_bytes([dd[0x18 + 10], dd[0x18 + 11]]);
|
|
||||||
if !addr_map.iter().any(|(u, _)| *u == src_uid) {
|
|
||||||
addr_map.push((src_uid, a));
|
|
||||||
println!(" [F] ★{} → DMX地址 {} ({}通道)", uid_s(&src_uid), a, fp);
|
|
||||||
}
|
|
||||||
} else if dd.len() >= 0x18 + 2 && dd[0] == 0xCC && dd[0x15..0x17] == [0x00, 0xF0] {
|
|
||||||
let src_uid = [dd[9], dd[10], dd[11], dd[12], dd[13], dd[14]];
|
|
||||||
let a = u16::from_be_bytes([dd[0x18], dd[0x19]]);
|
|
||||||
if !addr_map.iter().any(|(u, _)| *u == src_uid) {
|
|
||||||
addr_map.push((src_uid, a));
|
|
||||||
println!(" [F] ★{} → DMX_START_ADDRESS={}", uid_s(&src_uid), a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("[F] 结束: 应答 {} 帧; 灯具 {} 台: {:?}; 地址: {:?}", resp_n, seen_uids.len(),
|
|
||||||
seen_uids.iter().map(uid_s).collect::<Vec<_>>(), addr_map);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut b12 = rng.next();
|
let mut b12 = rng.next();
|
||||||
let mut claim_ok = false;
|
let mut claim_ok = false;
|
||||||
|
|
||||||
|
|
@ -1159,12 +723,8 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
let mut tn: u8 = rng.byte();
|
let mut tn: u8 = rng.byte();
|
||||||
let mut qseq: u16 = 1;
|
let mut qseq: u16 = 1;
|
||||||
let mut seq: u32 = 1;
|
let mut seq: u32 = 1;
|
||||||
// 发现任务队列: ★窄分支优先 (已知灯具各一个只含自己的分支 — Butler 可能拒转全范围查询)
|
// 发现任务队列: (dest, cc, pid, pd) — None = 分支查询由 walk 状态机生成
|
||||||
let mut stack: Vec<(u64, u64)> = vec![(0, 0xFFFF_FFFF_FFFF)];
|
let mut stack: Vec<(u64, u64)> = vec![(0, 0xFFFF_FFFF_FFFF)];
|
||||||
for u in known.iter() {
|
|
||||||
let v = uid_u64(u);
|
|
||||||
stack.push((v - 1, v + 1));
|
|
||||||
}
|
|
||||||
let mut get_queue: Vec<[u8; 6]> = Vec::new();
|
let mut get_queue: Vec<[u8; 6]> = Vec::new();
|
||||||
let mut muted: Vec<[u8; 6]> = Vec::new();
|
let mut muted: Vec<[u8; 6]> = Vec::new();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
@ -1197,8 +757,8 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
last_key_t = t;
|
last_key_t = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- RDM 任务 (每 ~100ms 一发; 窄分支优先, t>3s 即开始快速定位问题) ---
|
// --- RDM 任务 (每 ~100ms 一发; ★SL 实测: 认领后 27.5s 纯 DMX, RDM 过早发可能重置武装计时) ---
|
||||||
if discovering && t > 3.0 && last_rdm.elapsed() >= Duration::from_millis(100) {
|
if discovering && t > 30.0 && last_rdm.elapsed() >= Duration::from_millis(100) {
|
||||||
last_rdm = Instant::now();
|
last_rdm = Instant::now();
|
||||||
if collecting && Instant::now() >= collect_until {
|
if collecting && Instant::now() >= collect_until {
|
||||||
// 本分支收集结束 → 判定
|
// 本分支收集结束 → 判定
|
||||||
|
|
@ -1244,7 +804,6 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
let _ = sock.send_to(&f, dst);
|
let _ = sock.send_to(&f, dst);
|
||||||
let mut f2 = f.clone();
|
let mut f2 = f.clone();
|
||||||
f2[0x30] = 0x01;
|
f2[0x30] = 0x01;
|
||||||
f2[0x49] = f2[0x49].wrapping_sub(1);
|
|
||||||
let _ = sock.send_to(&f2, dst);
|
let _ = sock.send_to(&f2, dst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1271,8 +830,6 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
let _ = sock.send_to(&f, dst);
|
let _ = sock.send_to(&f, dst);
|
||||||
let mut f2 = f.clone();
|
let mut f2 = f.clone();
|
||||||
f2[0x30] = 0x01;
|
f2[0x30] = 0x01;
|
||||||
// flag=01 副本的 srcUID 末字节 = fe (SL 实测: ff/fe 成对)
|
|
||||||
f2[0x49] = f2[0x49].wrapping_sub(1);
|
|
||||||
let _ = sock.send_to(&f2, dst);
|
let _ = sock.send_to(&f2, dst);
|
||||||
current_branch = Some((lo, hi));
|
current_branch = Some((lo, hi));
|
||||||
collecting = true;
|
collecting = true;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user