//! hostdemo.rs — 主机侧等效示例程序 (2026-09-10): 认领真机 + RDM 发现 + 加密 DMX 控制 //! //! 全流程不依赖 SymphoLight: //! 1. 发现 (type=1, 180B) → 217B 应答 //! 2. sub=01 宣告 → 89B 注册应答 //! 3. AECH 认领 ×3 轮 (nonce/B12 自选, AERE[0xC0]==G(nonce) 逐轮验证) //! ★ 真机 .1 只应答源 IP=192.168.123.113 的 AECH (固件过滤, §3.7) //! 4. RDM 发现: E1.20 DISC_UNIQUE_BRANCH 二分遍历 + DISC 脉冲解码 (§5.4 逆运算) //! → 每 UID 发 DISC_MUTE(0002) 收 70B ACK → GET DEVICE_INFO/DMX_START_ADDRESS/DEVICE_LABEL //! 5. DMX 控制: 30fps zPprv0TXRRW 加密帧 (§4.1), 按发现的灯具地址做呼吸图案 //! //! 注意: 真机控灯! 且 SymphoLight 若同时运行会插黑场帧 (两主机流交替 → 闪烁)。 use std::net::UdpSocket; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use crate::aech::{agios_dmx_encrypt, agios_g}; use crate::enet::{load_template, open_reuse}; const HOST_RDM_UID: [u8; 6] = [0x45, 0x3a, 0x7f, 0xff, 0xff, 0xff]; // 与真主机一致 (§5.3) const BCAST_UID: [u8; 6] = [0xff; 6]; /// 真机冷启动实测: AECH 认领前主机先发 102B type=2 身份帧 (cold2 @38.087) /// 主机 IP@0x1C (LE), MAC@0x20 (模板值即本机 123 网卡) const IDENT102: [u8; 102] = [ 0x45, 0x4e, 0x65, 0x74, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x66, 0x00, 0x00, 0x00, 0x01, 0x00, 0x71, 0x7b, 0xa8, 0xc0, 0x30, 0x0e, 0xd5, 0x59, 0xbb, 0x36, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; /// ★type=02 sub=07 绑定帧 (52B, sym_claim2 @351.1/353.4 — 认领前 2s 各一发): /// 主机IP+MAC@0x1A/0x1E, 设备IP+MAC@0x25/0x29, ffff/0000@0x2F (两发唯一差异), 6400 f401@0x31 /// (0x64=100, 0x01f4=500 — 疑似刷新率参数, 原样保留) fn build_bind52(flag16: [u8; 2]) -> Vec { let mut f = Vec::with_capacity(52); f.extend_from_slice(b"ENet"); f.extend_from_slice(&2u32.to_le_bytes()); f.extend_from_slice(&[0x00, 0x00, 0x1c, 0x7c]); f.extend_from_slice(&[0u8; 4]); f.extend_from_slice(&[0x00, 0x00, 0x01, 0x00]); f.extend_from_slice(&52u32.to_le_bytes()); f.extend_from_slice(&[0x07, 0x00]); // sub=07 f.extend_from_slice(&[0x71, 0x7b, 0xa8, 0xc0]); // 占位: 主机IP 逆序字节 (patch) f.extend_from_slice(&[0x30, 0x0e, 0xd5, 0x59, 0xbb, 0x36]); // 主机MAC f.push(0x00); f.extend_from_slice(&[0x01, 0x7b, 0xa8, 0xc0]); // 设备IP LE = 192.168.123.1 f.extend_from_slice(&[0x00, 0x16, 0x1c, 0xf1, 0x2e, 0x39]); // 设备MAC f.extend_from_slice(&flag16); f.extend_from_slice(&[0x64, 0x00, 0xf4, 0x01]); f } fn g16(b: &[u8], o: usize) -> u16 { u16::from_le_bytes([b[o], b[o + 1]]) } fn g32(b: &[u8], o: usize) -> u32 { u32::from_le_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]]) } fn uid_u64(u: &[u8; 6]) -> u64 { u64::from_be_bytes([0, 0, u[0], u[1], u[2], u[3], u[4], u[5]]) } fn u64_uid(v: u64) -> [u8; 6] { let b = v.to_be_bytes(); [b[2], b[3], b[4], b[5], b[6], b[7]] } fn hexs(b: &[u8]) -> String { b.iter().map(|x| format!("{:02x}", x)).collect() } fn uid_s(u: &[u8; 6]) -> String { hexs(u) } struct Rng(u32); impl Rng { fn new() -> Self { Rng(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos() | 1) } fn next(&mut self) -> u32 { self.0 ^= self.0 << 13; self.0 ^= self.0 >> 17; self.0 ^= self.0 << 5; self.0 & 0x7FFF_FFFF } fn byte(&mut self) -> u8 { (self.next() >> 8) as u8 } } /// E1.20 请求构建: 在 1084B 事件帧模板上 patch RDM 消息 (§4.2/§5.3) /// frame: 0x22 qseq(LE), 0x34=msgLen+1(LE), 0x3C 起 = SubSC/msgLen/destUID/srcUID/TN/... fn build_rdm_frame(dest: &[u8; 6], tn: u8, qseq: u16, cc: u8, pid: u16, pd: &[u8]) -> Vec { let mut f = load_template("rdm_disc_req"); // 1084B, flag=00 独立帧形态 let msg_len = 24 + pd.len(); // CC..PD 末 (不含 checksum) // ★RDM 请求帧头与 DMX 帧不同 (SL 实时帧对照): 计数器/随机槽位必须清零 f[0x1A..0x20].copy_from_slice(&[0u8; 6]); // 0x1A 计数器(会话内从0) + 0x1E-0x1F f[0x24..0x2A].copy_from_slice(&[0u8; 6]); // 0x24-0x29 (DMX 帧的 r1/r2 槽位 = 0) f[0x22] = (qseq & 0xFF) as u8; f[0x23] = (qseq >> 8) as u8; f[0x30] = 0; // flag=00 独立查询帧 (真机只应答此类) f[0x34..0x38].copy_from_slice(&((msg_len + 1) as u32).to_le_bytes()); f[0x3C] = 0x01; // SubSC f[0x3D] = msg_len as u8; f[0x3E..0x44].copy_from_slice(dest); f[0x44..0x4A].copy_from_slice(&HOST_RDM_UID); f[0x4A] = tn; f[0x4B] = 0x00; // 独立帧 port 字段=00 (模板实证; flag=01 内嵌副本才是 01) f[0x4C] = 0x00; // msgCount f[0x4D] = 0; f[0x4E] = 0; // subDevice 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); // checksum: 含隐式 0xCC 起始字节, Σ(0x3C..0x53+PDL) (不含 checksum 槽位自身!), BE // ★历史 bug: 曾写成 0x3C..0x3C+msg_len 多算 1 字节(含模板残留旧 checksum) → Butler 不应答 let mut sum: u16 = 0xCC; for i in 0x3C..0x53 + pd.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; f } /// ★从零构造 DMX 数据帧 (1084B, 全字段已破解 — analyze hdr 实证): /// 帧头全部常量已知, 仅 0x1A 计数器(+1/帧) 与 0x22/0x26 r1/r2(加密随机) 变化 pub fn build_dmx_frame(p1: &[u8; 512], p2: &[u8; 512], r1: u32, r2: u32, b12: u32, ctr: u32) -> Vec { let mut f = vec![0u8; 1084]; f[0..4].copy_from_slice(b"ENet"); f[4] = 4; // type=4 f[0x0A] = 0x1c; f[0x0B] = 0x7c; // 目标端口 31772 (LE) f[0x12] = 1; // 常量 01 00 f[0x14] = 0x3c; f[0x15] = 0x04; // 帧长 1084 (u16 LE) f[0x18] = 1; // 常量 01 00 f[0x1A..0x1E].copy_from_slice(&ctr.to_le_bytes()); // 帧计数器 (+1/帧, 任意起始) f[0x20] = 1; f[0x21] = 1; // 端口使能 mask: port1+port2 f[0x22..0x26].copy_from_slice(&r1.to_le_bytes()); f[0x26..0x2A].copy_from_slice(&r2.to_le_bytes()); f[0x2E] = 2; // 输出口对序号 (SL 抓包=01, 本机实测 01/02 均可; 关键在数据区按端口分开) f[0x32] = 1; // 常量 f[0x35] = 2; f[0x37] = 2; // port1/port2 数据长度 = 512 (u16 LE ×2) f[0x38] = 1; // 常量 01 00 00 00 f[0x3C..0x23C].copy_from_slice(&agios_dmx_encrypt(p1, r1, b12)); f[0x23C..0x43C].copy_from_slice(&agios_dmx_encrypt(p2, r2, b12)); f } /// ★从零构造 RDM 请求帧 (1084B, 全字段已破解 — analyze rdm 实证): /// 计数器恒 0; 0x20=02 为 RDM 请求标志; 0x2A=0xCC 常量; checksum=0xCC+Σ(0x3C..0x53+PDL) @0x53+PDL /// msgLen=24+PDL (含隐式 0xCC 起始码); checksum 后任意填充 (Butler 不读) fn build_rdm_frame2(dest: &[u8; 6], tn: u8, qseq: u16, cc: u8, pid: u16, pd: &[u8]) -> Vec { let msg_len = 24 + pd.len(); let mut f = vec![0u8; 1084]; f[0..4].copy_from_slice(b"ENet"); f[4] = 4; f[0x0A] = 0x1c; f[0x0B] = 0x7c; f[0x12] = 1; f[0x14] = 0x3c; f[0x15] = 0x04; f[0x18] = 1; // 0x1A-0x1F: 计数器恒 0 f[0x20] = 2; // RDM 请求标志 f[0x22..0x24].copy_from_slice(&qseq.to_le_bytes()); f[0x2A] = 0xCC; // 常量 f[0x2E] = 1; // 0x30: 00 主帧 (成对副本=01) f[0x34..0x38].copy_from_slice(&((msg_len + 1) as u32).to_le_bytes()); f[0x38] = 1; // RDM 消息 @0x3C: SubSC/msgLen/dest/src/TN/port/msgCount/subDev/CC/PID/PDL/PD f[0x3C] = 1; f[0x3D] = msg_len as u8; f[0x3E..0x44].copy_from_slice(dest); f[0x44..0x4A].copy_from_slice(&HOST_RDM_UID); f[0x4A] = tn; 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..0x53 + pd.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; f } /// RDM 成对副本 (SL 实测: flag@0x30=01, port@0x4B=01, srcUID 末字节-1, checksum 不重算) fn rdm_pair_copy(f: &[u8]) -> Vec { let mut f2 = f.to_vec(); f2[0x30] = 0x01; f2[0x4B] = 0x01; f2[0x49] = f2[0x49].wrapping_sub(1); f2 } /// DISC 脉冲解码 (§5.4 逆运算): data = RDM 数据区 (0x2A 起), 返回 Some(UID) 或 None(冲突/非法) fn decode_pulse(d: &[u8]) -> Option<[u8; 6]> { if d.len() < 17 || d[0] != 0xAA { return None; } let mut uid = [0u8; 6]; let mut sum: u16 = 0; for i in 0..6 { let b1 = d[1 + 2 * i]; let b2 = d[2 + 2 * i]; // 编码有效性: b1 高位必含 0xAA 模式, b2 必含 0x55 — 违例即多机冲突 if b1 & 0xAA != 0xAA || b2 & 0x55 != 0x55 { return None; } uid[i] = (b1 & 0x55) | (b2 & 0xAA); sum = sum.wrapping_add(b1 as u16).wrapping_add(b2 as u16); } let cs_hi = (d[13] & 0x55) | (d[14] & 0xAA); let cs_lo = (d[15] & 0x55) | (d[16] & 0xAA); let cs = ((cs_hi as u16) << 8) | cs_lo as u16; if cs != sum { return None; } // 冲突/损坏 Some(uid) } /// 从 Butler→主机 应答帧提取 RDM 数据区 (§5.1): 返回 (is_pulse, data) fn rdm_region(b: &[u8]) -> Option<(bool, &[u8])> { if b.len() < 0x2E || &b[0..4] != b"ENet" || g32(b, 4) != 4 { return None; } if g16(b, 0x18) != 0x0002 { return None; } if &b[0x1A..0x1E] != b"Butl" { return None; } let rlen = g16(b, 0x28) as usize; if b.len() < 0x2A + rlen { return None; } let d = &b[0x2A..0x2A + rlen]; Some((d[0] == 0xAA, d)) } pub fn run(target: &str, bind_ip: &str, secs: u64) { let dst = format!("{}:31772", target); // HOSTDEMO_LISTEN: 实际 socket 绑定地址 (默认=bind_ip:31772)。 // 跨网段经 udprelay 中继时, Ubuntu 侧须绑本地地址 (如 0.0.0.0:31772), 帧内宣告 IP 仍为 bind_ip (123.113) let listen = std::env::var("HOSTDEMO_LISTEN").unwrap_or_else(|_| format!("{}:31772", bind_ip)); let sock = open_reuse(&listen); // 源 123.113:31772 (真机过滤+主机惯例) sock.set_read_timeout(Some(Duration::from_millis(5))).unwrap(); let mut rng = Rng::new(); let mut buf = [0u8; 4096]; println!("=== 主机侧等效示例: 目标 {} (源 {}, 监听 {}) ===", dst, bind_ip, listen); // ---- 1) 发现 ---- let disc = load_template("discovery_req"); sock.send_to(&disc, &dst).unwrap(); let mut dev_ok = false; for _ in 0..150 { if let Ok((n, src)) = sock.recv_from(&mut buf) { let b = &buf[..n]; if n >= 200 && &b[0..4] == b"ENet" && g32(b, 4) == 1 { println!("[1] 发现应答 {}B from {} (Butler 在线)", n, src); dev_ok = true; break; } } } 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)> = 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 = 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> = None; let mut base_rdm: Option> = 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 = 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 = 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 / show: 认领 + 用 SL 实帧骨架, 替换为我们的 DMX 数据 + 我们构造的 RDM // 区别: hybrid 保留原 SL 数据地址(101/123/1 可选替换); show 固定地址1-32炫彩呼吸 let mode = std::env::var("HOSTDEMO_MODE").unwrap_or_default(); if mode == "hybrid" || mode == "show" { let is_show = mode == "show"; let label = if is_show { "SHOW" } else { "H" }; // show: 全字段已破解, 零模板纯自构造; hybrid: 仍用 SL 实帧骨架做对照 let mut dmxf: Vec> = Vec::new(); let mut base_rdm: Option> = None; if !is_show { 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"); 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; claim 帧纯自构造: 不透明区填 0, CLAIM_ZERO 消融已证实 Butler 不校验) let o2: Vec = 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 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; let w = Instant::now(); while w.elapsed() < Duration::from_millis(700) { if let Ok((n, _)) = sock.recv_from(&mut buf) { let b = &buf[..n]; if n > 0xC4 && b.windows(4).any(|x| x == b"AERE") { let resp = g32(b, 0xC0); let want = agios_g(nonce); aere_ok = resp == want; println!("[{}] AERE resp=0x{:08x} want=0x{:08x} {}", label, resp, want, if aere_ok { "★PASS" } else { "FAIL" }); break; } } } if !aere_ok { println!("[{}] ⚠ 未收到有效 AERE — 认领可能失败, DMX 将无法解密!", label); } println!("[{}] 认领完成 B12=0x{:08x}; {}", label, bc, if is_show { "地址1-32 炫彩呼吸 (零模板纯自构造 DMX+RDM)" } else { "verbatim DMX + 模板 RDM" }); let rdm_t: Option> = base_rdm; 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 qseq: u16 = 0; let mut last_rdm = Instant::now() - Duration::from_secs(10); let mut step = 0usize; let mut addr_list: Vec = Vec::new(); let mut resp_n = 0u64; let mut state: Vec<([u8; 6], u16, u16)> = Vec::new(); let mut last_status = Instant::now() - Duration::from_secs(100); // ★消融实证: 周期宣告/发现广播是 RDM 代理开关 (nobcast 失败, nodmx 成功) let disc = load_template("discovery_req"); let mut ann = load_template("announce"); ann[0x41..0x45].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]); let mut last_bcast = Instant::now() - Duration::from_secs(10); while secs == 0 || start.elapsed() < Duration::from_secs(secs) { let t = start.elapsed().as_secs_f32(); if last_bcast.elapsed() >= Duration::from_millis(2500) { let _ = sock.send_to(&disc, "255.255.255.255:31772"); let _ = sock.send_to(&ann, &format!("{}:31772", target.rsplitn(2, '.').nth(1).unwrap_or("192.168.123").to_string() + ".255")); last_bcast = Instant::now(); } // verbatim DMX 循环 // DMX 数据替换 let mut p1 = [0u8; 512]; if std::env::var("SHOW_DIAG").unwrap_or_default() == "1" { // 诊断: ch1-32 全灭3s / 全亮3s 交替 — 验证 DMX 是否到达灯具 let v = if (t as u32 / 3) % 2 == 0 { 0u8 } else { 255u8 }; for i in 0..32 { p1[i] = v; } } else if is_show { // show: 地址 1-32 炫彩呼吸波 (周期放慢, 更舒缓) let breathe = (t * 0.45).sin() * 0.5 + 0.5; for i in 0..32usize { let wave = ((t * 0.6 + i as f32 * 0.55).sin() * 0.5 + 0.5); p1[i] = (wave * breathe * 255.0) as u8; } } else if std::env::var("SHOW_PATTERN").unwrap_or_default() == "1" { for (k, &a) in [101u16, 123, 1].iter().enumerate() { let phase = t * 1.0 + k as f32 * 2.1; let v = (((phase.sin() * 0.5 + 0.5) * 220.0) * (t * 0.3).min(1.0)) as u8; p1[(a - 1) as usize] = v; } } let rr1 = rng.next(); let rr2 = rng.next(); // show 模式: 图案同时输出到 port1+port2 (灯具可能在任一口, 实测 port2 出灯) let mut p2 = [0u8; 512]; if is_show || std::env::var("SHOW_PORT2").unwrap_or_default() == "1" { p2 = p1; } let f = if is_show { // ★纯自构造 DMX 帧 (零模板) build_dmx_frame(&p1, &p2, rr1, rr2, bc, di as u32) } else { let mut fx = dmxf[di % dmxf.len()].clone(); fx[0x1A..0x1E].copy_from_slice(&(di as u32).to_le_bytes()); fx[0x22..0x26].copy_from_slice(&rr1.to_le_bytes()); fx[0x26..0x2A].copy_from_slice(&rr2.to_le_bytes()); fx[0x3C..0x23C].copy_from_slice(&agios_dmx_encrypt(&p1, rr1, bc)); fx[0x23C..0x43C].copy_from_slice(&agios_dmx_encrypt(&[0u8; 512], rr2, bc)); fx }; 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 pd: Vec = 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 tn = (step * 7 + 1) as u8; let (f, f2) = if is_show { // ★纯自构造 RDM 帧 (零模板) + 成对副本 qseq = qseq.wrapping_add(1); let f = build_rdm_frame2(&dest, tn, qseq, cc, pid, &pd); let f2 = rdm_pair_copy(&f); (f, f2) } else { let mut f = rdm_t.clone().unwrap(); 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] = tn; 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); (f, f2) }; let _ = sock.send_to(&f, &dst); let _ = sock.send_to(&f2, &dst); println!("[{}] RDM step={} pid={:04x}", label, 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_show { // show 模式维护状态表 if is_pulse { if let Some(uid) = decode_pulse(d) { println!(" [{}] ★脉冲 UID={}", label, uid_s(&uid)); } } else if d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0x60] { let uid = [d[9], d[10], d[11], d[12], d[13], d[14]]; let a = u16::from_be_bytes([d[0x18 + 14], d[0x18 + 15]]); let fp = u16::from_be_bytes([d[0x18 + 10], d[0x18 + 11]]); if let Some(e) = state.iter_mut().find(|(u, _, _)| *u == uid) { if e.1 != a || e.2 != fp { println!(" [{}] ★{} 状态变更: 地址 {}→{} 通道 {}→{}", label, uid_s(&uid), e.1, a, e.2, fp); e.1 = a; e.2 = fp; } } else { state.push((uid, a, fp)); println!(" [{}] ★设备注册: {} 地址={} 通道={} 传感器={}", label, uid_s(&uid), a, fp, d[0x18 + 18]); } } else if d.len() >= 0x18 + 2 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0xF0] { let uid = [d[9], d[10], d[11], d[12], d[13], d[14]]; let a = u16::from_be_bytes([d[0x18], d[0x19]]); if let Some(e) = state.iter_mut().find(|(u, _, _)| *u == uid) { if e.1 != a { println!(" [{}] ★{} START_ADDRESS 变更: {}→{}", label, uid_s(&uid), e.1, a); e.1 = a; } } } } else { 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()); } } } } // show 模式每 10s 打印状态汇总 if is_show && last_status.elapsed() >= Duration::from_secs(10) { last_status = Instant::now(); let lines: Vec = state.iter().map(|(u, a, fp)| format!("{} @DMX{} ({}ch)", uid_s(u), a, fp)).collect(); println!("[SHOW] 状态: {} 台 | {} | 收帧{}", state.len(), lines.join(" | "), resp_n); } std::thread::sleep(Duration::from_millis(33)); } println!("[{}] 结束: 应答 {} 帧, 地址 {:?}", label, resp_n, if is_show { state.iter().map(|(_, a, _)| *a).collect::>() } else { addr_list.clone() }); 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)> = 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 = 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::>(), addr_map); return; } // slbisect: SL 原帧逐字段突变 — 定位 Butler 拒收我们 RDM 帧的确切字段 // 突变序列: verbatim(对照) → counter → TN → qseq → 全部 → 纯我们构造 if std::env::var("HOSTDEMO_MODE").unwrap_or_default() == "slbisect" { // 认领 let o2: Vec = 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!("[B] 认领完成 B12=0x{:08x}", bc); // verbatim DMX 背景流 + 突变 branch 查询 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::new(); 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 byte = |o: usize| u8::from_str_radix(&hex[o * 2..o * 2 + 2], 16).unwrap(); if byte(0x18) == 1 && byte(0x20) == 1 && byte(0x21) == 1 && byte(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); } } let sl_branch = std::fs::read_to_string("C:/Users/tonycao/AppData/Local/Temp/opencode/sl_branch_exact.hex") .unwrap_or_else(|_| String::new()); let mut slb: Vec = Vec::new(); { let h = sl_branch.trim(); for i in (0..h.len()).step_by(2) { slb.push(u8::from_str_radix(&h[i..i + 2], 16).unwrap()); } } // NFS1K2D GET DEVICE_INFO 探针 (被 MUTE 的设备也须应答 GET — 应答可观测) let sl_get = std::fs::read_to_string("C:/Users/tonycao/AppData/Local/Temp/opencode/sl_get_exact.hex") .unwrap_or_default(); let mut slg: Vec = Vec::new(); { let h = sl_get.trim(); for i in (0..h.len()).step_by(2) { slg.push(u8::from_str_radix(&h[i..i + 2], 16).unwrap()); } } let known: [[u8; 6]; 3] = [ [0x01, 0x07, 0x34, 0x67, 0x01, 0x25], [0x01, 0x07, 0x34, 0x88, 0x09, 0x00], [0x08, 0x99, 0x50, 0x00, 0x26, 0xc9], ]; println!("[B] 开始突变序列 (GET→NFS; 每步 1s DMX 背景 + 发送 + 500ms 侦听)"); let mut di = 0usize; let mutations = ["verbatim对照", "counter=ours", "TN=ours", "qseq=ours", "全突变"]; for round in 0..2 { for (mi, mname) in mutations.iter().enumerate() { let seg = Instant::now(); while seg.elapsed() < Duration::from_millis(1000) { 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); } std::thread::sleep(Duration::from_millis(33)); while sock.recv_from(&mut buf).is_ok() {} } let mut f = slg.clone(); let ctr = (di as u32).wrapping_add(9000); match mi { 1 => { f[0x1A..0x1E].copy_from_slice(&ctr.to_le_bytes()); } 2 => { f[0x4A] = 0x77; } 3 => { f[0x22] = 0x55; f[0x23] = 0x00; } 4 => { f[0x1A..0x1E].copy_from_slice(&ctr.to_le_bytes()); f[0x4A] = 0x78; f[0x22] = 0x56; f[0x23] = 0x00; } _ => {} } let _ = sock.send_to(&f, &dst); let mut f2 = f.clone(); f2[0x30] = 0x01; f2[0x49] = f2[0x49].wrapping_sub(1); let _ = sock.send_to(&f2, &dst); let mut info = false; let mut any = 0usize; let w = Instant::now(); // ★边发 DMX 边侦听 (Butler 仅在 DMX 流持续时吐应答, 停流即饿死) while w.elapsed() < Duration::from_millis(500) { 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); } while let Ok((n, src)) = sock.recv_from(&mut buf) { if src.ip().to_string() != target { continue; } let b = &buf[..n]; any += 1; if let Some((false, d)) = rdm_region(b) { if d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0x60] { info = true; } } } std::thread::sleep(Duration::from_millis(33)); } println!(" [B] r{} {:<14} → 收帧 {} DEVICE_INFO={}", round, mname, any, info); } } let _ = known; return; } // show: 地址 1-32 炫彩呼吸持续播放 + 并行 RDM 状态轮询 (DEVICE_INFO / DMX 地址) // 旧的 show 实现已合并到上方 hybrid/show 块;保留结构,条件永假 if false { let o2: Vec = bind_ip.split('.').map(|x| x.parse().unwrap()).collect(); let bcast_subnet = format!("{}:31772", target.rsplitn(2, '.').nth(1).unwrap_or("192.168.123").to_string() + ".255"); // ---- 冷启动前奏 (默认模式同款; RDM 代理门控, 短前奏 RDM 无应答) ---- let pre_secs: u64 = std::env::var("HOSTDEMO_PRE").ok().and_then(|s| s.parse().ok()).unwrap_or(30); let disc = load_template("discovery_req"); let euce = load_template("euce"); let pre = Instant::now(); let mut last = Instant::now() - Duration::from_secs(10); let mut last_euce = Instant::now() - Duration::from_secs(10); while pre.elapsed() < Duration::from_secs(pre_secs) { if last.elapsed() >= Duration::from_secs(2) { let _ = sock.send_to(&disc, "255.255.255.255:31772"); let mut ann = build_fresh_announce(&mut rng); ann[0x41..0x45].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]); let _ = sock.send_to(&ann, &bcast_subnet); last = Instant::now(); } if last_euce.elapsed() >= Duration::from_secs(6) { let _ = sock.send_to(&euce, "255.255.255.255:31772"); last_euce = Instant::now(); } if let Ok((n, src)) = sock.recv_from(&mut buf) { println!(" [SHOW/前奏] 收帧 len={} src={}", n, src); } } for flag in [[0xff_u8, 0xff_u8], [0x00, 0x00], [0xff, 0xff], [0x00, 0x00]] { let mut b = build_bind52(flag); b[0x1A..0x1E].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]); sock.send_to(&b, &dst).unwrap(); std::thread::sleep(Duration::from_millis(320)); } std::thread::sleep(Duration::from_millis(1800)); let mut ident = IDENT102.to_vec(); 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"); 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!("[SHOW] 认领完成 B12=0x{:08x}; 地址1-32 炫彩呼吸 + RDM 状态轮询 (Ctrl+C 停止)", bc); let start = Instant::now(); // SL 实帧 RDM 模板 (自构造帧 Butler 不响应, SL 帧响应 — 实测结论) let sl_rdm_tpl: Option> = std::fs::read_to_string("C:/Users/tonycao/AppData/Local/Temp/opencode/sl_get_exact.hex").ok().map(|h| { let h = h.trim(); let mut v = Vec::new(); for i in (0..h.len()).step_by(2) { v.push(u8::from_str_radix(&h[i..i + 2], 16).unwrap()); } v }); let disc = load_template("discovery_req"); let mut ann = load_template("announce"); ann[0x41..0x45].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]); let known: [[u8; 6]; 3] = [ [0x01, 0x07, 0x34, 0x67, 0x01, 0x25], [0x01, 0x07, 0x34, 0x88, 0x09, 0x00], [0x08, 0x99, 0x50, 0x00, 0x26, 0xc9], ]; // 状态表: UID → (DMX地址, 通道数) let mut state: Vec<([u8; 6], u16, u16)> = Vec::new(); let mut di = 0usize; let mut last_bcast = Instant::now() - Duration::from_secs(10); let mut last_hb = Instant::now() - Duration::from_secs(10); let mut last_rdm = Instant::now() - Duration::from_secs(10); let mut poll_idx = 0usize; let mut addr_req_pending = false; let mut last_status = Instant::now() - Duration::from_secs(100); let mut resp_n: u64 = 0; loop { // 广播 (RDM 代理开关) if last_bcast.elapsed() >= Duration::from_millis(2500) { let _ = sock.send_to(&disc, "255.255.255.255:31772"); let _ = sock.send_to(&ann, &format!("{}:31772", target.rsplitn(2, '.').nth(1).unwrap_or("192.168.123").to_string() + ".255")); last_bcast = Instant::now(); } // 心跳 if last_hb.elapsed() >= Duration::from_secs(1) { let _ = sock.send_to(&load_template("heartbeat"), &dst); last_hb = Instant::now(); } // RDM 轮询: 每 700ms 一步, 轮转 3 台 × (DEVICE_INFO / START_ADDRESS) if last_rdm.elapsed() >= Duration::from_millis(700) { last_rdm = Instant::now(); let uid = known[poll_idx % 3]; let (pid, cc) = if addr_req_pending { (0x00F0u16, 0x20u8) } else { (0x0060, 0x20) }; // RDM 请求用 SL 实帧模板 (实测: 自构造帧 Butler 不响应, SL 帧响应) let mut f = match &sl_rdm_tpl { Some(t) => { let mut t = t.clone(); t[0x1A..0x1E].copy_from_slice(&(di as u32).to_le_bytes()); t[0x3D] = 24; // msgLen (PDL=0) t[0x3E..0x44].copy_from_slice(&uid); t[0x4A] = (poll_idx * 13 + 1) as u8; t[0x4F] = cc; t[0x50] = (pid >> 8) as u8; t[0x51] = (pid & 0xFF) as u8; t[0x52] = 0; let mut sum: u16 = 0xCC; for i in 0x3C..0x3C + 24 { sum = sum.wrapping_add(t[i] as u16); } t[0x5F] = (sum >> 8) as u8; t[0x60] = (sum & 0xFF) as u8; t } None => build_rdm_frame(&uid, (poll_idx * 13 + 1) as u8, poll_idx as u16, cc, pid, &[]), }; 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); poll_idx += 1; if poll_idx % 3 == 0 { addr_req_pending = !addr_req_pending; } } // 收包: 解析 DEVICE_INFO / START_ADDRESS, 报告变更 while let Ok((n, src)) = sock.recv_from(&mut buf) { if src.ip().to_string() != target { continue; } let b = &buf[..n]; if let Some((false, d)) = rdm_region(b) { if d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0x60] { let uid = [d[9], d[10], d[11], d[12], d[13], d[14]]; let a = u16::from_be_bytes([d[0x18 + 14], d[0x18 + 15]]); let fp = u16::from_be_bytes([d[0x18 + 10], d[0x18 + 11]]); match state.iter_mut().find(|(u, _, _)| *u == uid) { Some(e) => { if e.1 != a || e.2 != fp { println!(" [SHOW] ★{} 状态变更: 地址 {}→{} 通道 {}→{}", uid_s(&uid), e.1, a, e.2, fp); e.1 = a; e.2 = fp; } } None => { state.push((uid, a, fp)); println!(" [SHOW] ★设备注册: {} 地址={} 通道={} 传感器={}", uid_s(&uid), a, fp, d[0x18 + 18]); } } } else if d.len() >= 0x18 + 2 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0xF0] { let uid = [d[9], d[10], d[11], d[12], d[13], d[14]]; let a = u16::from_be_bytes([d[0x18], d[0x19]]); if let Some(e) = state.iter_mut().find(|(u, _, _)| *u == uid) { if e.1 != a { println!(" [SHOW] ★{} START_ADDRESS 变更: {}→{}", uid_s(&uid), e.1, a); e.1 = a; } } } } } // 每 10s 打印一次状态汇总 if last_status.elapsed() >= Duration::from_secs(10) { last_status = Instant::now(); let lines: Vec = state.iter().map(|(u, a, fp)| format!("{} @DMX{} ({}ch)", uid_s(u), a, fp)).collect(); println!("[SHOW] 状态: {} 台 | {} | 收帧{}", state.len(), lines.join(" | "), resp_n); } // DMX @30fps: 地址 1-32 彩虹波 × 呼吸包络 let t = start.elapsed().as_secs_f32(); di += 1; let mut p1 = [0u8; 512]; let breathe = (t * 0.9).sin() * 0.5 + 0.5; // 全局呼吸包络 0..1 for i in 0..32usize { let wave = ((t * 1.2 + i as f32 * 0.55).sin() * 0.5 + 0.5); // 行波 p1[i] = (wave * breathe * 255.0) as u8; } let rr1 = rng.next(); let rr2 = rng.next(); let mut f = load_template("dmx_a"); f[0x1A..0x1E].copy_from_slice(&(di as u32).to_le_bytes()); f[0x22..0x26].copy_from_slice(&rr1.to_le_bytes()); f[0x26..0x2A].copy_from_slice(&rr2.to_le_bytes()); f[0x3C..0x23C].copy_from_slice(&agios_dmx_encrypt(&p1, rr1, bc)); f[0x23C..0x43C].copy_from_slice(&agios_dmx_encrypt(&[0u8; 512], rr2, bc)); let _ = sock.send_to(&f, &dst); std::thread::sleep(Duration::from_millis(33)); } } let mut b12 = rng.next(); let mut claim_ok = false; // claimonly/quick: 最小认领 (announce144 → 47B → AECH) — 实测成功配方, 无绑定/宣告/前奏 let hd_mode = std::env::var("HOSTDEMO_MODE").unwrap_or_default(); if hd_mode.starts_with("claim") || hd_mode == "quick" { let o2: Vec = bind_ip.split('.').map(|x| x.parse().unwrap()).collect(); let full = hd_mode == "claimfull"; if full { // sub=07 绑定帧 ×4 (ffff→0000→ffff→0000, 0.32s 间隔) for flag in [[0xff_u8, 0xff_u8], [0x00, 0x00], [0xff, 0xff], [0x00, 0x00]] { let mut b = build_bind52(flag); b[0x1A..0x1E].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]); sock.send_to(&b, &dst).unwrap(); std::thread::sleep(Duration::from_millis(320)); } std::thread::sleep(Duration::from_millis(1500)); } let mut ident = IDENT102.to_vec(); ident[0x1C..0x20].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]); // runs1-3 成功配方: 仅 announce144 (102B 身份帧) 单播一次, 无绑定/EUCE/宣告 let ident144 = if full { ident } else { load_template("announce144") }; sock.send_to(&ident144, &dst).unwrap(); let mut reg = false; let w = Instant::now(); while w.elapsed() < Duration::from_millis(400) { if let Ok((n, src)) = sock.recv_from(&mut buf) { if src.ip().to_string() == target && n == 47 { reg = true; break; } } } // CLAIM_BINDFF=1: 认领前先发单个 bind52(ffff), 测试能否解除 Butler 拒答态 (bind 中毒恢复实验) if std::env::var("CLAIM_BINDFF").unwrap_or_default() == "1" { let mut b = build_bind52([0xff, 0xff]); b[0x1A..0x1E].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]); sock.send_to(&b, &dst).unwrap(); std::thread::sleep(Duration::from_millis(320)); println!("CLAIM_BINDFF: 已发 bind52(ffff)"); } let nonce = rng.next(); let b12c = 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(&b12c.to_le_bytes()); // CLAIM_ZERO=1: 清零不透明区 (0x2C-0x87, 0x90-0x123), 消融验证 Butler 是否校验 if std::env::var("CLAIM_ZERO").unwrap_or_default() == "1" { for i in 0x2C..0x88 { ch[i] = 0; } for i in 0x90..0x124 { ch[i] = 0; } println!("CLAIM_ZERO: 不透明区已清零 (240B)"); } sock.send_to(&ch, &dst).unwrap(); let w2 = Instant::now(); while w2.elapsed() < Duration::from_millis(700) { if let Ok((n, _)) = sock.recv_from(&mut buf) { let b = &buf[..n]; if n > 0xC4 && b.windows(4).any(|w| w == b"AERE") { let resp = g32(b, 0xC0); println!("CLAIM_PROBE reg89={} AERE resp=0x{:08x} G=0x{:08x} {}", reg, resp, agios_g(nonce), if resp == agios_g(nonce) { "PASS" } else { "FAIL" }); if resp == agios_g(nonce) { // quick 模式: 认领成功后直通 RDM+DMX 阶段 (跳过绑定/宣告/前奏 — 实测有毒) if hd_mode == "quick" { b12 = b12c; claim_ok = true; break; } } if !claim_ok { return; } } } } if !claim_ok { println!("CLAIM_PROBE reg89={} AERE=none FAIL", reg); return; } } // takeover: 跳过认领, 用 SL 上次会话的 B12 续发 DMX+心跳, 探会话是否可直接延续 if std::env::var("HOSTDEMO_MODE").unwrap_or_default() == "takeover" { let b12_old: u32 = 0x4366937a; // sym_startup @101.278 AECH 的 B12@0x8C (SL 22:52 会话) println!("[P] takeover: B12=0x{:08x} 续发 15s DMX(ch101 ramp)+心跳+RDM GET, 侦听回包", b12_old); let tpl = load_template("dmx_a"); let hb = load_template("heartbeat"); let known: [u8; 6] = [0x01, 0x07, 0x34, 0x67, 0x01, 0x25]; // NFS1K2D let start = Instant::now(); let mut seq: u32 = 5000; // 帧序号跳开, 避免与 SL 旧计数冲突 let mut last_hb = Instant::now() - Duration::from_secs(10); let mut rdm_done = false; while start.elapsed() < Duration::from_secs(15) { if last_hb.elapsed() >= Duration::from_secs(6) { let _ = sock.send_to(&hb, &dst); last_hb = Instant::now(); println!(" [P] 心跳已发"); } if !rdm_done && start.elapsed() > Duration::from_secs(5) { rdm_done = true; let f = build_rdm_frame(&known, 1, 1, 0x20, 0x0060, &[]); // GET DEVICE_INFO sock.send_to(&f, &dst).unwrap(); println!(" [P] RDM GET DEVICE_INFO 已发"); } let t = start.elapsed().as_secs_f32(); let mut p1 = [0u8; 512]; p1[100] = (((t * 0.8).sin() * 0.5 + 0.5) * 255.0) as u8; let p2 = [0u8; 512]; let r1 = rng.next(); let r2 = rng.next(); let mut f = tpl.clone(); f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes()); f[0x2E] = 2; f[0x2F] = 0; 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, b12_old)); f[0x23C..0x43C].copy_from_slice(&agios_dmx_encrypt(&p2, r2, b12_old)); let _ = sock.send_to(&f, &dst); seq += 1; while let Ok((n, src)) = sock.recv_from(&mut buf) { let b = &buf[..n]; println!(" [P] ← len={} src={} head={}", n, src, hexs(&b[..std::cmp::min(16, n)])); } std::thread::sleep(Duration::from_millis(33)); } return; } // ---- 2) 宣告: 真主机用 644B type=1 广播宣告 (announce 模板), 主机 IP@0x41 LE; // 再发 102B type=2 身份帧 (cold2 实测: 紧邻 AECH 之前), IP@0x1C LE ---- let mode = std::env::var("HOSTDEMO_MODE").unwrap_or_default(); let o: Vec = bind_ip.split('.').map(|x| x.parse().unwrap()).collect(); let bcast = format!("{}.255:31772", target.rsplitn(2, '.').nth(1).unwrap_or("192.168.123")); if mode != "skip2" && mode != "noclaim" { // 冷启动广播前奏 (cold2=38s, SymphoLight 实测 2.5min 成功; 10s 短前奏被拒!) // ★门控假说: Butler 须收到 ~13+ 轮 discovery/announce 才武装认领应答 // ★2026-09-11 消融证伪: 广播前奏不仅非必需而且有毒 (PRE=45 认领FAIL / PRE=0 PASS) // → 默认 PRE=0; 复现 SL 行为用 HOSTDEMO_PRE=45 let pre_secs: u64 = std::env::var("HOSTDEMO_PRE").ok().and_then(|s| s.parse().ok()).unwrap_or(0); // ★宣告帧用 announce_live 模板 + 新鲜时间戳/随机会话 ID (陈旧宣告 = 僵尸主机, 认领被无视) let euce = load_template("euce"); // 540B, 无主机相关字段 let pre = Instant::now(); let mut queries_dbg = 0; let mut last = Instant::now() - Duration::from_secs(10); let mut last_euce = Instant::now() - Duration::from_secs(10); while pre.elapsed() < Duration::from_secs(pre_secs) { if last.elapsed() >= Duration::from_secs(2) { let _ = sock.send_to(&disc, "255.255.255.255:31772"); let mut ann = build_fresh_announce(&mut rng); ann[0x41..0x45].copy_from_slice(&[o[3], o[2], o[1], o[0]]); // 主机IP逆序字节@0x41 if queries_dbg == 0 { queries_dbg = 1; let live = load_template("announce_live"); println!(" [dbg] ann timestamp区: ours={}", hexs(&ann[0x230..0x245])); println!(" [dbg] ann timestamp区: live={}", hexs(&live[0x230..0x245])); println!(" [dbg] ann sessid区: ours={}", hexs(&ann[0x219..0x226])); println!(" [dbg] ann sessid区: live={}", hexs(&live[0x219..0x226])); let diffs: Vec = (0..ann.len()).filter(|&i| ann[i] != live[i]).collect(); println!(" [dbg] ann 与 live 模板差异偏移 ({}处): {:?}", diffs.len(), diffs); } let _ = sock.send_to(&ann, &bcast); last = Instant::now(); } if last_euce.elapsed() >= Duration::from_secs(6) { // ★EUCE 每 6s (SymphoLight 启动期实测节奏, 30s 节奏认领被拒) let _ = sock.send_to(&euce, "255.255.255.255:31772"); last_euce = Instant::now(); } // 排空 (217B 发现应答 / 47B 注册等) if let Ok((n, src)) = sock.recv_from(&mut buf) { println!(" [dbg] 前奏期收帧 len={} src={}", n, src); } } // ★bind52×4 实测有毒 (2026-09-11 消融: skip2 认领PASS / 仅加bind×4 即 FAIL) — // ffff→0000→ffff→0000 以 0000(unbind) 收尾, Butler 进入未绑定态后无视 AECH。 // 默认不再发送; 复现实验用 HOSTDEMO_BIND=1 开启。 if std::env::var("HOSTDEMO_BIND").unwrap_or_default() == "1" { for flag in [[0xff_u8, 0xff_u8], [0x00, 0x00], [0xff, 0xff], [0x00, 0x00]] { let mut b = build_bind52(flag); b[0x1A..0x1E].copy_from_slice(&[o[3], o[2], o[1], o[0]]); sock.send_to(&b, &dst).unwrap(); std::thread::sleep(Duration::from_millis(320)); } std::thread::sleep(Duration::from_millis(1800)); } // IDENT102 常量帧 0x1A 起已是本机 IP+MAC — 不要再补丁 (旧代码 patch 0x1C 会踩坏 MAC 高2字节!) sock.send_to(&IDENT102, &dst).unwrap(); println!("[2] 冷启动前奏完成 ({}s 广播 + 身份帧; bind 已默认跳过=有毒)", pre_secs); } let mut reg_ok = false; for _ in 0..10 { if let Ok((n, _)) = sock.recv_from(&mut buf) { if n == 89 { reg_ok = true; break; } } } println!("[2] 宣告 → 注册应答: {}", if reg_ok { "OK (89B)" } else { "未收到 (继续)" }); // ---- 3) AECH 认领 ×3 (quick 模式已在上面认领成功; noclaim 跳过) ---- if !claim_ok { b12 = rng.next(); if mode == "noclaim" { println!("[3] noclaim 模式: 跳过 AECH, 直接以 RDM GET 探测连接态"); claim_ok = true; } for round in 0..3 { if mode == "noclaim" { break; } // ★每轮先发身份帧(announce144)等 47B 注册应答, 再发认领 — skip2 实测: 无身份帧直接 claim 被无视 let o2a = [o[0], o[1], o[2], o[3]]; sock.send_to(&load_template("announce144"), &dst).unwrap(); let wr = Instant::now(); let mut reg47 = false; while wr.elapsed() < Duration::from_millis(400) { if let Ok((n, _)) = sock.recv_from(&mut buf) { if n == 47 { reg47 = true; break; } } } let nonce = rng.next(); let ch = build_claim_frame(o2a, nonce, b12); // 排空缓存 (前奏自广播回收), 避免 AERE 到达前读循环耗尽 while sock.recv_from(&mut buf).is_ok() {} sock.send_to(&ch, &dst).unwrap(); let w = Instant::now(); let mut rx_n = 0u32; let mut rx_lens: Vec = Vec::new(); while w.elapsed() < Duration::from_millis(700) { if let Ok((n, _)) = sock.recv_from(&mut buf) { rx_n += 1; if rx_lens.len() < 8 { rx_lens.push(n); } let b = &buf[..n]; if n > 0xC4 && b.windows(4).any(|w| w == b"AERE") { let resp = g32(b, 0xC0); let want = agios_g(nonce); println!("[3] AECH 第{}轮: reg47={} resp=0x{:08x} G=0x{:08x} {}", round + 1, reg47, resp, want, if resp == want { "★PASS" } else { "FAIL" }); if resp == want { claim_ok = true; } break; } } } if !claim_ok { println!("[3] AECH 第{}轮: reg47={} 无AERE (等700ms内收{}帧 {:?})", round + 1, reg47, rx_n, rx_lens); } } } if !claim_ok { println!("[3] 认领失败 (真机只应答源 123.113 的 AECH), 终止"); return; } println!(" 认领通过, B12=0x{:08x}", b12); // quick 模式: 密钥广播 + 统一会话环 — DMX 持续流 (cold2 实证: RDM 代理仅在 DMX 流活跃时工作) if hd_mode == "quick" { println!("[K] 1947 许可密钥广播 4s..."); send_key_burst(&sock); session_loop(&sock, &dst, target, &mut rng, b12, secs); return; } // noclaim 连接态探测: 直接 GET DEVICE_INFO 已知 3 灯具, 验证 Butler 是否仍视我们为主机 if mode == "noclaim" { let known: [[u8; 6]; 3] = [ [0x01, 0x07, 0x34, 0x67, 0x01, 0x25], // NFS1K2D DMX101 [0x01, 0x07, 0x34, 0x88, 0x09, 0x00], // NFM680S DMX112 [0x08, 0x99, 0x50, 0x00, 0x26, 0xc9], // HMKJ DMX1-32 ]; let mut tn0: u8 = rng.byte(); for (i, uid) in known.iter().enumerate() { tn0 = tn0.wrapping_add(1); let f = build_rdm_frame(uid, tn0, (i + 1) as u16, 0x20, 0x0060, &[]); sock.send_to(&f, &dst).unwrap(); let w = Instant::now(); let mut got = false; while w.elapsed() < Duration::from_millis(500) { if let Ok((n, src)) = sock.recv_from(&mut buf) { if src.ip().to_string() != target { continue; } let b = &buf[..n]; println!(" [dbg] 收帧 len={} 0x18={:02x}{:02x}", n, b.get(0x19).copied().unwrap_or(0), b.get(0x18).copied().unwrap_or(0)); if let Some((false, d)) = rdm_region(b) { 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]]); println!(" ★ 连接态确认! UID={} DMX地址={}", uid_s(uid), a); got = true; break; } } } } if !got { println!(" UID={} 无应答", uid_s(uid)); } } return; } // ---- 4) RDM 发现 ---- let mut tn: u8 = rng.byte(); let mut qseq: u16 = 1; let mut fixtures: Vec<[u8; 6]> = Vec::new(); { // 4a) 先直查已知 UID 验证 RDM 代理通道 (灯具可能仍处 SL 会话留下的 MUTE 态) let known: [[u8; 6]; 3] = [ [0x01, 0x07, 0x34, 0x67, 0x01, 0x25], // NFS1K2D DMX101 [0x01, 0x07, 0x34, 0x88, 0x09, 0x00], // NFM680S DMX112 [0x08, 0x99, 0x50, 0x00, 0x26, 0xc9], // HMKJ DMX1-32 ]; println!("[4a] 直查已知 UID (GET DEVICE_INFO):"); for uid in &known { tn = tn.wrapping_add(1); let f = build_rdm_frame(uid, tn, qseq, 0x20, 0x0060, &[]); qseq += 1; sock.send_to(&f, &dst).unwrap(); let w = Instant::now(); let mut ok = false; while w.elapsed() < Duration::from_millis(400) { if let Ok((n, src)) = sock.recv_from(&mut buf) { if src.ip().to_string() != target { continue; } let b = &buf[..n]; if let Some((false, d)) = rdm_region(b) { 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 footprint = u16::from_be_bytes([d[0x18 + 10], d[0x18 + 11]]); println!(" ★ {} → DMX地址 {} / {}通道", uid_s(uid), a, footprint); if !fixtures.contains(uid) { fixtures.push(*uid); } ok = true; break; } } } } if !ok { println!(" {} → 无应答", uid_s(uid)); } } // 先广播 DISC_UN_MUTE (PID=0003): 稳态下主机已把全部灯具 MUTE, // 被 MUTE 的灯具不应答 UNIQUE_BRANCH → 须先解除静音才能重新发现 tn = tn.wrapping_add(1); let um = build_rdm_frame(&BCAST_UID, tn, qseq, 0x10, 0x0003, &[]); qseq += 1; sock.send_to(&um, &dst).unwrap(); std::thread::sleep(Duration::from_millis(80)); println!("[4] 已广播 DISC_UN_MUTE, 开始 UNIQUE_BRANCH 二分遍历"); let mut stack: Vec<(u64, u64)> = vec![(0, 0xFFFF_FFFF_FFFF)]; let mut queries = 0; let walk_start = Instant::now(); while let Some((lo, hi)) = stack.pop() { if queries >= 400 || walk_start.elapsed() > Duration::from_secs(120) { println!(" (发现遍历到达上限)"); break; } let mut pd = Vec::with_capacity(12); pd.extend_from_slice(&u64_uid(lo)); pd.extend_from_slice(&u64_uid(hi)); // 真机实测: 首个应答延迟可达 ~1.4s, 真主机对同一分支连发多次 → 每分支最多 4 次尝试 × 250ms 窗口 let mut found: Vec<[u8; 6]> = Vec::new(); let mut collision = false; for attempt in 0..4 { queries += 1; tn = tn.wrapping_add(1); let f = build_rdm_frame(&BCAST_UID, tn, qseq, 0x10, 0x0001, &pd); // DISC_UNIQUE_BRANCH qseq += 1; if queries == 1 { let t = load_template("rdm_disc_req"); let diffs: Vec = (0x2A..0x62).filter(|&i| f[i] != t[i]).collect(); println!(" [dbg] 首帧与模板差异偏移: {:?} (应仅 qseq/TN/srcUID末字节/PD/checksum)", diffs); } sock.send_to(&f, &dst).unwrap(); let w = Instant::now(); while w.elapsed() < Duration::from_millis(250) { if let Ok((n, src)) = sock.recv_from(&mut buf) { let b = &buf[..n]; if src.ip().to_string() != target { continue; } if let Some((is_pulse, d)) = rdm_region(b) { if is_pulse { match decode_pulse(d) { Some(uid) => { if !found.contains(&uid) { found.push(uid); } } None => collision = true, } } } } } if !found.is_empty() || collision { break; } let _ = attempt; } if collision { let mid = lo + (hi - lo) / 2; stack.push((mid + 1, hi)); stack.push((lo, mid)); } else if found.len() == 1 { let uid = found[0]; if found_valid(&uid) { println!(" [发现] UID={} (深度 {} 次查询后)", uid_s(&uid), queries); // DISC_MUTE (PID=0002) → 70B 参数 ACK tn = tn.wrapping_add(1); let f2 = build_rdm_frame(&uid, tn, qseq, 0x10, 0x0002, &[]); qseq += 1; sock.send_to(&f2, &dst).unwrap(); let w2 = Instant::now(); let mut acked = false; while w2.elapsed() < Duration::from_millis(400) { if let Ok((n, src)) = sock.recv_from(&mut buf) { if src.ip().to_string() != target { continue; } let b = &buf[..n]; if let Some((false, d)) = rdm_region(b) { if d.len() >= 0x18 && d[0] == 0xCC && d[0x14] == 0x11 && d[0x15..0x17] == [0x00, 0x02] { acked = true; break; } } } } println!(" MUTE(0002) ACK: {}", if acked { "OK" } else { "未收到" }); fixtures.push(uid); } } else if found.len() > 1 { let mid = lo + (hi - lo) / 2; stack.push((mid + 1, hi)); stack.push((lo, mid)); } // found==0: 空分支 } println!("[4] RDM 发现完成: {} 台灯具, {} 次查询, {:.1}s", fixtures.len(), queries, walk_start.elapsed().as_secs_f32()); } // ---- 4b) 读参数: DEVICE_INFO / DMX_START_ADDRESS / DEVICE_LABEL ---- let mut addr_list: Vec = Vec::new(); for uid in &fixtures { tn = tn.wrapping_add(1); let f = build_rdm_frame(uid, tn, qseq, 0x20, 0x0060, &[]); // GET DEVICE_INFO qseq += 1; sock.send_to(&f, &dst).unwrap(); let mut dmx_addr: Option = None; let mut footprint = 0u16; let mut sensors = 0u8; let w = Instant::now(); while w.elapsed() < Duration::from_millis(400) { if let Ok((n, src)) = sock.recv_from(&mut buf) { if src.ip().to_string() != target { continue; } let b = &buf[..n]; if let Some((false, d)) = rdm_region(b) { // d: CC@0 ... CC字段@0x14, PID@0x15-16, PDL@0x17, PD@0x18 if d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x14] == 0x21 && d[0x15..0x17] == [0x00, 0x60] { let pd = &d[0x18..0x18 + 19]; footprint = u16::from_be_bytes([pd[10], pd[11]]); dmx_addr = Some(u16::from_be_bytes([pd[14], pd[15]])); sensors = pd[18]; break; } } } } // GET DMX_START_ADDRESS 复核 tn = tn.wrapping_add(1); let f2 = build_rdm_frame(uid, tn, qseq, 0x20, 0x00F0, &[]); qseq += 1; sock.send_to(&f2, &dst).unwrap(); let w2 = Instant::now(); while w2.elapsed() < Duration::from_millis(400) { if let Ok((n, src)) = sock.recv_from(&mut buf) { if src.ip().to_string() != target { continue; } let b = &buf[..n]; if let Some((false, d)) = rdm_region(b) { if d.len() >= 0x18 + 2 && d[0] == 0xCC && d[0x14] == 0x21 && d[0x15..0x17] == [0x00, 0xF0] { let a = u16::from_be_bytes([d[0x18], d[0x19]]); if dmx_addr.is_none() { dmx_addr = Some(a); } break; } } } } match dmx_addr { Some(a) => { println!(" 灯具 {} → DMX 地址 {} (占用 {} 通道, {} 传感器)", uid_s(uid), a, footprint, sensors); addr_list.push(a); } None => println!(" 灯具 {} → 参数读取超时", uid_s(uid)), } } // ---- 5) DMX 控制: 呼吸图案 (按发现的灯具地址; 无发现则用已知 101/112/1) ---- if secs == 0 { println!("[5] secs=0, 跳过 DMX 控制"); return; } if addr_list.is_empty() { addr_list = vec![101, 112, 1]; println!("[5] 无发现结果, 用已知地址 101/112/1"); } println!("[5] DMX 控制 {}s @30fps: 呼吸图案 @地址 {:?} (zPprv0TXRRW 加密, B12=0x{:08x})", secs, addr_list, b12); println!(" ⚠ SymphoLight 若同时运行会插黑场帧造成闪烁; 关其输出可得纯净效果"); let tpl = load_template("dmx_a"); let hb = load_template("heartbeat"); // 36B type=02 sub=03 心跳 (会话保活, 每 ~6s 单播) let start = Instant::now(); let mut seq: u32 = 1; let mut sent = 0u64; let mut last_hb = Instant::now(); while start.elapsed() < Duration::from_secs(secs) { if last_hb.elapsed() >= Duration::from_secs(6) { let _ = sock.send_to(&hb, &dst); last_hb = Instant::now(); } let t = start.elapsed().as_secs_f32(); let mut p1 = [0u8; 512]; let mut p2 = [0u8; 512]; 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) as u8; let ch = a.max(1) - 1; // 0-based if ch < 512 { p1[ch as usize] = v; } else if ch < 1024 { p2[(ch - 512) as usize] = v; } } let r1 = rng.next(); let r2 = rng.next(); let mut f = tpl.clone(); f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes()); f[0x2E] = 2; f[0x2F] = 0; // 节点序号 (真机 .1 = 2) 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, b12)); f[0x23C..0x43C].copy_from_slice(&agios_dmx_encrypt(&p2, r2, b12)); if sock.send_to(&f, &dst).is_ok() { sent += 1; } seq += 1; std::thread::sleep(Duration::from_millis(33)); } println!("[5] 完成: 发送 {} 帧", sent); } fn found_valid(uid: &[u8; 6]) -> bool { uid.iter().any(|&b| b != 0) && uid.iter().any(|&b| b != 0xFF) } /// 构造"新鲜"宣告帧: announce_live 模板 (今日 SL 实抓) + 每发随机会话 ID@0x221 /// + 当前本地时间戳@0x239 (年u16LE 月 日 时 分 秒 毫秒u16LE 时区) — Butler 按 /// 宣告时间戳判主机新鲜度, 陈旧宣告(旧抓包模板)会被当僵尸主机无视 ident/AECH fn build_fresh_announce(rng: &mut Rng) -> Vec { let mut a = load_template("announce_live"); // 644B let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); // 本地时间分解 (UTC+8, 与实测 tz=02... 用本地时区偏移) let secs = now + tz_offset_secs(); let days = secs / 86400; let rem = secs % 86400; let (year, month, day) = civil_from_days(days as i64); let hour = (rem / 3600) as u8; let min = ((rem % 3600) / 60) as u8; let sec = (rem % 60) as u8; let ms = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_millis(); // 时间戳字段实际位于 0x235: 年u16LE@0x235 月@0x237 日@0x238 时@0x239 分@0x23A // 秒@0x23B 毫秒u16LE@0x23C 时区@0x23E (实测 2026-09-10 22:52:50.992 tz=02) a[0x235] = (year & 0xFF) as u8; a[0x236] = (year >> 8) as u8; a[0x237] = month; a[0x238] = day; a[0x239] = hour; a[0x23A] = min; a[0x23B] = sec; let msle = (ms as u16).to_le_bytes(); a[0x23C] = msle[0]; a[0x23D] = msle[1]; a[0x23E] = 0x02; // 时区字段 (实测值) // 4B 随机会话 ID @0x21D (每发宣告都变; 0x221 的 0c 恒定) let id = rng.next(); a[0x21D..0x221].copy_from_slice(&id.to_le_bytes()); a } fn tz_offset_secs() -> u64 { // 本地时区偏移 (秒): 用 UTC 与本地时间差 let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; let utc_days = (now.div_euclid(86400) + 4) % 7; // 0=周四 let _ = utc_days; // 简化: 读环境 TZ 不可靠, 直接用 Windows 时区 API? 用 chrono 不行(无依赖). // 实测帧 tz 字节恒 02, 时间字段为本地时间 (UTC+8 中 hour=22 对应当地 22 点) // → 这里返回 +8h; 若部署在其它时区需调整 8 * 3600 } fn civil_from_days(z: i64) -> (u16, u8, u8) { // Howard Hinnant 算法: days since 1970-01-01 → (y, m, d) let z = z + 719468; let era = if z >= 0 { z } else { z - 146096 } / 146097; let doe = (z - era * 146097) as u64; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe as i64 + era * 400; let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = (doy - (153 * mp + 2) / 5 + 1) as u8; let m = if mp < 10 { mp + 3 } else { mp - 9 } as u8; (if m <= 2 { (y + 1) as u16 } else { y as u16 }, m, d) } /// ★1947 端口"密钥报文" (allports.pcap 实测): 40B ASCII 许可令牌, SL 每 ~34s 广播一轮 /// (双目标 21 帧 @200ms) — Butler 在 1947 监听, 疑为 RDM 代理的许可开关 const KEY40: &[u8] = b"BlZRQphAGxUbi7at8KEfmdZ7TdSB03b+qoC1RIA"; fn send_key_burst(sock: &UdpSocket) { for _ in 0..21 { let _ = sock.send_to(KEY40, "192.168.123.255:1947"); let _ = sock.send_to(KEY40, "255.255.255.255:1947"); std::thread::sleep(Duration::from_millis(200)); } } /// ★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 { 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 地址 → 呼吸图案控制 (未发现则用已知地址) fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u32, secs: u64) { let tpl = load_template("dmx_a"); let hb = load_template("heartbeat"); let known: [[u8; 6]; 3] = [ [0x01, 0x07, 0x34, 0x67, 0x01, 0x25], // NFS1K2D DMX101 [0x01, 0x07, 0x34, 0x88, 0x09, 0x00], // NFM680S DMX112 [0x08, 0x99, 0x50, 0x00, 0x26, 0xc9], // HMKJ DMX1-32 ]; let mut buf = [0u8; 4096]; let mut fixtures: Vec<[u8; 6]> = Vec::new(); let mut addr_list: Vec = Vec::new(); let mut tn: u8 = rng.byte(); let mut qseq: u16 = 1; let mut seq: u32 = 1; // 发现任务队列: ★窄分支优先 (已知灯具各一个只含自己的分支 — Butler 可能拒转全范围查询) 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 muted: Vec<[u8; 6]> = Vec::new(); let start = Instant::now(); let mut last_hb = Instant::now(); let mut last_key_t: f32 = -100.0; let mut last_rdm = Instant::now() - Duration::from_secs(1); let mut unmuting = false; let mut discovering = true; let mut collecting = false; let mut collect_until = Instant::now(); let mut found: Vec<[u8; 6]> = Vec::new(); let mut collision = false; let mut current_branch: Option<(u64, u64)> = None; let mut sent = 0u64; println!("[S] 统一会话环启动: DMX 30fps + RDM 穿插 ({}s)", secs); while start.elapsed() < Duration::from_secs(secs) { let t = start.elapsed().as_secs_f32(); // --- 心跳每 6s + 密钥每 30s + ★EUCE 每 6s (SL 全程 6s 节奏, 许可信标) --- if last_hb.elapsed() >= Duration::from_secs(6) { let _ = sock.send_to(&hb, dst); let euce = load_template("euce"); let _ = sock.send_to(&euce, "255.255.255.255:31772"); last_hb = Instant::now(); } if t - last_key_t >= 30.0 { let _ = sock.send_to(KEY40, "192.168.123.255:1947"); let _ = sock.send_to(KEY40, "255.255.255.255:1947"); last_key_t = t; } // --- RDM 任务 (每 ~100ms 一发; 窄分支优先, t>3s 即开始快速定位问题) --- if discovering && t > 3.0 && last_rdm.elapsed() >= Duration::from_millis(100) { last_rdm = Instant::now(); if collecting && Instant::now() >= collect_until { // 本分支收集结束 → 判定 collecting = false; if let Some((lo, hi)) = current_branch.take() { if collision { let mid = lo + (hi - lo) / 2; stack.push((mid + 1, hi)); stack.push((lo, mid)); println!(" [S] 分支冲突 → 二分"); } else if found.len() == 1 && found_valid(&found[0]) { let uid = found[0]; println!(" [S] ★发现灯具 {}", uid_s(&uid)); if !fixtures.contains(&uid) { fixtures.push(uid); } get_queue.push(uid); // DISC_MUTE 该灯具 tn = tn.wrapping_add(1); let f = build_rdm_frame(&uid, tn, qseq, 0x10, 0x0002, &[]); qseq += 1; let _ = sock.send_to(&{ let mut f = f; f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes()); seq += 1; f }, dst); } else if found.len() > 1 { let mid = lo + (hi - lo) / 2; stack.push((mid + 1, hi)); stack.push((lo, mid)); } found.clear(); collision = false; } } if collecting { // 收集窗口内每 150ms 重发同分支查询 (真主机连发节奏) if last_rdm.elapsed() >= Duration::from_millis(150) { last_rdm = Instant::now(); if let Some((lo, hi)) = current_branch { let mut pd = Vec::with_capacity(12); pd.extend_from_slice(&u64_uid(lo)); pd.extend_from_slice(&u64_uid(hi)); tn = tn.wrapping_add(1); let mut f = build_rdm_frame(&BCAST_UID, tn, qseq, 0x10, 0x0001, &pd); qseq += 1; f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes()); seq += 1; let _ = sock.send_to(&f, dst); let mut f2 = f.clone(); f2[0x30] = 0x01; f2[0x49] = f2[0x49].wrapping_sub(1); let _ = sock.send_to(&f2, dst); } } } if !collecting { if !unmuting { // DISC_UN_MUTE 广播 tn = tn.wrapping_add(1); let f = build_rdm_frame(&BCAST_UID, tn, qseq, 0x10, 0x0003, &[]); qseq += 1; let _ = sock.send_to(&{ let mut f = f; f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes()); seq += 1; f }, dst); unmuting = true; println!("[S] DISC_UN_MUTE 已发"); } else if let Some((lo, hi)) = stack.pop() { let mut pd = Vec::with_capacity(12); pd.extend_from_slice(&u64_uid(lo)); pd.extend_from_slice(&u64_uid(hi)); tn = tn.wrapping_add(1); let mut f = build_rdm_frame(&BCAST_UID, tn, qseq, 0x10, 0x0001, &pd); qseq += 1; // SL 实测: RDM 请求成对发送 (flag=00 + flag=01 副本, 背靠背 26μs) f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes()); seq += 1; let _ = sock.send_to(&f, dst); let mut f2 = f.clone(); f2[0x30] = 0x01; // flag=01 副本的 srcUID 末字节 = fe (SL 实测: ff/fe 成对) f2[0x49] = f2[0x49].wrapping_sub(1); let _ = sock.send_to(&f2, dst); current_branch = Some((lo, hi)); collecting = true; collect_until = Instant::now() + Duration::from_millis(500); } else { if fixtures.is_empty() && t < 90.0 { // 未发现 → 重新压栈全范围, 循环重试 (SL 实测: 认领后 ~27.5s RDM 才开始被代理!) stack.push((0, 0xFFFF_FFFF_FFFF)); } else { discovering = false; println!("[S] 发现完成: {} 台灯具", fixtures.len()); } } } } // --- 收包 (非阻塞): 脉冲/参数应答 --- while let Ok((n, src)) = sock.recv_from(&mut buf) { if src.ip().to_string() != target { continue; } let b = &buf[..n]; if let Some((is_pulse, d)) = rdm_region(b) { if is_pulse && collecting { match decode_pulse(d) { Some(uid) => { if !found.contains(&uid) { found.push(uid); } } None => collision = true, } } else if !is_pulse && d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0x60] { let addr = u16::from_be_bytes([d[0x18 + 14], d[0x18 + 15]]); let footprint = u16::from_be_bytes([d[0x18 + 10], d[0x18 + 11]]); println!(" [S] ★{} → DMX地址 {} / {}通道", uid_s(&[d[9], d[10], d[11], d[12], d[13], d[14]]), addr, footprint); addr_list.push(addr); } else if !is_pulse && d.len() >= 0x18 + 2 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0xF0] { let addr = u16::from_be_bytes([d[0x18], d[0x19]]); println!(" [S] ★DMX_START_ADDRESS 应答: {}", addr); addr_list.push(addr); } } } // --- GET 队列 (发现后逐台读参数) --- if !discovering && !get_queue.is_empty() && last_rdm.elapsed() >= Duration::from_millis(120) { last_rdm = Instant::now(); let uid = get_queue.remove(0); tn = tn.wrapping_add(1); let f = build_rdm_frame(&uid, tn, qseq, 0x20, 0x0060, &[]); qseq += 1; let _ = sock.send_to(&{ let mut f = f; f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes()); seq += 1; f }, dst); let _ = muted; } // --- DMX 帧 @30fps --- let mut p1 = [0u8; 512]; let mut p2 = [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 * 0.5).min(1.0)) as u8; // 淡入 let ch = a.max(1) - 1; if ch < 512 { p1[ch as usize] = v; } else if ch < 1024 { p2[(ch - 512) as usize] = v; } } } let r1 = rng.next(); let r2 = rng.next(); let mut f = tpl.clone(); f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes()); f[0x2E] = 2; f[0x2F] = 0; 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, b12)); f[0x23C..0x43C].copy_from_slice(&agios_dmx_encrypt(&p2, r2, b12)); let _ = sock.send_to(&{ let mut f = f; f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes()); seq += 1; f }, dst); seq += 1; sent += 1; std::thread::sleep(Duration::from_millis(33)); } println!("[S] 会话环结束: DMX {} 帧, 发现灯具 {} 台, 控制地址 {:?}", sent, fixtures.len(), addr_list); } /// ★UDP 31772 通用中继 (udprelay 子命令): 跨网段主机验证用 /// 客户端(任意网段) ↔ sockA(listen_addr) | sockB(src_ip:0) ↔ Butler /// 发往 Butler 的帧源 IP = src_ip (123.113, 绕过 AECH 固件源过滤); /// Butler 应答(单播到 sockB) 转发回最后一个客户端; Butler 广播(到 sockA) 也转发。 pub fn udp_relay(listen_addr: &str, butler_ip: &str, src_ip: &str) { let a = open_reuse(listen_addr); let b = open_reuse(&format!("{}:0", src_ip)); a.set_read_timeout(Some(Duration::from_millis(2))).unwrap(); b.set_read_timeout(Some(Duration::from_millis(2))).unwrap(); let bdst = format!("{}:31772", butler_ip); let mut client: Option = None; let mut ba = [0u8; 4096]; let mut bb = [0u8; 4096]; let (mut n_fwd, mut n_rev) = (0u64, 0u64); println!("[RELAY] {} <-> Butler {} (源伪装 {}), Ctrl+C 停止", listen_addr, bdst, src_ip); loop { if let Ok((n, from)) = a.recv_from(&mut ba) { if from.ip().to_string() == butler_ip { // Butler 广播/事件 → 客户端 if let Some(c) = client { let _ = a.send_to(&ba[..n], c); n_rev += 1; } } else { client = Some(from); let _ = b.send_to(&ba[..n], &bdst); n_fwd += 1; if n_fwd == 1 { println!("[RELAY] 客户端 = {}", from); } } } if let Ok((n, _from)) = b.recv_from(&mut bb) { if let Some(c) = client { let _ = a.send_to(&bb[..n], c); n_rev += 1; } } if (n_fwd + n_rev) % 2000 == 1 && n_fwd > 1 { println!("[RELAY] 已转发 客户端→Butler {} 帧, Butler→客户端 {} 帧", n_fwd, n_rev); } } }