feat: show mode claim frame now fully self-constructed (zero opaque blocks)

build_claim_frame(): 552B AECH claim from scratch - envelope + host IP/MAC +
AECH + zeroed opaque regions + nonce@0x88 + B12@0x8C. Verified on real Butler:
AERE PASS, 3 fixtures online. Show mode no longer needs claim_h112 template.
This commit is contained in:
OpenCode 2026-09-11 15:03:09 +08:00
parent 44ff080ae6
commit c555a2252f

View File

@ -471,16 +471,14 @@ pub fn run(target: &str, bind_ip: &str, secs: u64) {
}
}
}
// 认领 (announce144 → AECH)
// 认领 (announce144 → AECH; claim 帧纯自构造: 不透明区填 0, CLAIM_ZERO 消融已证实 Butler 不校验)
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());
let o2a = [o2[0], o2[1], o2[2], o2[3]];
let ch = build_claim_frame(o2a, nonce, bc);
sock.send_to(&ch, &dst).unwrap();
// ★验证 AERE 应答 (RDM 不加密, 认领失败 RDM 照样通但 DMX 解密失败 → 灯白色兜底)
let mut aere_ok = false;
@ -936,7 +934,7 @@ pub fn run(target: &str, bind_ip: &str, secs: u64) {
ident[0x1C..0x20].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]);
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");
@ -1633,6 +1631,28 @@ fn send_key_burst(sock: &UdpSocket) {
}
}
/// ★AECH 认领质询帧 (552B) 从零构造 — 2026-09-11 全字段破解:
/// 不透明区 A(0x2C-0x87)/B(0x90-0x123) Butler 不校验 (CLAIM_ZERO 消融实证: 清零后 AERE 仍 PASS), 填 0
fn build_claim_frame(host_ip: [u8; 4], nonce: u32, b12: u32) -> Vec<u8> {
let mut f = Vec::with_capacity(552);
f.extend_from_slice(b"ENet");
f.extend_from_slice(&2u32.to_le_bytes()); // type=2
f.extend_from_slice(&[0x00, 0x00, 0x1c, 0x7c]); // 目标端口 31772
f.extend_from_slice(&[0u8; 4]);
f.extend_from_slice(&[0x00, 0x00, 0x01, 0x00]);
f.extend_from_slice(&552u32.to_le_bytes()); // 0x14 len
f.extend_from_slice(&[0x05, 0x00]); // 0x18 sub=05
f.extend_from_slice(&[host_ip[3], host_ip[2], host_ip[1], host_ip[0]]); // 0x1A 主机IP 逆序
f.extend_from_slice(&[0x30, 0x0e, 0xd5, 0x59, 0xbb, 0x36]); // 0x1E 主机MAC (本机)
f.extend_from_slice(&[0u8; 4]); // 0x24
f.extend_from_slice(b"AECH"); // 0x28
f.extend_from_slice(&[0u8; 0x88 - 0x2C]); // 0x2C-0x87 不透明区A = 0
f.extend_from_slice(&nonce.to_le_bytes()); // 0x88 nonce
f.extend_from_slice(&b12.to_le_bytes()); // 0x8C B12 (DMX rot 基值)
f.extend_from_slice(&[0u8; 552 - 0x90]); // 0x90-0x227 不透明区B+零填充
f
}
/// 统一会话环 (quick): DMX 30fps 持续 + RDM 穿插
/// 阶段: 0-2s 黑场(设备预热) → UN_MUTE + UNIQUE_BRANCH 二分发现 → MUTE 已发现灯具
/// → GET DEVICE_INFO/DMX 地址 → 呼吸图案控制 (未发现则用已知地址)