enet-validator: e:net protocol reverse + host-side emulation (AECH claim / encrypted DMX / RDM discovery, hostdemo)

This commit is contained in:
tonycao 2026-09-11 01:02:39 +08:00
commit e248a979e8
49 changed files with 4970 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
*.log
*.pcap
*.tsv

7
Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "enet-validator"
version = "0.1.0"

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "enet-validator"
version = "0.1.0"
edition = "2021"
[dependencies]
# 闆跺閮ㄤ緷璧栵細std::net::UdpSocket + std::fs 瓒冲
[profile.release]
opt-level = 2

1
samples/ack_84.hex Normal file
View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001002a00000002004275746c010018030000010000000000

1
samples/aere_594.hex Normal file
View File

@ -0,0 +1 @@
454e65740200000000001c7c0000000000000100280200000600017ba8c000161cf12e3900717ba84145524559bb367fffffff0107346701250015000a00000001017ba8c000ffffff00161cf12e39000000000000000000001a0000000101fa0c000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023010000000024000000240000001c001c001d001d8361bf1d00f00af00a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

1
samples/aerep_113.hex Normal file
View File

@ -0,0 +1 @@
454e65740200000000001c7c0000000000000100280200000600017ba8c000161cf12e3900717ba84145524559bb367ffffffd0107348809000015000a00000001017ba8c000ffffff00161cf12e39000000000000000000001a0000000101fa0c000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023010000000023000000230000001c001c001d001d5904e1c100f00af00a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

1
samples/aerep_115.hex Normal file
View File

@ -0,0 +1 @@
454e65740200000000001c7c0000000000000100280200000600017ba8c000161cf12e3900717ba84145524559bb367ffffffd0107348809000015000a00000001017ba8c000ffffff00161cf12e39000000000000000000001a0000000101fa0c000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023010000000023000000230000001c001c001d001d51cf405000f00af00a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

1
samples/aerep_117.hex Normal file
View File

@ -0,0 +1 @@
454e65740200000000001c7c0000000000000100280200000600017ba8c000161cf12e3900717ba84145524559bb367ffffffd0107348809000015000a00000001017ba8c000ffffff00161cf12e39000000000000000000001a0000000101fa0c000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023010000000023000000230000001c001c001d001dfce6923d00f00af00a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

1
samples/announce_144.hex Normal file
View File

@ -0,0 +1 @@
454e65740200000000001c7c0000000000000100660000000100717ba8c0300ed559bb361a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

1
samples/announce_686.hex Normal file
View File

@ -0,0 +1 @@
454e65740100000000001c7c000000000000010084020000150000000000000000000000000000000000000000000000000000000000000000001a000000000001717ba8c000ffffff300ed559bb3600000000000000000000000000000001ce43050500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043a0f4f7ec175641812b909bdc99f2ff4000000000010003000000000000000000000000000100030000000000000000000000000074006f006e007900630061006f004400450053004b0054004f0050002d0050004500440032004c00560044002d00660037006600340061003000340033002d0031003700650063002d0034003100350036002d0038003100320062002d0039003000390062006400630039003900660032006600660000000000000000000000911f343cf708bf342e8e519da944bd8a5a4a24433989ea070902001819410343003a005c00550073006500720073005c0074006f006e007900630061006f005c0041007000700044006100740061005c004c006f00630061006c005c0065006300750065005c00530059004d00500048004f004c004900470048005400200035002e0035005c00430075007200720065006e007400530068006f0077000000000000000000000000000000000000000000d00df1e81900000081710b0e000000008ee4f7f6190000001e00000000000000010000005a00ffffea07090717182f16010200000000000000000000000002000000000000000000000000201c0000000000000000000001000000000000000000000001000000000000000100000001000000

View File

@ -0,0 +1 @@
454e65740100000000001c7c000000000000010084020000150000000000000000000000000000000000000000000000000000000000000000001a000000000001717ba8c000ffffff300ed559bb3600000000000000000000000000000001ce43050500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043a0f4f7ec175641812b909bdc99f2ff4000000000010003000000000000000000000000000100030000000000000000000000000074006f006e007900630061006f004400450053004b0054004f0050002d0050004500440032004c00560044002d00660037006600340061003000340033002d0031003700650063002d0034003100350036002d0038003100320062002d0039003000390062006400630039003900660032006600660000000000000000000000911f0000000000000000000000000000000000000000d00701010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e1bf21b20c0000001e00000000000000010000005a00ffffea07090a163432e003020000000000000000000000000200000000000000000000000098080000000000000000000000000000000000000000000000000000000000000000000000000000

1
samples/claim_h112.hex Normal file
View File

@ -0,0 +1 @@
454e65740200000000001c7c0000000000000100280200000500717ba8c0300ed559bb36000000004145434841cad262b4c0d9aea3e3c1cc0898bffec74c63aca7b51e5727a7ae330ca7d61e162e57e9fa86044d898e5f16a8249dd5d6ab360eedef3a8cab7a3a0d12a6a1fb0da2b5d940d50340481523195508af0309d95c6071c73027f4ca3e80e2545f3b9bb5327e4cf63be3ec6408056ec49400a7f6c6d7cc2136a3cb37043849418945728ee5769783f351da3f2cf1ffa99445455bb87a01585bd3949ab415f20299abdac393e0de1e69240274fd8cf445a422a2bef3ed880e44193212e1fccede5fd8a681163c16b46626f294788d5bcfdf4ef69272330eac22cb7e79adcc3c6c55231525e5722e670bed58ccfbf496b7be633393063c6c2bc9fa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

1
samples/claim_h114.hex Normal file
View File

@ -0,0 +1 @@
454e65740200000000001c7c0000000000000100280200000500717ba8c0300ed559bb3600000000414543484bbd9d079aea853e547835e96c208e8685db41b02e35c7ba4998a96619b7e95beb13e3aa6ee5bde275b5413f897ac67277b5e64f87493016f5e2bd01411e259c8ed9fd593de4189f1d5f3a78f38830cfd236ee63d099d4a2e2cdb48b5f2e2a1f9bb5327e27bca0578fa4b0af7c6d35939c4731280eb8226db1ee2f0a30c2ccf4055a77702f8d7761bee3e94ed7815265015ac68967ac9429aa47d9398d9626cdd8bca921f5c4640fd0d0692aced495309bca09d5fb438c59c64dc6e8b33dd428bc1c751bd7306f3cf6b6b89742695bd6b1a99ec38d51d1952f505ba7fe7b5c92fb416eb25e7a586a5accde458af5165df2f0370a9f119b6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

1
samples/claim_h116.hex Normal file
View File

@ -0,0 +1 @@
454e65740200000000001c7c0000000000000100280200000500717ba8c0300ed559bb360000000041454348e0eb87d5c73e7a5a269b01faa87737503368c0728c0b3a39fdb50e4452f3eaa602687e8ed195928e64ea645915624a7800ba6ca8daf9bdd5b14dd8122f360833b15af840f9a87e9dd6320ea1a6e1239a6b9644978641cb09aaa8dc834a22993e9bb5327ebd805e2de231a8bb6a0a7e573bced2a8ea1c2ed02a3da799f170ac6f8fee9550cbfae056d3e0bf7539682f6117ec5cb76f231f8e7a0c597fd8c53b7284382c7c742e8b40ee7cd50a5db897c10d45820413f4006bcf07d6bfa0607d0da44cf0003cb3ede8611cc9f34d1430c17598719342365e9037c0ad6c57cc9f8dfec76510c796855a5899a3f72a0d1ebad6144a08eeeaca6d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

View File

@ -0,0 +1 @@
454e65740100000000001c7c0000000000000100b40000000100746f6e7963616f4445534b544f502d504544324c56442d66376634613034332d1a000000000002717ba8c000ffffff300ed559bb3600000000000000000000000000000001ce430505000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000900

View File

@ -0,0 +1 @@
454e65740100000000001c7c0000000000000100d90000000f004275746c657220533200000000001b00cc0119453a7ffffff30107346701250015000a00000001017ba8c000ffffff00161cf12e396b3aa8c0300ed559bb351a0000000101fa0c00020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002301000000001e0000001e000000240024002400240020002000720d720d1e0000000a0000000000000000000a00

View File

@ -0,0 +1 @@
454e65740100000000001c7c0000000000000100d90000000f004275746c657220533200000000000000aaaa5dbbddfa55aa55ae77ebddaf570015000a00000001017ba8c000ffffff00161cf12e39000000000000000000001a0000000101fa0c000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023010000000024000000240000001c001c001d001d001c001c00f00af00a00000000000000000000000000000000

View File

@ -0,0 +1 @@
454e65740100000000001c7c0000000000000100d90000000f004275746c657220533200000000000000aaaa5dbbddfa55aa55ae77ebddaf570015000a00000001017ba8c000ffffff00161cf12e39000000000000000000001a0000000101fa0c000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023010000000024000000240000001c001c001d001d001c001c00f00af00a00000000000000000000000000000000

1
samples/dmx_a_1126.hex Normal file
View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003c040000010097010000000001011a8c191563d67616000000000100000001000002000201000000abaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a0908070605040302015554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a0908070605040302012c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201

1
samples/euce_582.hex Normal file
View File

@ -0,0 +1 @@
4555434500000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746f6e7963616f4445534b544f502d504544324c56442d66376634613034332d313765632d343135362d383132622d3930396264633939663266660000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300ed559bb36

1
samples/event_b_1126.hex Normal file
View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003c040000010000000000000002001903000000000000cc00000001000100000025000000010000000124ffffffffffff453a7ffffffe96010000001000010c80007ffffffffffffffffffe158e1b1a191817161514131211100f0e0d0c0b0a09080706050403020100fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a0908070605040302018b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a0908070605040302017574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201

1
samples/heartbeat_78.hex Normal file
View File

@ -0,0 +1 @@
454e65740200000000001c7c0000000000000100240000000300717ba8c0300ed559bb36

50
samples/pid_map.txt Normal file
View File

@ -0,0 +1,50 @@
0080 454e65740400000000001c7c00000000000001005200000002004275746c000098000000000000002800cc0126453a7fffffff01073467012584000000002100800e4e46532d314b32447878784278200b32
0504 454e65740400000000001c7c00000000000001003b0000000200017ba8c001000d010000000000001100aaab55af57be75aaddab5daa55ae57efd7
8013 454e65740400000000001c7c00000000000001004500000002004275746c0000a2000000000000001b00cc0119453a7fffffff0107346701258e00000000218013010106ee
8074 454e65740400000000001c7c00000000000001004500000002004275746c0000b9000000000000001b00cc0119453a7fffffff010734670125a50000000021807401000765
0060 454e65740400000000001c7c00000000000001005700000002004275746c000021010000000000002d00cc012b453a7fffffff0107348809000c00000000210060130100020005090001000100010101006f00001106f6
8021 454e65740400000000001c7c00000000000001004500000002004275746c0000a4000000000000001b00cc0119453a7fffffff0107346701259000000000218021010106fe
8064 454e65740400000000001c7c00000000000001004600000002004275746c0000b3000000000000001c00cc011a453a7fffffff0107346701259f000000002180640200000751
00c0 454e65740400000000001c7c00000000000001005700000002004275746c000096000000000000002d00cc012b453a7fffffff01073467012582000000002100c0134e46532d314b324478787842782056312e30310c90
8051 454e65740400000000001c7c00000000000001004500000002004275746c0000ac000000000000001b00cc0119453a7fffffff010734670125980000000021805101000735
8072 454e65740400000000001c7c00000000000001004500000002004275746c0000b7000000000000001b00cc0119453a7fffffff010734670125a30000000021807201000761
8063 454e65740400000000001c7c00000000000001004600000002004275746c0000b2000000000000001c00cc011a453a7fffffff0107346701259e0000000021806302154d07b1
8084 454e65740400000000001c7c00000000000001004600000002004275746c0000bf000000000000001c00cc011a453a7fffffff010734670125aa0000000021808402005c07d8
8050 454e65740400000000001c7c00000000000001004500000002004275746c0000ab000000000000001b00cc0119453a7fffffff010734670125970000000021805001010734
8071 454e65740400000000001c7c00000000000001004500000002004275746c0000b6000000000000001b00cc0119453a7fffffff010734670125a2000000002180710100075f
0082 454e65740400000000001c7c00000000000001005000000002004275746c00009a000000000000002600cc0124453a7fffffff01073467012586000000002100820c4e4653314b324478787842780ae5
8041 454e65740400000000001c7c00000000000001004500000002004275746c0000a8000000000000001b00cc0119453a7fffffff010734670125940000000021804101fe081f
0003 454e65740400000000001c7c00000000000001005700000002004275746c00005c000000000000002d00cc012b453a7fffffff010734670125510000000021006013010001030509000100010002010100650000170736
0401 454e65740400000000001c7c00000000000001004800000002004275746c00009c000000000000001e00cc011c453a7fffffff0107346701258800000000210401040000000d066c
8090 454e65740400000000001c7c00000000000001004500000002004275746c0000c2000000000000001b00cc0119453a7fffffff010734670125ad00000000218090013207bb
8053 454e65740400000000001c7c00000000000001004500000002004275746c0000ae000000000000001b00cc0119453a7fffffff0107346701259a0000000021805301000739
8010 454e65740400000000001c7c00000000000001004500000002004275746c0000a0000000000000001b00cc0119453a7fffffff0107346701258c00000000218010010106e9
8042 454e65740400000000001c7c00000000000001004500000002004275746c0000a9000000000000001b00cc0119453a7fffffff010734670125950000000021804201ff0822
8061 454e65740400000000001c7c00000000000001004800000002004275746c0000b0000000000000001e00cc011c453a7fffffff0107346701259c00000000218061040000a3650857
8081 454e65740400000000001c7c00000000000001004800000002004275746c00007c010000000000001e00cc011c453a7fffffff0107348809005e00000000218081040008a67b085e
0405 454e65740400000000001c7c00000000000001004800000002004275746c00009f000000000000001e00cc011c453a7fffffff0107346701258b00000000210405040000006906cf
00f0 454e65740400000000001c7c0000000000000100460000000200017ba8c000002e020000000000001c00cc011a453a7fffffff0107346701250f000000002100f00200650732
0002 454e65740400000000001c7c00000000000001004600000002004275746c000020010000000000001c00cc011a453a7fffffff0107348809000b0000000011000202000005cf
0403 454e65740400000000001c7c00000000000001004500000002004275746c00009e000000000000001b00cc0119453a7fffffff0107346701258a00000000210403017f06dc
8060 454e65740400000000001c7c00000000000001004700000002004275746c0000af000000000000001d00cc011b453a7fffffff0107346701259b000000002180600309334707ce
8022 454e65740400000000001c7c00000000000001004500000002004275746c0000a5000000000000001b00cc0119453a7fffffff010734670125910000000021802201010700
8065 454e65740400000000001c7c00000000000001004800000002004275746c0000b4000000000000001e00cc011c453a7fffffff010734670125a00000000021806504000000000757
8085 454e65740400000000001c7c00000000000001004800000002004275746c0000c0000000000000001e00cc011c453a7fffffff010734670125ab0000000021808504000000040786
8011 454e65740400000000001c7c00000000000001004500000002004275746c0000a1000000000000001b00cc0119453a7fffffff0107346701258d00000000218011013c0726
8070 454e65740400000000001c7c00000000000001004500000002004275746c0000b5000000000000001b00cc0119453a7fffffff010734670125a1000000002180700100075d
8040 454e65740400000000001c7c00000000000001004500000002004275746c0000a7000000000000001b00cc0119453a7fffffff010734670125930000000021804001010720
8023 454e65740400000000001c7c00000000000001004500000002004275746c0000a6000000000000001b00cc0119453a7fffffff010734670125920000000021802301000701
8083 454e65740400000000001c7c00000000000001004600000002004275746c0000be000000000000001c00cc011a453a7fffffff010734670125a9000000002180830208f60878
8052 454e65740400000000001c7c00000000000001004500000002004275746c0000ad000000000000001b00cc0119453a7fffffff010734670125990000000021805201010738
1000 454e65740400000000001c7c00000000000001004500000002004275746c000097000000000000001b00cc0119453a7fffffff01073467012583000000002110000100065f
0081 454e65740400000000001c7c00000000000001005f00000002004275746c000099000000000000003500cc0133453a7fffffff01073467012585000000002100811b496e76656e74726f6e6963732848616e677a686f75292c496e632e1111
8043 454e65740400000000001c7c00000000000001004500000002004275746c0000aa000000000000001b00cc0119453a7fffffff010734670125960000000021804301ff0824
8087 454e65740400000000001c7c00000000000001004800000002004275746c0000c1000000000000001e00cc011c453a7fffffff010734670125ac0000000021808704000000d30858
0050 454e65740400000000001c7c00000000000001004e0000000200017ba8c00000fd010000000000002400cc0122453a7fffffff0899500026c9de000000002100500a020002010081008204030932
8073 454e65740400000000001c7c00000000000001004500000002004275746c0000b8000000000000001b00cc0119453a7fffffff010734670125a40000000021807301000763
8082 454e65740400000000001c7c00000000000001004500000002004275746c0000bd000000000000001b00cc0119453a7fffffff010734670125a80000000021808201210797
0400 454e65740400000000001c7c00000000000001004800000002004275746c00009b000000000000001e00cc011c453a7fffffff010734670125870000000021040004000000c80725
8080 454e65740400000000001c7c00000000000001004700000002004275746c0000ba000000000000001d00cc011b453a7fffffff010734670125a6000000002180800300006907df
8062 454e65740400000000001c7c00000000000001004500000002004275746c0000b1000000000000001b00cc0119453a7fffffff0107346701259d00000000218062017f07ca
0402 454e65740400000000001c7c00000000000001004800000002004275746c00009d000000000000001e00cc011c453a7fffffff010734670125890000000021040204000933840721
8020 454e65740400000000001c7c00000000000001004500000002004275746c0000a3000000000000001b00cc0119453a7fffffff0107346701258f00000000218020010006fb

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003b00000002004275746c0100bc010000000000001100aaaa5dbbddfa55aa55ae77ebddaf57fadf

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003b00000002004275746c010036000000000000001100aaab55af57be75ef77ab55af75ae57ebd7

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003b0000000200017ba8c00100fb000000000000001100aaab55af57be75aaddab5daa55ae57efd7

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003b00000002004275746c01009c050000000000001100fefefeffffffddffbfbfbfbfbfbf57efd7

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003b0000000200017ba8c001001b000000000000001100fefefefefefffffff7f7bfbfff35ddedd7

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003c04000001000000000000000200090f000000000000cc00000001000000000025000000010000000124ffffffffffff453a7ffffffd86000000001000010c089950001767089950001f660e6c050500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043a0f4f7ec175641812b909bdc99f2ff4000000000010003000000000000000000000000000100030000000000000000000000000074006f006e007900630061006f004400450053004b0054004f0050002d0050004500440032004c00560044002d00660037006600340061003000340033002d0031003700650063002d0034003100350036002d0038003100320062002d0039003000390062006400630039003900660032006600660000000000000000000000911f343cf708bf342e8e519da944bd8a5a4a24433989ea070902001819410343003a005c00550073006500720073005c0074006f006e007900630061006f005c0041007000700044006100740061005c004c006f00630061006c005c0065006300750065005c00530059004d00500048004f004c004900470048005400200035002e0035005c00430075007200720065006e007400530068006f0077000000000000000000000000000000000000000000d00df1e819000000f6d1c94c090000003737b635230000001e00000000000000010000005a00ffffea0709080a1a2245020200000000000000000000000002000000000000000000000000000000000000000000000000010000000100000000000000010000000000000001000000010000002f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201

1
samples/rdm_info_119.hex Normal file
View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001004d00000002004275746c00007e0f0000000000002300cc0121453a7ffffffd010734670125c00000000021020109000280000000000000071f

178
samples/rdm_pool.txt Normal file
View File

@ -0,0 +1,178 @@
59 454e65740400000000001c7c00000000000001003b00000002004275746c01000b0f0000000000001100aaaa5dbbddfa55aa55ae77ebddaf57fadf
70 454e65740400000000001c7c00000000000001004600000002004275746c00002f0f0000000000001c00cc011a453a7ffffffd0899500026c99900000000110002020000076e
87 454e65740400000000001c7c00000000000001005700000002004275746c0000310f0000000000002d00cc012b453a7ffffffd0899500026c99a0000000021006013010001000100010009050020000000010000040836
59 454e65740400000000001c7c00000000000001003b00000002004275746c0100790f0000000000001100aaff7fffffaa5feaf5bad7ab7dab57ab77
77 454e65740400000000001c7c00000000000001004d00000002004275746c00007e0f0000000000002300cc0121453a7ffffffd010734670125c00000000021020109000280000000000000071f
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000800f0000000000002300cc0121453a7ffffffd010734670125c1000000002102010901001b00000000000006ba
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000830f0000000000002300cc0121453a7ffffffd010734670125c200000000210201090208dd08bc094000000893
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000850f0000000000002300cc0121453a7ffffffd010734670125c30000000021020109030010000505c900000786
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000870f0000000000002300cc0121453a7ffffffd010734670125c4000000002102010904003200000000000006d7
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000890f0000000000002300cc0121453a7ffffffd010734670125c500000000210201090500040004087f00000736
77 454e65740400000000001c7c00000000000001004d00000002004275746c00008b0f0000000000002300cc0121453a7ffffffd010734670125c6000000002102010906000000000000000006a9
77 454e65740400000000001c7c00000000000001004d00000002004275746c00008d0f0000000000002300cc0121453a7ffffffd010734670125c70000000021020109070019000b0d20000006fc
77 454e65740400000000001c7c00000000000001004d00000002004275746c00008f0f0000000000002300cc0121453a7ffffffd010734670125c8000000002102010908000000000000000006ad
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000910f0000000000002300cc0121453a7ffffffd010734670125c9000000002102010909000b00000000000006ba
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000930f0000000000002300cc0121453a7ffffffd010734670125ca00000000210201090a000000000000000006b1
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000950f0000000000002300cc0121453a7ffffffd010734670125cb00000000210201090b002900000000000006dc
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000960f0000000000002300cc0121453a7ffffffd010734670125cc00000000210201090c007f0000000000000734
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000980f0000000000002300cc0121453a7ffffffd010734670125cd00000000210201090d0503002a163200000731
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000990f0000000000002300cc0121453a7ffffffd010734670125ce00000000210201090e0000000000c700000780
77 454e65740400000000001c7c00000000000001004d00000002004275746c00009b0f0000000000002300cc0121453a7ffffffd010734670125cf00000000210201090f00000000037e0000073c
77 454e65740400000000001c7c00000000000001004d00000002004275746c00009d0f0000000000002300cc0121453a7ffffffd010734670125d0000000002102010910000000000000000006bd
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000a00f0000000000002300cc0121453a7ffffffd010734670125d1000000002102010911001200000000000006d1
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000a20f0000000000002300cc0121453a7ffffffd010734670125d2000000002102010912007f0000000000000740
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000a30f0000000000002300cc0121453a7ffffffd010734670125d3000000002102010913051c0044163b00000779
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000a60f0000000000002300cc0121453a7ffffffd010734670125d4000000002102010914000000000015000006da
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000a70f0000000000002300cc0121453a7ffffffd010734670125d500000000210201091500000000004200000709
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000a90f0000000000002300cc0121453a7ffffffd010734670125d6000000002102010916000000000000000006c9
70 454e65740400000000001c7c00000000000001004600000002004275746c0000ab0f0000000000001c00cc011a453a7ffffffd0899500026c9d7000000002100f002000108ab
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000ac0f0000000000002300cc0121453a7ffffffd0899500026c9d80000000021020109000ce400000000000008bc
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000ae0f0000000000002300cc0121453a7ffffffd0899500026c9d90000000021020109010945000000000000081c
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000b00f0000000000002300cc0121453a7ffffffd0899500026c9da000000002102010902001800000000000007e8
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000b20f0000000000002300cc0121453a7ffffffd0899500026c9db00000000210201090320db00000000000008cd
70 454e65740400000000001c7c00000000000001004600000002004275746c0000b50f0000000000001c00cc011a453a7ffffffd010734880900dc000000002100f0020070080c
69 454e65740400000000001c7c00000000000001004500000002004275746c0000b70f0000000000001b00cc0119453a7ffffffd010734880900dd00000000218011013c0778
69 454e65740400000000001c7c00000000000001004500000002004275746c0000b90f0000000000001b00cc0119453a7ffffffd010734880900de000000002180200100074c
69 454e65740400000000001c7c00000000000001004500000002004275746c0000ba0f0000000000001b00cc0119453a7ffffffd010734880900df000000002180210101074f
69 454e65740400000000001c7c00000000000001004500000002004275746c0000bc0f0000000000001b00cc0119453a7ffffffd010734880900e00000000021802201010751
69 454e65740400000000001c7c00000000000001004500000002004275746c0000be0f0000000000001b00cc0119453a7ffffffd010734880900e10000000021802301000752
69 454e65740400000000001c7c00000000000001004500000002004275746c0000bf0f0000000000001b00cc0119453a7ffffffd010734880900e20000000021804001010771
69 454e65740400000000001c7c00000000000001004500000002004275746c0000c10f0000000000001b00cc0119453a7ffffffd010734880900e30000000021804101fe0870
69 454e65740400000000001c7c00000000000001004500000002004275746c0000c30f0000000000001b00cc0119453a7ffffffd010734880900e40000000021804201ff0873
69 454e65740400000000001c7c00000000000001004500000002004275746c0000c50f0000000000001b00cc0119453a7ffffffd010734880900e50000000021804301ff0875
69 454e65740400000000001c7c00000000000001004500000002004275746c0000c70f0000000000001b00cc0119453a7ffffffd010734880900e60000000021805001010785
69 454e65740400000000001c7c00000000000001004500000002004275746c0000c90f0000000000001b00cc0119453a7ffffffd010734880900e70000000021805101000786
69 454e65740400000000001c7c00000000000001004500000002004275746c0000cb0f0000000000001b00cc0119453a7ffffffd010734880900e80000000021805201010789
69 454e65740400000000001c7c00000000000001004500000002004275746c0000ce0f0000000000001b00cc0119453a7ffffffd010734880900e9000000002180530100078a
71 454e65740400000000001c7c00000000000001004700000002004275746c0000cf0f0000000000001d00cc011b453a7ffffffd010734880900ea00000000218060030006d8087a
72 454e65740400000000001c7c00000000000001004800000002004275746c0000d10f0000000000001e00cc011c453a7ffffffd010734880900eb000000002180610400039c2a0869
69 454e65740400000000001c7c00000000000001004500000002004275746c0000d30f0000000000001b00cc0119453a7ffffffd010734880900ec0000000021806201d80874
70 454e65740400000000001c7c00000000000001004600000002004275746c0000d50f0000000000001c00cc011a453a7ffffffd010734880900ed0000000021806302005e07fe
70 454e65740400000000001c7c00000000000001004600000002004275746c0000d60f0000000000001c00cc011a453a7ffffffd010734880900ee0000000021806402000007a2
72 454e65740400000000001c7c00000000000001004800000002004275746c0000d80f0000000000001e00cc011c453a7ffffffd010734880900ef00000000218065040000000007a8
69 454e65740400000000001c7c00000000000001004500000002004275746c0000da0f0000000000001b00cc0119453a7ffffffd010734880900f000000000218070010007ae
69 454e65740400000000001c7c00000000000001004500000002004275746c0000db0f0000000000001b00cc0119453a7ffffffd010734880900f100000000218071010007b0
69 454e65740400000000001c7c00000000000001004500000002004275746c0000de0f0000000000001b00cc0119453a7ffffffd010734880900f200000000218072010007b2
69 454e65740400000000001c7c00000000000001004500000002004275746c0000e00f0000000000001b00cc0119453a7ffffffd010734880900f300000000218073010007b4
69 454e65740400000000001c7c00000000000001004500000002004275746c0000e10f0000000000001b00cc0119453a7ffffffd010734880900f400000000218074010007b6
71 454e65740400000000001c7c00000000000001004700000002004275746c0000e30f0000000000001d00cc011b453a7ffffffd010734880900f50000000021808003000075083c
72 454e65740400000000001c7c00000000000001004800000002004275746c0000e50f0000000000001e00cc011c453a7ffffffd010734880900f6000000002180810400082b820880
69 454e65740400000000001c7c00000000000001004500000002004275746c0000e70f0000000000001b00cc0119453a7ffffffd010734880900f700000000218082011b07e2
70 454e65740400000000001c7c00000000000001004600000002004275746c0000e90f0000000000001c00cc011a453a7ffffffd010734880900f8000000002180830208fe08d1
70 454e65740400000000001c7c00000000000001004600000002004275746c0000eb0f0000000000001c00cc011a453a7ffffffd010734880900f90000000021808402000007cd
72 454e65740400000000001c7c00000000000001004800000002004275746c0000ed0f0000000000001e00cc011c453a7ffffffd010734880900fa00000000218085040000000407d7
72 454e65740400000000001c7c00000000000001004800000002004275746c0000ee0f0000000000001e00cc011c453a7ffffffd010734880900fb00000000218087040000000007d6
69 454e65740400000000001c7c00000000000001004500000002004275746c0000f10f0000000000001b00cc0119453a7ffffffd010734880900fc000000002180900134080e
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000f20f0000000000002300cc0121453a7ffffffd010734880900fd000000002102010900021700000000000006f7
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000f40f0000000000002300cc0121453a7ffffffd010734880900fe000000002102010901001b00000000000006fb
77 454e65740400000000001c7c00000000000001004d00000002004275746c0100f60f0000000000002300cc0121453a7ffffffd010734880900ff00000000210201090208fe08cc096100000926
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000f80f0000000000002300cc0121453a7ffffffd0107348809000000000000210201090300000000024e00000634
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000fa0f0000000000002300cc0121453a7ffffffd010734880900010000000021020109040034000000000000061a
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000fc0f0000000000002300cc0121453a7ffffffd01073488090002000000002102010905000400040405000005f9
77 454e65740400000000001c7c00000000000001004d00000002004275746c0000fe0f0000000000002300cc0121453a7ffffffd01073488090003000000002102010906000000000000000005ea
77 454e65740400000000001c7c00000000000001004d00000002004275746c000000100000000000002300cc0121453a7ffffffd0107348809000400000000210201090700000000054e0000063f
77 454e65740400000000001c7c00000000000001004d00000002004275746c000003100000000000002300cc0121453a7ffffffd01073488090005000000002102010908000000000000000005ee
77 454e65740400000000001c7c00000000000001004d00000002004275746c000004100000000000002300cc0121453a7ffffffd0107348809000600000000210201090900630000000000000653
77 454e65740400000000001c7c00000000000001004d00000002004275746c000007100000000000002300cc0121453a7ffffffd0107348809000700000000210201090a000000000000000005f2
77 454e65740400000000001c7c00000000000001004d00000002004275746c000008100000000000002300cc0121453a7ffffffd0107348809000800000000210201090b00ec00000000000006e0
77 454e65740400000000001c7c00000000000001004d00000002004275746c00000a100000000000002300cc0121453a7ffffffd0107348809000900000000210201090c000000000000000005f6
77 454e65740400000000001c7c00000000000001004d00000002004275746c00000c100000000000002300cc0121453a7ffffffd0107348809000a00000000210201090d005c00081536000006a7
77 454e65740400000000001c7c00000000000001004d00000002004275746c00000e100000000000002300cc0121453a7ffffffd0107348809000b00000000210201090e00000000057c0000067b
77 454e65740400000000001c7c00000000000001004d00000002004275746c000010100000000000002300cc0121453a7ffffffd0107348809000c00000000210201090f000000001dbb000006d4
77 454e65740400000000001c7c00000000000001004d00000002004275746c000012100000000000002300cc0121453a7ffffffd0107348809000d000000002102010910000000000000000005fe
69 454e65740400000000001c7c00000000000001004500000002004275746c000014100000000000001b00cc0119453a7ffffffd0107346701250e00000000210403017f065e
69 454e65740400000000001c7c00000000000001004500000002004275746c000016100000000000001b00cc0119453a7ffffffd0899500026c90f00000000210403010106f8
69 454e65740400000000001c7c00000000000001004500000002004275746c000019100000000000001b00cc0119453a7ffffffd0107348809001000000000210403017f0664
65 454e65740400000000001c7c00000000000001004100000002004275746c01001f100000000000001700fefefefefefffffffdf5fdeff7ddf7ddd5f775f5d7ffdf
66 454e65740400000000001c7c00000000000001004200000002004275746c010021100000000000001800fefefefffffffcfdddfd7ffbffb5f5aeae7bffdf7f75ffff
66 454e65740400000000001c7c00000000000001004200000002004275746c010023100000000000001800fefefffffdfffff7ba357fd7ddf75d55bbdff7dfffef9ddf
67 454e65740400000000001c7c00000000000001004300000002004275746c010025100000000000001900feffffffffffffffabd5353dff5fabf7ddf57debd7bd75ffff
57 454e65740400000000001c7c00000000000001003900000002004275746c010028100000000000000f00fefeffffffffffffd5ffffffffdfff
59 454e65740400000000001c7c00000000000001003b00000002004275746c01002c100000000000001100aaab55af57be75efffab5deff5aed7ffff
62 454e65740400000000001c7c00000000000001003e00000002004275746c010030100000000000001400fefefefefefefefffeffabffffffffffae57efd7
66 454e65740400000000001c7c00000000000001004200000002004275746c010032100000000000001800fefefeffffffffffffffff7d7f757ffff7bdfdeff7b5dfff
54 454e65740400000000001c7c00000000000001003600000002004275746c010034100000000000000c00fefe75fffffffbbbbbafdfff
60 454e65740400000000001c7c00000000000001003c00000002004275746c010036100000000000001200fefefefeffff5fffdff57d7ff7ff3fddedd7
54 454e65740400000000001c7c00000000000001003600000002004275746c010038100000000000000c00fefefffff5fffbfbfbffffd7
58 454e65740400000000001c7c00000000000001003a00000002004275746c01003e100000000000001000fefefefffdff5ffff7f7fddf7f57efd7
66 454e65740400000000001c7c00000000000001004200000002004275746c010042100000000000001800fefefefefffffffffffffffdff75ff57f7fddf5f7357efd7
56 454e65740400000000001c7c00000000000001003800000002004275746c010044100000000000000e00fff57dfdfffffddf7dfd5fff7fff
61 454e65740400000000001c7c00000000000001003d00000002004275746c010046100000000000001300fefefefffffffdb7fdf375f5fffdfbbf57efd7
60 454e65740400000000001c7c00000000000001003c00000002004275746c010048100000000000001200fefefefefeffffffeed7ffbfbfffcd777bd7
53 454e65740400000000001c7c00000000000001003500000002004275746c01004c100000000000000b00fefefeffffffffffb5dfff
64 454e65740400000000001c7c00000000000001004000000002004275746c010051100000000000001600fefefefefefefffffffff777f777f7ff7f55ae57efd7
70 454e65740400000000001c7c00000000000001004600000002004275746c010052100000000000001c00fefefefefefefeffffffffbeffffbfd7bef7badfabfdeb55ae57efd7
53 454e65740400000000001c7c00000000000001003500000002004275746c010058100000000000000b00febfffbfbfffffffbff5fd
50 454e65740400000000001c7c00000000000001003200000002004275746c01005c100000000000000800fefdfffffffffffd
59 454e65740400000000001c7c00000000000001003b00000002004275746c01005f100000000000001100aaab55af57be75ef77ab55af75ae57ebd7
70 454e65740400000000001c7c00000000000001004600000002004275746c0000a7100000000000001c00cc011a453a7ffffffd01073467012557000000001100020200000615
87 454e65740400000000001c7c00000000000001005700000002004275746c0000aa100000000000002d00cc012b453a7ffffffd01073467012558000000002100601301000103050900010001000201010065000017073b
59 454e65740400000000001c7c00000000000001003b00000002004275746c0100be100000000000001100aaab55af57be75aaddab5daa55ae57efd7
70 454e65740400000000001c7c00000000000001004600000002004275746c000006110000000000001c00cc011a453a7ffffffd0107348809008800000000110002020000064a
87 454e65740400000000001c7c00000000000001005700000002004275746c000008110000000000002d00cc012b453a7ffffffd010734880900890000000021006013010002000509000100010001010100700000110772
70 454e65740400000000001c7c00000000000001004600000002004275746c0000b6110000000000001c00cc011a453a7ffffffd0899500026c9df0000000011000202000007b4
87 454e65740400000000001c7c00000000000001005700000002004275746c0000b7110000000000002d00cc012b453a7ffffffd0899500026c9e0000000002100601301000100010001000905002000000001000004087c
70 454e65740400000000001c7c00000000000001004600000002004275746c000006120000000000001c00cc011a453a7ffffffd01073467012508000000002100f00200650729
69 454e65740400000000001c7c00000000000001004500000002004275746c000008120000000000001b00cc0119453a7ffffffd010734670125090000000021801001010664
69 454e65740400000000001c7c00000000000001004500000002004275746c00000b120000000000001b00cc0119453a7ffffffd0107346701250a00000000218011013c06a1
69 454e65740400000000001c7c00000000000001004500000002004275746c00000d120000000000001b00cc0119453a7fbffffd0107346701250b0000000021801301010669
69 454e65740400000000001c7c00000000000001004500000002004275746c00000d120000000000001b00cc0119453a7ffffffd0107346701250b0000000021801301010669
69 454e65740400000000001c7c00000000000001004500000002004275746c000010120000000000001b00cc0119453a7ffffffd0107346701250c0000000021802001000676
69 454e65740400000000001c7c00000000000001004500000002004275746c000011120000000000001b00cc0119453a7ffffffd0107346701250d0000000021802101010679
69 454e65740400000000001c7c00000000000001004500000002004275746c000013120000000000001b00cc0119453a7ffffffd0107346701250e000000002180220101067b
69 454e65740400000000001c7c00000000000001004500000002004275746c000015120000000000001b00cc0119453a7ffffffd0107346701250f000000002180230100067c
69 454e65740400000000001c7c00000000000001004500000002004275746c000017120000000000001b00cc0119453a7ffffffd01073467012510000000002180400101069b
69 454e65740400000000001c7c00000000000001004500000002004275746c000019120000000000001b00cc0119453a7ffffffd010734670125110000000021804101fe079a
69 454e65740400000000001c7c00000000000001004500000002004275746c00001b120000000000001b00cc0119453a7ffffffd010734670125120000000021804201ff079d
69 454e65740400000000001c7c00000000000001004500000002004275746c00001d120000000000001b00cc0119453a7ffffffd010734670125130000000021804301ff079f
69 454e65740400000000001c7c00000000000001004500000002004275746c00001f120000000000001b00cc0119453a7ffffffd0107346701251400000000218050010106af
69 454e65740400000000001c7c00000000000001004500000002004275746c000021120000000000001b00cc0119453a7ffffffd0107346701251500000000218051010006b0
69 454e65740400000000001c7c00000000000001004500000002004275746c000023120000000000001b00cc0119453a7ffffffd0107346701251600000000218052010106b3
69 454e65740400000000001c7c00000000000001004500000002004275746c000025120000000000001b00cc0119453a7ffffffd0107346701251700000000218053010006b4
71 454e65740400000000001c7c00000000000001004700000002004275746c000027120000000000001d00cc011b453a7ffffffd0107346701251800000000218060030933470749
72 454e65740400000000001c7c00000000000001004800000002004275746c000029120000000000001e00cc011c453a7ffffffd0107346701251900000000218061040000a36507d2
69 454e65740400000000001c7c00000000000001004500000002004275746c00002b120000000000001b00cc0119453a7ffffffd0107346701251a00000000218062017f0745
70 454e65740400000000001c7c00000000000001004600000002004275746c00002e120000000000001c00cc011a453a7ffffffd0107346701251b0000000021806302050306d2
70 454e65740400000000001c7c00000000000001004600000002004275746c000030120000000000001c00cc011a453a7ffffffd0107346701251c0000000021806402000006cc
72 454e65740400000000001c7c00000000000001004800000002004275746c000031120000000000001e00cc011c453a7ffffffd0107346701251d00000000218065040000000006d2
69 454e65740400000000001c7c00000000000001004500000002004275746c000033120000000000001b00cc0119453a7ffffffd0107346701251e00000000218070010006d8
69 454e65740400000000001c7c00000000000001004500000002004275746c000035120000000000001b00cc0119453a7ffffffd0107346701251f00000000218071010006da
69 454e65740400000000001c7c00000000000001004500000002004275746c000037120000000000001b00cc0119453a7ffffffd0107346701252000000000218072010006dc
69 454e65740400000000001c7c00000000000001004500000002004275746c000039120000000000001b00cc0119453a7ffffffd0107346701252100000000218073010006de
69 454e65740400000000001c7c00000000000001004500000002004275746c00003b120000000000001b00cc0119453a7ffffffd0107346701252200000000218074010006e0
71 454e65740400000000001c7c00000000000001004700000002004275746c00003d120000000000001d00cc011b453a7ffffffd010734670125230000000021808003000069075a
72 454e65740400000000001c7c00000000000001004800000002004275746c000040120000000000001e00cc011c453a7ffffffd0107346701252400000000218081040009c587084a
69 454e65740400000000001c7c00000000000001004500000002004275746c000041120000000000001b00cc0119453a7ffffffd0107346701252500000000218082011b070c
70 454e65740400000000001c7c00000000000001004600000002004275746c000043120000000000001c00cc011a453a7ffffffd01073467012526000000002180830208e507e2
70 454e65740400000000001c7c00000000000001004600000002004275746c000045120000000000001c00cc011a453a7ffffffd010734670125270000000021808402000b0702
72 454e65740400000000001c7c00000000000001004800000002004275746c000047120000000000001e00cc011c453a7ffffffd010734670125280000000021808504000000040701
72 454e65740400000000001c7c00000000000001004800000002004275746c000049120000000000001e00cc011c453a7ffffffd010734670125290000000021808704000000190719
69 454e65740400000000001c7c00000000000001004500000002004275746c00004b120000000000001b00cc0119453a7ffffffd0107346701252a0000000021809001320736
77 454e65740400000000001c7c00000000000001004d00000002004275746c00004d120000000000002300cc0121453a7ffffffd0107346701252b0000000021020109000280000000000000068a
77 454e65740400000000001c7c00000000000001004d00000002004275746c00004f120000000000002300cc0121453a7ffffffd0107346701252c000000002102010901001b0000000000000625
77 454e65740400000000001c7c00000000000001004d00000002004275746c000051120000000000002300cc0121453a7ffffffd0107346701252d00000000210201090208dd08bc0940000007fe
77 454e65740400000000001c7c00000000000001004d00000002004275746c000053120000000000002300cc0121453a7ffffffd0107346701252e0000000021020109030010000505c9000006f1
77 454e65740400000000001c7c00000000000001004d00000002004275746c000055120000000000002300cc0121453a7ffffffd0107346701252f00000000210201090400320000000000000642
77 454e65740400000000001c7c00000000000001004d00000002004275746c000057120000000000002300cc0121453a7ffffffd0107346701253000000000210201090500040004087f000006a1
77 454e65740400000000001c7c00000000000001004d00000002004275746c000059120000000000002300cc0121453a7ffffffd0107346701253100000000210201090600000000000000000614
77 454e65740400000000001c7c00000000000001004d00000002004275746c00005b120000000000002300cc0121453a7ffffffd010734670125320000000021020109070019000b0d2000000667
77 454e65740400000000001c7c00000000000001004d00000002004275746c00005d120000000000002300cc0121453a7ffffffd0107346701253300000000210201090800000000000000000618
77 454e65740400000000001c7c00000000000001004d00000002004275746c00005e120000000000002300cc0121453a7ffffffd010734670125340000000021020109090010000000000000062a
77 454e65740400000000001c7c00000000000001004d00000002004275746c000060120000000000002300cc0121453a7ffffffd0107346701253500000000210201090a0000000000000000061c
77 454e65740400000000001c7c00000000000001004d00000002004275746c000062120000000000002300cc0121453a7ffffffd0107346701253600000000210201090b00290000000000000647
77 454e65740400000000001c7c00000000000001004d00000002004275746c000064120000000000002300cc0121453a7ffffffd0107346701253700000000210201090c007f000000000000069f
77 454e65740400000000001c7c00000000000001004d00000002004275746c000066120000000000002300cc0121453a7ffffffd0107346701253800000000210201090d04fa002a163200000792
77 454e65740400000000001c7c00000000000001004d00000002004275746c000068120000000000002300cc0121453a7ffffffd0107346701253900000000210201090e0000000000c7000006eb
77 454e65740400000000001c7c00000000000001004d00000002004275746c00006a120000000000002300cc0121453a7ffffffd0107346701253a00000000210201090f00000000037e000006a7
77 454e65740400000000001c7c00000000000001004d00000002004275746c00006c120000000000002300cc0121453a7ffffffd0107346701253b00000000210201091000000000000000000628
77 454e65740400000000001c7c00000000000001004d00000002004275746c00006e120000000000002300cc0121453a7ffffffd0107346701253c0000000021020109110012000000000000063c
77 454e65740400000000001c7c00000000000001004d00000002004275746c000070120000000000002300cc0121453a7ffffffd0107346701253d000000002102010912007f00000000000006ab
77 454e65740400000000001c7c00000000000001004d00000002004275746c000073120000000000002300cc0121453a7ffffffd0107346701253e000000002102010913051c0044163b000006e4
77 454e65740400000000001c7c00000000000001004d00000002004275746c000074120000000000002300cc0121453a7ffffffd0107346701253f00000000210201091400000000001500000645
77 454e65740400000000001c7c00000000000001004d00000002004275746c000076120000000000002300cc0121453a7ffffffd0107346701254000000000210201091500000000004200000674
77 454e65740400000000001c7c00000000000001004d00000002004275746c000078120000000000002300cc0121453a7ffffffd0107346701254100000000210201091600000000000000000634
70 454e65740400000000001c7c00000000000001004600000002004275746c00007a120000000000001c00cc011a453a7ffffffd0899500026c942000000002100f00200010816
77 454e65740400000000001c7c00000000000001004d00000002004275746c00007c120000000000002300cc0121453a7ffffffd0899500026c9430000000021020109000ce40000000000000827
77 454e65740400000000001c7c00000000000001004d00000002004275746c00007f120000000000002300cc0121453a7ffffffd0899500026c94400000000210201090109450000000000000787
77 454e65740400000000001c7c00000000000001004d00000002004275746c000080120000000000002300cc0121453a7ffffffd0899500026c94500000000210201090200180000000000000753
77 454e65740400000000001c7c00000000000001004d00000002004275746c000082120000000000002300cc0121453a7ffffffd0899500026c94600000000210201090320dd000000000000083a

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003b00000002004275746c01000b0f0000000000001100aaaa5dbbddfa55aa55ae77ebddaf57fadf

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003b00000002004275746c01000b0f0000000000001100aaaa5dbbddfa55aa55ae77ebddaf57fadf

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003b00000002004275746c01005f100000000000001100aaab55af57be75ef77ab55af75ae57ebd7

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003b00000002004275746c0100be100000000000001100aaab55af57be75aaddab5daa55ae57efd7

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003b00000002004275746c0100790f0000000000001100aaff7fffffaa5feaf5bad7ab7dab57ab77

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003b00000002004275746c01002c100000000000001100aaab55af57be75efffab5deff5aed7ffff

1
samples/rdm_req_1126.hex Normal file
View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001003c040000010000000000000002007f0f000000000000cc00000001000100000025000000010000000124ffffffffffff453a7ffffffccc010000001000010c80007ffffffffffffffffffe15c291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a0908070605040302014a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201

1
samples/rdm_resp_111.hex Normal file
View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001004500000002004275746c0000b70f0000000000001b00cc0119453a7ffffffd010734880900dd00000000218011013c0778

View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001004400000002004275746c00006efa0600000000001a00cc0118453a7fffffff010734880900b1000000003100f000077f

1
samples/register_89.hex Normal file
View File

@ -0,0 +1 @@
454e65740200000000001c7c00000000000001002f0000000200017ba8c000161cf12e3900717ba8c0300ed559bb36

1
samples/status_84.hex Normal file
View File

@ -0,0 +1 @@
454e65740400000000001c7c00000000000001002a00000002004275746c010062000000010000000000

1226
src/aech.rs Normal file

File diff suppressed because it is too large Load Diff

1201
src/dmxhash.rs Normal file

File diff suppressed because it is too large Load Diff

239
src/enet.rs Normal file
View File

@ -0,0 +1,239 @@
// enet.rs - e:net 协议公共设施
use std::net::{SocketAddr, UdpSocket};
use std::time::{Duration, Instant};
pub const BIND: &str = "192.168.123.113:31772";
pub const BUTLER: &str = "192.168.123.1:31772";
pub const BCAST_SUB: &str = "192.168.123.255:31772";
pub const BCAST_ALL: &str = "255.255.255.255:31772";
// ---------- 瀛楁璇诲啓 (灏忕) ----------
pub fn g16(b: &[u8], o: usize) -> u16 { b[o] as u16 | (b[o + 1] as u16) << 8 }
pub fn g32(b: &[u8], o: usize) -> u32 {
b[o] as u32 | (b[o + 1] as u32) << 8 | (b[o + 2] as u32) << 16 | (b[o + 3] as u32) << 24
}
pub fn p16(b: &mut [u8], o: usize, v: u16) { b[o] = v as u8; b[o + 1] = (v >> 8) as u8; }
pub fn p32(b: &mut [u8], o: usize, v: u32) {
b[o] = v as u8; b[o + 1] = (v >> 8) as u8; b[o + 2] = (v >> 16) as u8; b[o + 3] = (v >> 24) as u8;
}
pub fn find(b: &[u8], pat: &[u8]) -> bool {
b.windows(pat.len()).any(|w| w == pat)
}
pub fn hexs(b: &[u8]) -> String { b.iter().map(|x| format!("{:02x}", x)).collect() }
// ---------- 妯℃澘鍔犺浇 (缂栬瘧鏈熷祵鍏?pcap 瀹炴祴鏍锋湰) ----------
/// 真机 RDM 应答帧池: 每行 "<载荷长度> <hex>"
pub fn load_rdm_pool() -> Vec<Vec<u8>> {
let txt = include_str!("../samples/rdm_pool.txt");
let mut out = Vec::new();
for line in txt.lines() {
let line = line.trim();
if line.is_empty() { continue; }
if let Some(pos) = line.find(' ') {
let hex = line[pos + 1..].trim();
let b: Vec<u8> = (0..hex.len()).step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()).collect();
out.push(b);
}
}
out
}
pub fn load_template(name: &str) -> Vec<u8> {
let hex = match name {
"discovery_req" => include_str!("../samples/discovery_req_222.hex"),
"discovery_resp" => include_str!("../samples/discovery_resp_259.hex"),
"announce" => include_str!("../samples/announce_686.hex"),
"announce_live" => include_str!("../samples/announce_live.hex"),
"heartbeat" => include_str!("../samples/heartbeat_78.hex"),
"euce" => include_str!("../samples/euce_582.hex"),
"dmx_a" => include_str!("../samples/dmx_a_1126.hex"),
"event_b" => include_str!("../samples/event_b_1126.hex"),
"ack" => include_str!("../samples/ack_84.hex"),
"rdm_req" => include_str!("../samples/rdm_req_1126.hex"),
"rdm_disc_req" => include_str!("../samples/rdm_disc_req_1126.hex"),
"claim_h112" => include_str!("../samples/claim_h112.hex"),
"claim_h114" => include_str!("../samples/claim_h114.hex"),
"claim_h116" => include_str!("../samples/claim_h116.hex"),
"aerep" => include_str!("../samples/aerep_113.hex"),
"status" => include_str!("../samples/status_84.hex"),
"rdm_pulse_0" => include_str!("../samples/rdm_pulse_101_0.hex"),
"rdm_pulse_1" => include_str!("../samples/rdm_pulse_101_1.hex"),
"rdm_pulse_2" => include_str!("../samples/rdm_pulse_101_2.hex"),
"rdm_pulse_3" => include_str!("../samples/rdm_pulse_101_3.hex"),
"rdm_pulse_4" => include_str!("../samples/rdm_pulse_101_4.hex"),
"rdm_resp111" => include_str!("../samples/rdm_resp_111.hex"),
"rdm_setack68" => include_str!("../samples/rdm_setack_68.hex"),
"rdm_info119" => include_str!("../samples/rdm_info_119.hex"),
"register89" => include_str!("../samples/register_89.hex"),
"aere594" => include_str!("../samples/aere_594.hex"),
"announce144" => include_str!("../samples/announce_144.hex"),
"rdm_disc_req" => include_str!("../samples/rdm_disc_req_1126.hex"),
_ => panic!("unknown template {}", name),
};
let h = hex.trim();
(0..h.len()).step_by(2).map(|i| u8::from_str_radix(&h[i..i + 2], 16).unwrap()).collect()
}
// ---------- Socket ----------
pub fn open() -> UdpSocket {
let s = UdpSocket::bind(BIND).expect("bind 192.168.123.113:31772 澶辫触(缃戝彛/绔彛鍗犵敤?)");
s.set_broadcast(true).unwrap();
s.set_read_timeout(Some(Duration::from_millis(2))).unwrap();
s
}
/// SO_REUSEADDR bind (Windows)
#[cfg(target_os = "windows")]
pub fn open_reuse(bind_addr: &str) -> UdpSocket {
use std::os::windows::io::FromRawSocket;
#[repr(C)]
struct SockAddrIn { sin_family: u16, sin_port: u16, sin_addr: u32, sin_zero: [u8; 8] }
#[repr(C)]
struct WsaData { wversion: u16, whighversion: u16, szdescription: [u8; 257], szsystemstatus: [u8; 129], imaxsockets: u16, imaxudpdg: u16, lpvendorinfo: usize }
#[link(name = "ws2_32")]
extern "system" {
fn WSAStartup(wver: u16, data: *mut WsaData) -> i32;
fn socket(af: i32, ty: i32, proto: i32) -> usize;
fn setsockopt(s: usize, level: i32, optname: i32, optval: *const u8, optlen: i32) -> i32;
fn bind(s: usize, addr: *const SockAddrIn, len: i32) -> i32;
}
unsafe {
let mut wd: WsaData = std::mem::zeroed();
WSAStartup(0x0202, &mut wd);
let s = socket(2, 2, 17); // AF_INET, SOCK_DGRAM, IPPROTO_UDP
assert!(s != usize::MAX, "ws socket 澶辫触");
const SOL_SOCKET: i32 = 0xFFFF;
const SO_REUSEADDR: i32 = 4;
const SO_BROADCAST: i32 = 6;
let one: u8 = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, 1);
setsockopt(s, SOL_SOCKET, SO_BROADCAST, &one, 1);
let (ip, port) = bind_addr.rsplit_once(':').unwrap();
let o: Vec<u8> = ip.split('.').map(|x| x.parse().unwrap()).collect();
let a = SockAddrIn {
sin_family: 2,
sin_port: port.parse::<u16>().unwrap().to_be(),
sin_addr: u32::from_ne_bytes([o[0], o[1], o[2], o[3]]),
sin_zero: [0; 8],
};
let r = bind(s, &a, std::mem::size_of::<SockAddrIn>() as i32);
if r != 0 {
let err: i32 = { extern "system" { fn WSAGetLastError() -> i32; } WSAGetLastError() };
panic!("reuse bind {} 澶辫触 r={} WSAE={}", bind_addr, r, err);
}
let u = UdpSocket::from_raw_socket(s as _);
u.set_broadcast(true).unwrap();
u.set_read_timeout(Some(Duration::from_millis(2))).unwrap();
u
}
}
/// Linux: 鏍囧噯 bind 鍗冲厑璁稿杩涚▼ SO_REUSEADDR 鍏卞瓨, 鐩存帴杩斿洖 (daemon 鐢?
#[cfg(not(target_os = "windows"))]
pub fn open_reuse(bind_addr: &str) -> UdpSocket {
use std::net::UdpSocket as S;
use std::os::unix::io::FromRawFd;
#[repr(C)]
struct SockAddrIn { sin_family: u16, sin_port: u16, sin_addr: u32, sin_zero: [u8; 8] }
#[link(name = "c")]
extern "C" {
fn socket(af: i32, ty: i32, proto: i32) -> i32;
fn setsockopt(s: i32, level: i32, optname: i32, optval: *const u8, optlen: u32) -> i32;
fn bind(s: i32, addr: *const SockAddrIn, len: u32) -> i32;
}
const AF_INET: i32 = 2;
const SOCK_DGRAM: i32 = 2;
const IPPROTO_UDP: i32 = 17;
const IPPROTO_IP: i32 = 0;
const IP_TTL: i32 = 2;
const IP_MTU_DISCOVER: i32 = 10;
const SOL_SOCKET: i32 = 1;
const SO_REUSEADDR: i32 = 2;
const SO_BROADCAST: i32 = 6;
unsafe {
let s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
assert!(s >= 0, "socket 澶辫触");
let one: i32 = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one as *const i32 as *const u8, 4);
setsockopt(s, SOL_SOCKET, SO_BROADCAST, &one as *const i32 as *const u8, 4);
// 鎸囩汗瀵归綈鐪熸満 Butler: TTL=100, 鍏抽棴 DF (PMTUDISC_DONT)
let ttl: i32 = 100;
setsockopt(s, IPPROTO_IP, IP_TTL, &ttl as *const i32 as *const u8, 4);
let pmtu: i32 = 0; // IP_PMTUDISC_DONT
setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu as *const i32 as *const u8, 4);
let (ip, port) = bind_addr.rsplit_once(':').unwrap();
let o: Vec<u8> = ip.split('.').map(|x| x.parse().unwrap()).collect();
let a = SockAddrIn {
sin_family: AF_INET as u16,
sin_port: port.parse::<u16>().unwrap().to_be(),
sin_addr: u32::from_ne_bytes([o[0], o[1], o[2], o[3]]),
sin_zero: [0; 8],
};
let r = bind(s, &a, std::mem::size_of::<SockAddrIn>() as u32);
if r != 0 {
panic!("reuse bind {} 澶辫触 r={} errno={}", bind_addr, r, std::io::Error::last_os_error());
}
let u = S::from_raw_fd(s);
u.set_broadcast(true).unwrap();
u.set_read_timeout(Some(Duration::from_millis(2))).unwrap();
u
}
}
/// 鍦?dur 鏃堕棿绐楀唴鏀堕泦鎵€鏈夊埌杈剧殑 UDP 包)
pub fn recv_window(s: &UdpSocket, dur: Duration) -> Vec<(SocketAddr, Vec<u8>)> {
let deadline = Instant::now() + dur;
let mut out = Vec::new();
loop {
let now = Instant::now();
if now >= deadline { break; }
s.set_read_timeout(Some(deadline.saturating_duration_since(now))).unwrap();
let mut buf = [0u8; 2048];
match s.recv_from(&mut buf) {
Ok((n, a)) => out.push((a, buf[..n].to_vec())),
Err(_) => break, // 瓒呮椂
}
}
out
}
// ---------- 鏀跺寘鍒嗙被 ----------
#[derive(Debug, Clone)]
pub enum Rx {
/// Butler 鍙戠幇搴旂瓟 (259B 甯?/ 217B 璐熻浇)
DiscoveryResp(Vec<u8>),
/// Butler ACK (84B 甯?/ 42B 璐熻浇) ack_seq
Ack(u32),
/// Butler 鐘舵€佸抚 (>90B 璐熻浇, 0200+Butl)
Status(Vec<u8>),
/// RDM 搴旂瓟 (璐熻浇 60-90B, 0x28 闀垮害 + cc01)
RdmResp(Vec<u8>, usize),
/// 涓绘満瀹e憡搴旂瓟/鍏朵粬 ENet type=01
EnetType1(usize),
/// 鍏跺畠
Other(usize),
}
pub fn classify(b: &[u8]) -> Rx {
if b.len() < 8 || &b[0..4] != b"ENet" {
return Rx::Other(b.len());
}
let t = g32(b, 4);
if t == 1 { return Rx::EnetType1(b.len()); }
if t == 4 {
if b.len() == 42 && g16(b, 0x18) == 2 && &b[0x1A..0x1E] == b"Butl" {
return Rx::Ack(g32(b, 0x20));
}
if b.len() > 0x2C && g16(b, 0x18) == 2 && &b[0x1A..0x1E] == b"Butl" {
let rlen = g16(b, 0x28) as usize;
if b.len() >= 0x2A + rlen && b[0x2A] == 0xCC && b[0x2B] == 0x01 {
return Rx::RdmResp(b.to_vec(), rlen);
}
return Rx::Status(b.to_vec());
}
if b.len() == 217 { return Rx::DiscoveryResp(b.to_vec()); }
}
Rx::Other(b.len())
}

909
src/hostdemo.rs Normal file
View File

@ -0,0 +1,909 @@
//! 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<u8> {
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<u8> {
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 起始字节, 从 CC 起 msgLen 字节 (不含自身), BE
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;
f
}
/// 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);
let sock = open_reuse(&format!("{}:31772", bind_ip)); // 源 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);
// ---- 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; }
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<u8> = 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; }
}
}
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());
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<u8> = 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 才武装认领应答
let pre_secs: u64 = std::env::var("HOSTDEMO_PRE").ok().and_then(|s| s.parse().ok()).unwrap_or(45);
// ★宣告帧用 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<usize> = (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);
}
}
let mut ident = IDENT102.to_vec();
ident[0x1C..0x20].copy_from_slice(&[o[3], o[2], o[1], o[0]]);
// ★sub=07 绑定帧 ×4: ffff→0000→ffff→0000 (间隔 ~0.32s, sym_startup @98.8-99.5 实测)
// 1.8s 后发 ident
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));
let mut ident = IDENT102.to_vec();
ident[0x1C..0x20].copy_from_slice(&[o[3], o[2], o[1], o[0]]);
sock.send_to(&ident, &dst).unwrap();
println!("[2] 冷启动前奏完成 ({}s 广播 + 绑定帧×4 + 身份帧)", 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; }
let nonce = rng.next();
let mut ch = load_template("claim_h112");
ch[0x1A..0x1E].copy_from_slice(&[o[3], o[2], o[1], o[0]]);
ch[0x88..0x8C].copy_from_slice(&nonce.to_le_bytes());
ch[0x8C..0x90].copy_from_slice(&b12.to_le_bytes());
sock.send_to(&ch, &dst).unwrap();
for _ in 0..10 {
if let Ok((n, src)) = sock.recv_from(&mut buf) {
let b = &buf[..n];
println!(" [dbg] AECH等待中收帧 len={} src={} magic={:?}", n, src, &b[0..4.min(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 第{}轮: resp=0x{:08x} G=0x{:08x} {}", round + 1, resp, want,
if resp == want { "★PASS" } else { "FAIL" });
if resp == want { claim_ok = true; }
break;
}
}
}
}
}
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<usize> = (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<u16> = 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<u16> = 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<u8> {
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));
}
}
/// 统一会话环 (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<u16> = Vec::new();
let mut tn: u8 = rng.byte();
let mut qseq: u16 = 1;
let mut seq: u32 = 1;
// 发现任务队列: (dest, cc, pid, pd) — None = 分支查询由 walk 状态机生成
let mut stack: Vec<(u64, u64)> = vec![(0, 0xFFFF_FFFF_FFFF)];
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 一发; ★SL 实测: 认领后 27.5s 纯 DMX, RDM 过早发可能重置武装计时) ---
if discovering && t > 30.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;
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;
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);
}

194
src/main.rs Normal file
View File

@ -0,0 +1,194 @@
// main.rs — enet-validator: e:net 协议独立验证程序 (模拟 e:cue 主机, 连接真机 Butler S2)
mod enet;
mod tests;
mod dmxhash;
mod aech;
mod hostdemo;
use tests::Item;
fn main() {
let arg = std::env::args().nth(1).unwrap_or_else(|| "help".into());
let mut results: Vec<Item> = Vec::new();
println!("enet-validator — e:net 协议实测验证 (Butler S2 @ 192.168.123.1)");
println!("================================================================");
match arg.as_str() {
"discover" => results.push(tests::t1_discover()),
"announce" => results.push(tests::tx_observe("T2", "announce", false)),
"heartbeat" => results.push(tests::tx_observe("T3", "heartbeat", false)),
"euce" => results.push(tests::tx_observe("T4", "euce", false)),
"session" => {
let secs: u64 = std::env::args().nth(2).and_then(|a| a.parse().ok()).unwrap_or(60);
results.extend(tests::t5_t8_session(secs));
}
"fallback" => results.push(tests::t9_fallback()),
"claim" => results.push(tests::t12_claim_replay()),
"takeover" => {
let secs: u64 = std::env::args().nth(2).and_then(|a| a.parse().ok()).unwrap_or(15);
results.extend(tests::t13_takeover(secs));
}
"holdbind" => {
// Rust 先 bind, 等待期间由 SymphoLight 认证; 之后 Rust 发帧测试共存
let s = enet::open_reuse("0.0.0.0:31772");
println!("Rust 已 bind 31772 (REUSEADDR)。等待 30s (此间启动 SymphoLight 认领)…");
std::thread::sleep(std::time::Duration::from_secs(30));
println!("开始发送测试帧 15s…");
results.extend(tests::t13_takeover_on(s, 15));
}
"device" => {
// 用法: device [设备IP] [MAC_hex12] [秒] (默认 192.168.123.200, 本机网卡MAC)
let ip_s = std::env::args().nth(2).unwrap_or_else(|| "192.168.123.200".into());
let mac_s = std::env::args().nth(3).unwrap_or_else(|| "300ed559bb36".into());
let secs: u64 = std::env::args().nth(4).and_then(|a| a.parse().ok()).unwrap_or(180);
let relay_s = std::env::args().nth(5); // 第5参: 中继目标 ip[:port] (默31772)
let ip: Vec<u8> = ip_s.split('.').filter_map(|x| x.parse().ok()).collect();
let mac: Vec<u8> = (0..mac_s.len()).step_by(2).filter_map(|i| u8::from_str_radix(&mac_s[i..i + 2], 16).ok()).collect();
let relay: Option<([u8; 4], u16)> = relay_s.and_then(|s| {
let (ips, port) = match s.rsplit_once(':') {
Some((a, p)) => (a.to_string(), p.parse::<u16>().unwrap_or(31772)),
None => (s, 31772),
};
let o: Vec<u8> = ips.split('.').filter_map(|x| x.parse().ok()).collect();
if o.len() == 4 { Some(([o[0], o[1], o[2], o[3]], port)) } else { None }
});
if ip.len() == 4 && mac.len() == 6 {
let mut ipa = [0u8; 4]; ipa.copy_from_slice(&ip);
let mut maca = [0u8; 6]; maca.copy_from_slice(&mac);
results.extend(tests::device2(ipa, maca, secs, relay));
} else {
println!("device 参数错误");
}
}
"rdm" => {
let secs: u64 = std::env::args().nth(2).and_then(|a| a.parse().ok()).unwrap_or(30);
results.extend(tests::t10_t11_rdm(secs));
}
"bridge" => {
// bridge <listen_ip:port> <real_ip> [secs] — AECH 桥 (非123网段设备认领桥)
let lp = std::env::args().nth(2).unwrap_or_else(|| "192.168.58.107:41772".into());
let rip_s = std::env::args().nth(3).unwrap_or_else(|| "192.168.123.1".into());
let secs: u64 = std::env::args().nth(4).and_then(|a| a.parse().ok()).unwrap_or(7200);
let (lip_s, lport) = match lp.rsplit_once(':') {
Some((a, p)) => (a.to_string(), p.parse::<u16>().unwrap_or(41772)),
None => (lp, 41772),
};
let lip: Vec<u8> = lip_s.split('.').filter_map(|x| x.parse().ok()).collect();
let rip: Vec<u8> = rip_s.split('.').filter_map(|x| x.parse().ok()).collect();
if lip.len() == 4 && rip.len() == 4 {
tests::bridge([lip[0], lip[1], lip[2], lip[3]], lport, [rip[0], rip[1], rip[2], rip[3]], secs);
} else {
println!("bridge 参数错误");
}
return;
}
"all" => {
results.push(tests::t1_discover());
results.push(tests::tx_observe("T2", "announce", false));
results.push(tests::tx_observe("T3", "heartbeat", false));
results.push(tests::tx_observe("T4", "euce", false));
results.extend(tests::t5_t8_session(60));
results.push(tests::t9_fallback());
}
"rdmprobe" => {
let target = std::env::args().nth(2).unwrap_or_else(|| "192.168.123.1".into());
let secs: u64 = std::env::args().nth(3).and_then(|a| a.parse().ok()).unwrap_or(10);
results.push(tests::rdm_probe(&target, secs));
}
"dmxhash" => {
// dmxhash <tsv> [主机IP] — DMX-A 帧标签算法爆破 (离线分析, 无网络)
let tsv = std::env::args().nth(2).unwrap_or_else(|| "dmx.tsv".into());
let host = std::env::args().nth(3).unwrap_or_else(|| "192.168.123.113".into());
dmxhash::run(&tsv, &host);
return;
}
"evtf" => {
// evtf <tsv> [主机IP] [设备IP] [tsv2跨包] — 形态B事件帧结构分析 (离线)
let tsv = std::env::args().nth(2).unwrap_or_else(|| "dmx.tsv".into());
let host = std::env::args().nth(3).unwrap_or_else(|| "192.168.123.113".into());
let dev = std::env::args().nth(4).unwrap_or_else(|| "192.168.123.1".into());
let tsv2 = std::env::args().nth(5);
dmxhash::evt(&tsv, &host, &dev, tsv2.as_deref());
return;
}
"aech" => {
// aech <tsv1> [tsv2 ...] — AECH 密钥协商密码学分析 (离线)
let tsvs: Vec<String> = std::env::args().skip(2).collect();
if tsvs.is_empty() {
println!("用法: enet-validator aech <tsv1> [tsv2 ...]");
return;
}
aech::run(&tsvs);
return;
}
"aechprobe" => {
// aechprobe <tsv模板> <目标IP> — 选择密文仿射性分析 (在线探测真机)
let tsv = std::env::args().nth(2).unwrap_or_else(|| "cold2.tsv".into());
let target = std::env::args().nth(3).unwrap_or_else(|| "192.168.123.1".into());
aech::probe(&tsv, &target);
return;
}
"aechprobe2" => {
// aechprobe2 <tsv模板> <目标IP> — part1 块/字节影响图谱 (在线)
let tsv = std::env::args().nth(2).unwrap_or_else(|| "cold2.tsv".into());
let target = std::env::args().nth(3).unwrap_or_else(|| "192.168.123.1".into());
aech::probe2(&tsv, &target);
return;
}
"aechid" => {
// aechid <tsv> — f(last4) 离线候选函数鉴别
let tsv = std::env::args().nth(2).unwrap_or_else(|| "cold2.tsv".into());
aech::identify(&tsv);
return;
}
"dmxroll" => {
// dmxroll <tsv> — DMX 滚动值/保护字节数据驱动分析 (离线)
let tsv = std::env::args().nth(2).unwrap_or_else(|| "dmx.tsv".into());
aech::dmxroll(&tsv);
return;
}
"dmxdec" => {
// dmxdec <tsv> — DMX 帧加密假说验证 (kWarv47o79k/zPprv0TXRRW 逆运算, 需 TSV 含 AECH)
let tsv = std::env::args().nth(2).unwrap_or_else(|| "dmx.tsv".into());
aech::dmxdec(&tsv);
return;
}
"dmxsend" => {
// dmxsend <目标IP> [秒] — 主机侧等效播放: 认领 + 加密 DMX 全链路自证
let ip = std::env::args().nth(2).unwrap_or_else(|| "192.168.58.7".into());
let secs: u64 = std::env::args().nth(3).and_then(|s| s.parse().ok()).unwrap_or(30);
aech::dmxsend(&ip, secs);
return;
}
"hostdemo" => {
// hostdemo [目标IP] [秒] — 主机侧等效示例: 认领真机 + RDM 发现 + 加密 DMX 控制
// 源 IP 固定 192.168.123.113 (真机 .1 的 AECH 固件过滤); 秒=0 只做认领+RDM 不发 DMX
let ip = std::env::args().nth(2).unwrap_or_else(|| "192.168.123.1".into());
let secs: u64 = std::env::args().nth(3).and_then(|s| s.parse().ok()).unwrap_or(30);
hostdemo::run(&ip, "192.168.123.113", secs);
return;
}
_ => {
println!("用法: enet-validator <discover|announce|heartbeat|euce|session [秒]|fallback|claim|rdm [秒]|all>");
return;
}
}
println!("----------------------------------------------------------------");
let p = results.iter().filter(|r| r.pass).count();
for r in &results { r.show(); }
println!("================================================================");
println!("汇总: PASS={} FAIL={}", p, results.len() - p);
// 写 JSON 报告
let mut j = String::from("{\n \"target\": \"192.168.123.1\",\n \"results\": [\n");
for (i, r) in results.iter().enumerate() {
j.push_str(&format!(
" {{ \"id\": \"{}\", \"pass\": {}, \"detail\": \"{}\" }}{}\n",
r.id, r.pass, r.detail.replace('"', "'"),
if i + 1 < results.len() { "," } else { "" }));
}
j.push_str(" ]\n}\n");
let _ = std::fs::write("validation_report.json", j);
println!("报告已写: validation_report.json");
}

905
src/tests.rs Normal file
View File

@ -0,0 +1,905 @@
// tests.rs — T1..T11 协议验证实现 (判据 = Butler 实际应答)
use crate::enet::*;
use std::collections::HashSet;
use std::net::UdpSocket;
use std::time::{Duration, Instant};
pub struct Item {
pub id: &'static str,
pub pass: bool,
pub detail: String,
}
impl Item {
fn new(id: &'static str, pass: bool, detail: String) -> Item { Item { id, pass, detail } }
pub fn show(&self) {
println!(" [{}] {} {}", if self.pass { "PASS" } else { "FAIL" }, self.id, self.detail);
}
}
// ---------- T1: 发现请求 → Butler 应答 (§3.1→§3.2) ----------
pub fn t1_discover() -> Item {
let s = open();
let req = load_template("discovery_req");
let _ = s.send_to(&req, BCAST_SUB);
let _ = s.send_to(&req, BCAST_ALL);
let replies = recv_window(&s, Duration::from_secs(4));
let mut detail = format!("收到{}包; ", replies.len());
for (_, b) in &replies {
if b.len() == 217 && &b[0..4] == b"ENet" {
let len_ok = g32(b, 0x14) == 217;
let name_ok = find(b, b"Butler S2");
let mac_ok = find(b, &[0x00, 0x16, 0x1c, 0xf1, 0x2e, 0x39]);
let pass = len_ok && name_ok && mac_ok;
detail.push_str(&format!(
"应答217B: len字段={} 名字={} MAC={}",
len_ok, name_ok, mac_ok
));
return Item::new("T1", pass, detail);
}
}
detail.push_str("未收到 217B 发现应答");
Item::new("T1", false, detail)
}
// ---------- T2/T3/T4: 单向帧发送 → 观察会话反应 ----------
pub fn tx_observe(id: &'static str, tpl: &str, expect_reply: bool) -> Item {
let s = open();
let f = load_template(tpl);
let dst = if tpl == "heartbeat" { BCAST_SUB } else { BCAST_ALL };
let n = s.send_to(&f, dst).expect("send");
let replies = recv_window(&s, Duration::from_secs(3));
let mut detail = format!("发送{}B→{}; 收到{}包", n, dst, replies.len());
let mut pass = true;
if expect_reply {
pass = !replies.is_empty();
if !pass { detail.push_str("; 无应答"); }
}
Item::new(id, pass, detail)
}
// ---------- T5-T8: 会话 (DMX + 事件×2 + ACK配对 + 状态帧) ----------
pub struct SessionStats {
pub dmx_sent: u64,
pub evt_sent: u64,
pub acks: u64,
pub ack_matched: u64,
pub status: u64,
pub rdm: u64,
pub other: u64,
}
pub fn session(secs: u64, mutate_channels: bool) -> (SessionStats, Vec<(u32, u64)>) {
let s = open();
session_on(s, secs, mutate_channels, true)
}
/// 会话核心 (可注入 socket): takeover 模式跳过 preamble 且使用抢 bind 的 socket
pub fn session_on(s: UdpSocket, secs: u64, mutate_channels: bool, preamble: bool) -> (SessionStats, Vec<(u32, u64)>) {
if preamble {
// --- 前置认领 (§6 时序): 发现请求 → 宣告帧 → 短暂等待 ---
let disc = load_template("discovery_req");
let ann = load_template("announce");
let _ = s.send_to(&disc, BCAST_SUB);
let _ = s.send_to(&disc, BCAST_ALL);
let _ = s.send_to(&ann, BCAST_ALL);
let _ = recv_window(&s, Duration::from_secs(2)); // 吸收发现应答/宣告反应
}
let dmx_t = load_template("dmx_a");
let evt_t = load_template("event_b");
let mut dmx_seq = g16(&dmx_t, 0x1A).wrapping_add(1);
let mut evt_seq = g32(&evt_t, 0x22).wrapping_add(1);
let tick = Duration::from_millis(25);
let start = Instant::now();
let mut st = SessionStats { dmx_sent: 0, evt_sent: 0, acks: 0, ack_matched: 0, status: 0, rdm: 0, other: 0 };
let mut recent: HashSet<u32> = HashSet::new();
let mut timeline: Vec<(u32, u64)> = Vec::new();
let mut tick_n: u64 = 0;
while start.elapsed().as_secs() < secs {
// --- DMX 帧 (形态A) ---
let mut d = dmx_t.clone();
p16(&mut d, 0x1A, dmx_seq);
let tms = tick_n * 25;
for i in 0..8 { d[0x22 + i] = ((tms >> (i * 8)) & 0xFF) as u8; }
if mutate_channels {
let ph = start.elapsed().as_secs();
for i in 0..1024usize {
d[0x3C + i] = match ph {
0..=9 => 0,
10..=19 => 255,
_ => (((1024usize).wrapping_sub(i + (tick_n as usize) * 4)) & 0xFF) as u8,
};
}
}
let _ = s.send_to(&d, BUTLER);
st.dmx_sent += 1;
dmx_seq = dmx_seq.wrapping_add(1);
// --- 事件帧 ×2 (形态B) ---
for _ in 0..2 {
let mut e = evt_t.clone();
p32(&mut e, 0x22, evt_seq);
recent.insert(evt_seq);
if recent.len() > 64 { recent.remove(&(evt_seq.wrapping_sub(64))); }
let _ = s.send_to(&e, BUTLER);
st.evt_sent += 1;
evt_seq = evt_seq.wrapping_add(1);
}
// --- 收集应答 ---
for (_, b) in recv_window(&s, tick) {
match classify(&b) {
Rx::Ack(sq) => {
st.acks += 1;
if recent.contains(&sq) { st.ack_matched += 1; }
timeline.push((sq, tick_n));
}
Rx::Status(_) => st.status += 1,
Rx::RdmResp(_, _) => st.rdm += 1,
Rx::DiscoveryResp(_) | Rx::EnetType1(_) => {}
Rx::Other(_) => st.other += 1,
}
}
tick_n += 1;
}
(st, timeline)
}
pub fn t5_t8_session(secs: u64) -> Vec<Item> {
println!(" 会话运行 {}s (DMX→事件×2, 25ms/tick)…", secs);
let (st, _) = session(secs, true);
let rate_dmx = st.dmx_sent as f64 / secs as f64;
let pair = if st.acks > 0 { st.ack_matched as f64 / st.acks as f64 } else { 0.0 };
vec![
Item::new("T5", st.dmx_sent > secs * 30 && st.status > 0,
format!("DMX发送={} (~{:.0}Hz) 状态帧={}", st.dmx_sent, rate_dmx, st.status)),
Item::new("T6", pair >= 0.95,
format!("ACK={} 配对={} ({:.1}%)", st.acks, st.ack_matched, pair * 100.0)),
Item::new("T7", st.status > 0, format!("状态帧={}", st.status)),
Item::new("T8", st.dmx_sent > secs * 30 && st.acks > secs,
format!("会话{}s无中断", secs)),
]
}
// ---------- T9: 回落与恢复 ----------
pub fn t9_fallback() -> Item {
println!(" 阶段1: 会话 10s…");
let (a, _) = session(10, false);
println!(" 阶段2: 静默 12s (只收不发)…");
let s = open();
let quiet = recv_window(&s, Duration::from_secs(12));
let mut quiet_status = 0usize;
for (_, b) in &quiet {
if let Rx::Status(_) = classify(b) { quiet_status += 1; }
}
println!(" 阶段3: 恢复会话 8s…");
let (b, _) = session(8, false);
let pass = a.acks > 5 && b.acks > 5; // 恢复后 ACK 恢复 = 重新接管成功
Item::new("T9", pass, format!(
"会话前段ACK={}; 静默期收{}包(状态{}); 恢复后ACK={}",
a.acks, quiet.len(), quiet_status, b.acks))
}
// ---------- T12(实验): AECHA 认领重放 ----------
pub fn t12_claim_replay() -> Item {
let s = open();
let tick = Duration::from_millis(25);
let c1 = load_template("claim_h112");
let c2 = load_template("claim_h114");
let c3 = load_template("claim_h116");
let mut aereps = 0u64;
let mut last_detail = String::new();
for (i, f) in [&c1, &c2, &c3].into_iter().enumerate() {
let _ = s.send_to(f, BUTLER);
let replies = recv_window(&s, Duration::from_millis(800));
let mut got = false;
for (_, b) in &replies {
if find(b, b"AEREP") { aereps += 1; got = true; }
}
last_detail.push_str(&format!("[{}] 应答{}包{} ", i + 1, replies.len(), if got { "+AEREP" } else { "" }));
}
// 若 3 轮完成 → 立即试探 DMX: 发 10 个 tick 看是否出现 ACK
let mut acks = 0u64;
if aereps >= 1 {
let dmx_t = load_template("dmx_a");
let evt_t = load_template("event_b");
let mut dmx_seq = g16(&dmx_t, 0x1A).wrapping_add(1);
let mut evt_seq = g32(&evt_t, 0x22).wrapping_add(1);
for _ in 0..10 {
let mut d = dmx_t.clone();
p16(&mut d, 0x1A, dmx_seq);
let _ = s.send_to(&d, BUTLER);
dmx_seq = dmx_seq.wrapping_add(1);
for _ in 0..2 {
let mut e = evt_t.clone();
p32(&mut e, 0x22, evt_seq);
evt_seq += 1;
let _ = s.send_to(&e, BUTLER);
}
for (_, b) in recv_window(&s, tick) {
if let Rx::Ack(_) = classify(&b) { acks += 1; }
}
}
}
last_detail.push_str(&format!("; AEREP={} 试探ACK={}", aereps, acks));
Item::new("T12", acks > 0, last_detail)
}
/// takeover 变体: 使用外部传入的已 bind socket
pub fn t13_takeover_on(s: UdpSocket, secs: u64) -> Vec<Item> {
println!(" 发帧 {}s (共存模式)…", secs);
let (st, _) = session_on(s, secs, true, false);
let pair = if st.acks > 0 { st.ack_matched as f64 / st.acks as f64 } else { 0.0 };
vec![
Item::new("T13a", st.dmx_sent > secs * 20, format!("DMX发送={}", st.dmx_sent)),
Item::new("T13b", st.acks > 0, format!("Rust收到ACK={} 配对={:.1}% 状态={}",
st.acks, pair * 100.0, st.status)),
]
}
// ---------- T13(实验): 挂起 SymphoLight 后 Rust 抢占接管会话 ----------
pub fn t13_takeover(secs: u64) -> Vec<Item> {
// 用抢 bind 的 socket 跑会话核心 (无 preamble: 认证已由 SymphoLight 完成)
let s = open_reuse(BIND);
println!(" 接管会话 {}s (SO_REUSEADDR 模式)…", secs);
let (st, _) = session_on(s, secs, true, false);
let pair = if st.acks > 0 { st.ack_matched as f64 / st.acks as f64 } else { 0.0 };
vec![
Item::new("T13a", st.dmx_sent > secs * 20, format!("接管后 DMX发送={}", st.dmx_sent)),
Item::new("T13b", st.acks > 0, format!("ACK={} 配对={} ({:.1}%) 状态={}",
st.acks, st.ack_matched, pair * 100.0, st.status)),
]
}
// ---------- 设备端模拟 v2 (Secondary IP, 与真 Butler 并存) ----------
// 双 socket: wildcard(REUSEADDR) 收广播发现; dev_ip:31772 收会话/发应答(源IP正确)
pub fn device2(dev_ip: [u8; 4], dev_mac: [u8; 6], secs: u64, relay: Option<([u8; 4], u16)>) -> Vec<Item> {
let mut items = Vec::new();
let bind_addr = format!("{}.{}.{}.{}:31772", dev_ip[0], dev_ip[1], dev_ip[2], dev_ip[3]);
let sess = open_reuse(&bind_addr);
// wildcard(REUSEADDR) 收广播发现; 注意: 必须先启动本程序、后启动 SymphoLight
let disc = std::panic::catch_unwind(|| open_reuse("0.0.0.0:31772")).ok();
// AECH 中继 socket: 优先绑 113 (本机 123 网段时直接中继 .1); 非本网段则绑自身 IP (经桥中继)
let relay_sock: Option<UdpSocket> = relay.map(|_| {
std::panic::catch_unwind(|| open_reuse("192.168.123.113:0"))
.unwrap_or_else(|_| open_reuse(&format!("{}.{}.{}.{}:0", dev_ip[0], dev_ip[1], dev_ip[2], dev_ip[3])))
});
sess.set_read_timeout(Some(Duration::from_millis(2))).unwrap();
println!(" 虚拟 Butler @ {} (MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}), 运行 {}s",
bind_addr, dev_mac[0], dev_mac[1], dev_mac[2], dev_mac[3], dev_mac[4], dev_mac[5], secs);
let resp_t = load_template("discovery_resp");
let aerep_t = load_template("aerep");
let ack_t = load_template("ack");
let status_t = load_template("status"); // 84B "Butl" 设备状态遥测帧
let reg89_t = load_template("register89"); // 89B 主机注册应答 (sub=02)
let aere594_t = load_template("aere594"); // 594B AECH 认领应答 (sub=06, AERE)
let ann144_t = load_template("announce144"); // 144B 宣告 (sub=01, 中继唤醒用)
// RDM 应答模板: 5种DISC脉冲(101B) + 分支应答(111B) + 参数信息(119B)
let pulse_ts = vec![load_template("rdm_pulse_0"), load_template("rdm_pulse_1"),
load_template("rdm_pulse_2"), load_template("rdm_pulse_3"), load_template("rdm_pulse_4")];
let rdm111_t = load_template("rdm_resp111");
let rdm119_t = load_template("rdm_info119");
let setack68_t = load_template("rdm_setack68"); // RDM SET 应答模板 (CC=31, PDL=0, 68B, set123b 实证)
// 真机 .1 完整 RDM 应答帧池 (178 唯一帧: 56/59/70/77/87B 全部应答词表)
let rdm_pool = crate::enet::load_rdm_pool();
println!(" RDM 帧池: {} 帧", rdm_pool.len());
// PID→应答模板映射 (cmd=18=RDM GET, 查询 PID 在 0x50, 应答含 21<PID>)
let mut pid_map: std::collections::HashMap<[u8; 2], Vec<u8>> = std::collections::HashMap::new();
// SET 写路径状态: 主机 SET (CC=0x30) 的值按 (UID, PID) 落存, 后续 GET 同 PID 回显新值
// (真机行为: SET 立即生效, 主机 +33ms GET 验证 — set123b 实证, §5.6)
let mut set_store: std::collections::HashMap<([u8; 6], [u8; 2]), Vec<u8>> = std::collections::HashMap::new();
for line in include_str!("../samples/pid_map.txt").lines() {
let line = line.trim();
if line.len() < 8 { continue; }
let pid_hex = &line[..4];
let frame_hex = line[5..].trim();
let pb: Vec<u8> = (0..pid_hex.len()).step_by(2).map(|i| u8::from_str_radix(&pid_hex[i..i + 2], 16).unwrap()).collect();
let fb: Vec<u8> = (0..frame_hex.len()).step_by(2).map(|i| u8::from_str_radix(&frame_hex[i..i + 2], 16).unwrap()).collect();
if pb.len() == 2 { pid_map.insert([pb[0], pb[1]], fb); }
}
println!(" PID 映射: {} 个", pid_map.len());
let start = Instant::now();
let mut n_disc = 0u64; let mut n_aecha = 0u64; let mut n_dmx = 0u64;
let mut n_evt = 0u64; let mut n_ack = 0u64; let mut n_other = 0u64;
let mut n_status = 0u64; let mut status_seq = 0x62u32;
let mut n_rdm_pulse = 0u64; let mut n_rdm_111 = 0u64; let mut n_rdm_119 = 0u64;
let mut n_rdm_set = 0u64;
let mut n_announce = 0u64;
let mut pulse_idx = 0usize; let mut pulse_seq = 0x0f0bu32; let mut info_ctr = 0xc0u8;
let mut pulse_step: u32 = 2;
let mut pool_idx = 0usize;
let mut cur_pulse = 0usize; let mut branch_ctr = 0u64;
let mut cur_fixture = 0usize;
let mut rdm_req_dumped = 0u32;
let mut n_reg = 0u64; let mut n_claim = 0u64; let mut claim_nonce: u32 = 0x51f31ab9;
let mut dmx_b12: Option<u32> = None; // AECH 质询 0x8C-0x8F 实例随机 → DMX 解密 rot 基值
let mut n_rdm_relay = 0u64; let mut n_rdm_fwd = 0u64;
let mut pending_rdm_bridge = [0u8; 6];
// AECH 中继: (认领者地址, 主机IP LE, 主机MAC) — 质询转发真机后暂存
let mut pending_claim: Option<(std::net::SocketAddr, [u8;4], [u8;6])> = None;
let mut last_pulse = Instant::now() - Duration::from_secs(10);
let mut first_channels: Option<[u8; 32]> = None;
let mut last_channels: [u8; 32] = [0; 32];
let mut ch_changes = 0u64;
let mut last_ch_show = Instant::now() - Duration::from_secs(10);
let mut peer: Option<std::net::SocketAddr> = None;
// RDM 应答延迟队列 (真机灯具经 RS485 应答延迟 12-48ms, 中位 29ms — 瞬时应答是假指纹)
let mut rdm_queue: Vec<(Instant, Vec<u8>, std::net::SocketAddr)> = Vec::new();
let mut delay_lcg: u32 = 0x9e3779b9;
let mut next_delay = move |base_ms: u64, span_ms: u64| -> u64 {
delay_lcg = delay_lcg.wrapping_mul(2654435761).wrapping_add(0x9e3779b9);
base_ms + ((delay_lcg >> 16) as u64 % span_ms)
};
// 42B 遥测: 真机 ~每 115ms 上报一次"已收事件计数" (0x20-0x23 严格递增,
// 增量=事件B帧数, ss123 实证 +2959 计数 ≈ 2963 事件)
let mut event_ctr: u32 = 0;
let mut ack42_sent_val: u32 = 0;
let mut last_ack42 = Instant::now() - Duration::from_secs(1);
let mut evt_dst: Option<std::net::SocketAddr> = None;
while start.elapsed().as_secs() < secs {
// --- 真机无自发状态帧 (168s 仅 1 帧 42B, 已全部删除突发遥测) ---
// --- 到期的 RDM 延迟应答 ---
if !rdm_queue.is_empty() {
let now = Instant::now();
let mut i = 0;
while i < rdm_queue.len() {
if rdm_queue[i].0 <= now {
let (_, r, dst) = rdm_queue.remove(i);
let _ = sess.send_to(&r, dst);
} else { i += 1; }
}
}
// --- 42B 遥测: 真机 ~每 115ms 上报一次"已收事件计数" (ss123 实证) ---
if last_ack42.elapsed() > Duration::from_millis(115) && event_ctr != ack42_sent_val {
if let Some(dst) = evt_dst {
let mut a = ack_t.to_vec();
p32(&mut a, 0x20, event_ctr);
let _ = sess.send_to(&a, dst);
ack42_sent_val = event_ctr;
last_ack42 = Instant::now();
}
}
let mut buf = [0u8; 2048];
// --- wildcard socket: 收广播发现 (仅初始发现阶段用) ---
if let Some(ref d) = disc {
if let Ok((n, src)) = d.recv_from(&mut buf) {
let b = &buf[..n];
if b.len() == 180 && &b[0..4] == b"ENet" && g32(b, 4) == 1 {
// 真机处理延迟 ~160ms (指纹对齐)
std::thread::sleep(Duration::from_millis(150));
let mut r = resp_t.clone();
patch_dev(&mut r, dev_ip, dev_mac);
patch_259_host(&mut r, src, b);
let _ = sess.send_to(&r, src);
n_disc += 1;
println!(" [{}] 发现请求 type1 from {} len={}", chrono_t(start), src, b.len());
} else if b.len() >= 0x1A && &b[0..4] == b"ENet" && g32(b, 4) == 2 && g16(b, 0x18) == 1 {
// 广播主机宣告 sub=01 → 也回 89B 注册帧 (从 sess 口发出)
let mut r = reg89_t.clone();
let ip_le = [dev_ip[3], dev_ip[2], dev_ip[1], dev_ip[0]];
r[0x1A..0x1E].copy_from_slice(&ip_le);
r[0x1E..0x24].copy_from_slice(&dev_mac);
if b.len() > 0x24 {
r[0x25..0x29].copy_from_slice(&b[0x1A..0x1E]);
r[0x29..0x2F].copy_from_slice(&b[0x1E..0x24]);
}
let _ = sess.send_to(&r, src);
n_reg += 1;
}
}
}
// --- 中继回程轮询: 真机 AERE 回到 relay_sock (113:随机端口) ---
if let Some(rs) = relay_sock.as_ref() {
if let Ok((n, src)) = rs.recv_from(&mut buf) {
let b2 = &buf[..n];
if b2.len() >= 8 && &b2[0..4] == b"ENet" {
// --- RDM 应答回程: 真机灯具的 type=04 应答 (脉冲59B/分支69B/参数77B) → 发给主机 ---
if g32(b2, 4) == 4 && b2.len() >= 40 {
if let Some(p) = peer {
let mut r = b2.to_vec();
// cc01 帧内含 bridge-id (0x2D-0x32), 替换为查询的 bridge-id
if r.len() > 0x33 && r[0x2A] == 0xCC {
r[0x2D..0x33].copy_from_slice(&pending_rdm_bridge);
}
let _ = sess.send_to(&r, p);
n_rdm_fwd += 1;
if n_rdm_fwd <= 4 { println!(" [{}] RDM 应答回程 → 主机 ({}B)", chrono_t(start), r.len()); }
}
}
if let (Some((pc, hip, hmac)), Some(_)) = (pending_claim, relay) {
if b2.len() >= 552 && g32(b2, 4) == 2 && g16(b2, 0x18) == 6 { let mut r = b2.to_vec();
let ip_le = [dev_ip[3], dev_ip[2], dev_ip[1], dev_ip[0]];
r[0x1A..0x1E].copy_from_slice(&ip_le);
r[0x41..0x45].copy_from_slice(&ip_le);
r[0x1E..0x24].copy_from_slice(&dev_mac);
r[0x49..0x4F].copy_from_slice(&dev_mac);
r[0x25..0x28].copy_from_slice(&hip[0..3]);
r[0x2C..0x2F].copy_from_slice(&hmac[3..6]);
let _ = sess.send_to(&r, pc);
pending_claim = None;
n_claim += 1;
println!(" [{}] ★★ 真机 AERE 中继成功 → {} #{}", chrono_t(start), pc, n_claim);
}
}
}
}
}
// --- 会话 socket: 单播发现 / AECHA / DMX / 事件 ---
let (n, src) = match sess.recv_from(&mut buf) {
Ok(x) => x,
Err(_) => continue,
};
let b = &buf[..n];
if b.len() < 8 || &b[0..4] != b"ENet" { continue; }
peer = Some(src); // 记录对端(SymphoLight)用于遥测上行
let t = g32(b, 4);
if t == 1 && b.len() == 180 {
let mut r = resp_t.clone();
patch_dev(&mut r, dev_ip, dev_mac);
patch_259_host(&mut r, src, b);
let _ = sess.send_to(&r, src);
n_disc += 1;
println!(" [{}] 发现请求 type1 from {} len={}", chrono_t(start), src, b.len());
} else if t == 2 {
let sub = if b.len() >= 0x1A { g16(b, 0x18) } else { 0 };
if sub == 5 && find(b, b"AECH") {
if let (Some((rip, rport)), Some(rs)) = (relay, relay_sock.as_ref()) {
// --- 中继模式: 先向中继目标发 sub=01 宣告唤醒注册态, 再转发质询 ---
let hip = [b[0x1A], b[0x1B], b[0x1C], b[0x1D]];
let hmac = [b[0x1E], b[0x1F], b[0x20], b[0x21], b[0x22], b[0x23]];
pending_claim = Some((src, hip, hmac));
let raddr = format!("{}.{}.{}.{}:{}", rip[0], rip[1], rip[2], rip[3], rport);
// 宣告帧: 0x1A-1D 主机IP(LE), 0x1E-23 主机MAC
let mut ann = ann144_t.clone();
ann[0x1A..0x1E].copy_from_slice(&hip);
ann[0x1E..0x24].copy_from_slice(&hmac);
let _ = rs.send_to(&ann, &raddr);
std::thread::sleep(Duration::from_millis(30));
let _ = rs.send_to(b, &raddr);
println!(" [{}] AECH 质询中继 → 真机 {} (先宣告唤醒)", chrono_t(start), raddr);
} else {
// --- AECH 认领质询 (594B sub=05) → AERE 应答 (sub=06) ---
// 质询帧内: 0x1A-1D 主机IP(LE), 0x1E-23 主机MAC, 0x28 "AECH"
let mut r = aere594_t.clone();
// 设备 IP (LE) 0x1A 与 0x41; 设备 MAC 0x1E 与 0x49
let ip_le = [dev_ip[3], dev_ip[2], dev_ip[1], dev_ip[0]];
r[0x1A..0x1E].copy_from_slice(&ip_le);
r[0x41..0x45].copy_from_slice(&ip_le);
r[0x1E..0x24].copy_from_slice(&dev_mac);
r[0x49..0x4F].copy_from_slice(&dev_mac);
// AERE 0x25-27 = 主机IP前3字节(LE), 0x2C-2E = 主机MAC后3字节
if b.len() > 0x24 {
r[0x25..0x28].copy_from_slice(&b[0x1A..0x1D]);
r[0x2C..0x2F].copy_from_slice(&b[0x21..0x24]);
}
// 0xC0-0xC3 = AgiosElias G(nonce@0x88-0x8B LE) — 算法已从 SymphoLight
// Ecue.Devices.dll 反编译还原 (无密钥! G(x)=x^TABLE[(x%32+1+(x*13)%30)%32]),
// 主机按同式验证 AERE blob[152]。无需真机中继。
let nonce = if b.len() > 0x8C { g32(b, 0x88) } else { 0 };
p32(&mut r, 0xC0, crate::aech::agios_g(nonce));
// 0x8C-0x8F = AgiosElias 实例随机 B12 — DMX 帧混淆 rot=(B12^r)&511 的会话基值
if b.len() > 0x90 { dmx_b12 = Some(g32(b, 0x8C)); }
let _ = sess.send_to(&r, src);
n_claim += 1;
println!(" [{}] ★ AECH 认领质询 → 本地计算 AERE (G({:08x})={:08x}) #{}",
chrono_t(start), nonce, crate::aech::agios_g(nonce), n_claim);
}
} else if sub == 1 {
// --- 主机身份宣告 sub=01 (102B) → 89B 注册应答 sub=02 ---
// 宣告内: 0x1A-1D 主机IP(LE), 0x1E-23 主机MAC
let mut r = reg89_t.clone();
let ip_le = [dev_ip[3], dev_ip[2], dev_ip[1], dev_ip[0]];
r[0x1A..0x1E].copy_from_slice(&ip_le);
r[0x1E..0x24].copy_from_slice(&dev_mac);
if b.len() > 0x24 {
r[0x25..0x29].copy_from_slice(&b[0x1A..0x1E]); // 主机IP(LE)
r[0x29..0x2F].copy_from_slice(&b[0x1E..0x24]); // 主机MAC
}
let _ = sess.send_to(&r, src);
n_reg += 1;
if n_reg <= 3 { println!(" [{}] 主机宣告 sub=01 → 回 89B 注册帧", chrono_t(start)); }
} else {
// --- 已认领心跳 sub=03 (36B, 每~6s): 真机从不应答 (cold2 实证), 静默 ---
n_status += 1; // 仅计数
}
} else if t == 4 && b.len() == 1084 {
if g16(b, 0x20) != 0x0101 {
event_ctr = event_ctr.wrapping_add(1); // 事件B帧计数 (含 RDM 查询, 42B 遥测用)
evt_dst = Some(src);
}
if g16(b, 0x20) == 0x0101 {
n_dmx += 1;
// DMX 数据经 zPprv0TXRRW 逐帧混淆 (§4.1): r=0x22-0x25 LE, rot=(B12^r)&511
let ch16 = match dmx_b12 {
Some(b12) => {
let r1 = g32(b, 0x22);
let plain = crate::aech::agios_dmx_decrypt(&b[0x3C..0x3C + 512], r1, b12);
hexs(&plain[..16])
}
None => hexs(&b[0x3C..0x3C + 16]), // 未认领前: 原始线值
};
let mut ch = [0u8; 32];
ch.copy_from_slice(&b[0x3C..0x3C + 32]);
if first_channels.is_none() { first_channels = Some(ch); }
if ch != last_channels { ch_changes += 1; last_channels = ch; }
if last_ch_show.elapsed() > Duration::from_secs(5) {
last_ch_show = Instant::now();
println!(" [{}] DMX #{} CH1-16: {}", chrono_t(start), n_dmx, ch16);
}
} else if b.len() > 0x44 && b[0x3C] == 0x01 && (b[0x3D] == 0x24 || b[0x3D] == 0x18 || b[0x3D] == 0x19 || b[0x3D] == 0x1a) {
// --- RDM 请求 (0x20=0200<seq16>, 0x3C=01, 0x3D=cmd, 0x3E+=目标UID) ---
let mut uid = [0u8; 6];
uid.copy_from_slice(&b[0x3E..0x44]);
// RDM 一律本地生成式应答 (全新UID灯具); relay 仅用于 AECH 认领
if rdm_req_dumped < 3 {
rdm_req_dumped += 1;
let dump_len = std::cmp::min(0x80, b.len());
println!(" [RDM-REQ#{}] len={} hex[0..{}]={}", rdm_req_dumped, b.len(), dump_len, hexs(&b[..dump_len]));
}
if b[0x3D] == 0x19 && rdm_req_dumped < 8 {
rdm_req_dumped += 1;
let dump_len = std::cmp::min(0x90, b.len());
println!(" [RDM-19#{}] len={} hex[0..{}]={}", rdm_req_dumped, b.len(), dump_len, hexs(&b[..dump_len]));
}
if b[0x30] != 0x00 {
// flag=01 = DMX 内嵌副本帧: 真机完全不答, 忽略
} else if b[0x3D] == 0x24 {
// --- DISC_UNIQUE_BRANCH: 分支范围 lo@0x53-0x58 hi@0x59-0x5E (PID 0001@0x50, PDL=12@0x52) ---
// 真机灯具只应答 UID 落在分支范围内的查询 (ss123 实证二分收敛);
// 此前无视范围全部应答 → 主机二分水远无法排空分支 → 51/s 发现洪水, 稳态轮询不启动
if b.len() > 0x5E {
let lo = &b[0x53..0x59]; let hi = &b[0x59..0x5F];
for fx in FIXTURES.iter() {
if &fx[..] >= lo && &fx[..] <= hi {
let pulse = rdm_encode_uid(fx);
let mut r: Vec<u8> = Vec::with_capacity(59);
r.extend_from_slice(&pulse_ts[0][..0x2A]); // 模板头
// 脉冲 seq = 设备本地自增序号 (真机行为, 非回显!)
p32(&mut r, 0x20, pulse_seq);
pulse_seq = pulse_seq.wrapping_add(pulse_step);
pulse_step = 1 + (pulse_step * 7 + 0x1d) % 3; // 每帧 +1..3
r.extend_from_slice(&pulse);
let due = Instant::now() + Duration::from_millis(next_delay(2, 40));
rdm_queue.push((due, r, src));
n_rdm_pulse += 1;
}
}
if n_rdm_pulse <= 5 || n_rdm_pulse % 100 == 0 {
println!(" [RDM] 分支发现范围内应答 #{}", n_rdm_pulse);
}
}
} else if b[0x3D] == 0x18 && uid == [0xff; 6] {
// 广播 UID 的发现命令 (广播 MUTE 0003 等): 真机不应答, 静默
} else if b[0x3D] == 0x18 {
// cmd=18 = RDM GET. PID 在 0x50-0x51.
// PID=0002 = DISC_UN_MUTE → 70B ACK (CC=11, PDL=2, PD=0000) — 冷启动抓包实证
let pid = if b.len() > 0x51 { [b[0x50], b[0x51]] } else { [0, 0] };
let mirror = mirror_uid(&uid);
// 优先帧池中镜像灯具的同 PID 应答 (GET_RESP=0x21, 数据自洽); 其次 pid_map; 兜底 111
let pool_hit = rdm_pool.iter().find(|f|
f.len() >= 0x42 && f[0x2A] == 0xCC && f[0x33..0x39] == mirror
&& f[0x3E] == 0x21 && f[0x3F] == pid[0] && f[0x40] == pid[1]);
let mut r = if let Some(t) = pool_hit {
t.clone()
} else if let Some(t) = pid_map.get(&pid) {
t.clone()
} else {
rdm111_t.clone()
};
if r.len() > 0x39 { r[0x33..0x39].copy_from_slice(&uid); } // src UID = 虚拟灯具
// dest UID (0x2D-0x32) = 查询方 bridge-id; seq 回显; TN 回显查询 TN(0x4A)
if b.len() > 0x4A && r.len() > 0x33 { r[0x2D..0x33].copy_from_slice(&b[0x44..0x4A]); }
r[0x20] = b[0x22]; r[0x21] = b[0x23];
if b.len() > 0x4B && r.len() > 0x3A { r[0x39] = b[0x4A]; }
// SET 过的 PID 回显新值 (真机: SET 立即生效, §5.6)
if let Some(v) = set_store.get(&(uid, pid)) {
let pdl = if r.len() > 0x41 { r[0x41] as usize } else { 0 };
if pdl == v.len() && r.len() >= 0x42 + pdl {
r[0x42..0x42 + pdl].copy_from_slice(v);
}
}
rdm_checksum(&mut r); // patch 后必须重算 (真机 1396/1396 有效)
let due = Instant::now() + Duration::from_millis(next_delay(15, 25));
n_rdm_111 += 1;
if n_rdm_111 <= 6 || n_rdm_111 % 100 == 0 {
println!(" [RDM] GET UID={:02x}{:02x}{:02x}{:02x}{:02x}{:02x} PID={:02x}{:02x} → {}B",
uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], pid[0], pid[1], r.len());
}
rdm_queue.push((due, r, src));
} else if b[0x4F] == 0x30 {
// --- RDM SET (CC=0x30, msgLen=0x1a+): 真机回 CC=0x31 ACK, RT=00, PDL=0,
// 68B, TN/UID 回显, checksum 有效, 延迟 18-25ms (set123b 实证, §5.6) ---
let mut r = setack68_t.clone();
if r.len() > 0x39 { r[0x33..0x39].copy_from_slice(&uid); } // src UID = 虚拟灯具
if b.len() > 0x4A && r.len() > 0x33 { r[0x2D..0x33].copy_from_slice(&b[0x44..0x4A]); } // dest = 查询方
r[0x20] = b[0x22]; r[0x21] = b[0x23]; // seq 回显
if b.len() > 0x4B && r.len() > 0x3A { r[0x39] = b[0x4A]; } // TN 回显
if b.len() > 0x51 && r.len() > 0x41 { r[0x3F] = b[0x50]; r[0x40] = b[0x51]; } // PID 回显
rdm_checksum(&mut r); // patch 后必须重算
let due = Instant::now() + Duration::from_millis(next_delay(15, 25));
let pid = if b.len() > 0x51 { [b[0x50], b[0x51]] } else { [0, 0] };
// 应用写入值 (PDL@0x52, PD@0x53): 后续 GET 同 PID 回显新值 (§5.6 主机验证模式)
let pdl = if b.len() > 0x52 { b[0x52] as usize } else { 0 };
if pdl > 0 && b.len() >= 0x53 + pdl {
set_store.insert((uid, pid), b[0x53..0x53 + pdl].to_vec());
}
rdm_queue.push((due, r, src));
n_rdm_set += 1;
if n_rdm_set <= 6 || n_rdm_set % 100 == 0 {
println!(" [RDM] SET UID={:02x}{:02x}{:02x}{:02x}{:02x}{:02x} PID={:02x}{:02x} PD={} → 68B ACK #{}",
uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], pid[0], pid[1],
hexs(set_store.get(&(uid, pid)).map(|v| &v[..]).unwrap_or(&[])), n_rdm_set);
}
} else {
// cmd=19/1a (SET类/传感器轮询 SENSOR_VALUE 0201): PID@0x50, PD@0x53
// 按镜像灯具 + PID + PD[0](传感器序号) 从帧池取自恰应答
let pid = if b.len() > 0x51 { [b[0x50], b[0x51]] } else { [0, 0] };
let pd0 = if b.len() > 0x53 { b[0x53] } else { 0 };
let mirror = mirror_uid(&uid);
let pool_hit = rdm_pool.iter().find(|f|
f.len() >= 0x43 && f[0x2A] == 0xCC && f[0x33..0x39] == mirror
&& f[0x3F] == pid[0] && f[0x40] == pid[1] && f[0x42] == pd0);
let mut r = if let Some(t) = pool_hit {
t.clone()
} else {
rdm119_t.clone()
};
if r.len() > 0x39 { r[0x33..0x39].copy_from_slice(&uid); }
if b.len() > 0x4A && r.len() > 0x33 { r[0x2D..0x33].copy_from_slice(&b[0x44..0x4A]); }
r[0x20] = b[0x22]; r[0x21] = b[0x23];
if b.len() > 0x4B && r.len() > 0x3A { r[0x39] = b[0x4A]; }
rdm_checksum(&mut r);
let due = Instant::now() + Duration::from_millis(next_delay(15, 25));
let rlen = r.len();
rdm_queue.push((due, r, src));
n_rdm_119 += 1;
if n_rdm_119 <= 6 || n_rdm_119 % 100 == 0 {
println!(" [RDM] 参数/传感器 UID={:02x}{:02x}{:02x}{:02x}{:02x}{:02x} PID={:02x}{:02x} PD0={:02x} → {}B #{}",
uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], pid[0], pid[1], pd0, rlen, n_rdm_119);
}
}
} else {
// 非 RDM 事件B帧: 真机只计数, 由 42B 周期遥测上报, 无即时 ACK (ss123 实证)
n_evt += 1;
}
} else {
n_other += 1;
if n_other <= 12 {
println!(" [?] 其他帧 src={} len={} type={} hex[0..48]={}", src, b.len(), t, hexs(&b[..std::cmp::min(48, b.len())]));
}
}
}
items.push(Item::new("U1", n_disc > 0, format!("发现应答={} 遥测={} RDM脉冲={} RDM111={} RDM119={}", n_disc, n_status, n_rdm_pulse, n_rdm_111, n_rdm_119)));
items.push(Item::new("U2", n_aecha > 0, format!("AECHA 认领交换={} 轮 (AEREP 已回)", n_aecha)));
items.push(Item::new("U3", n_dmx > 0, format!("明文 DMX 帧接收={}", n_dmx)));
items.push(Item::new("U4", n_ack > 0, format!("事件帧={} ACK发送={}", n_evt, n_ack)));
items.push(Item::new("U5", n_dmx > secs * 10, format!(
"通道值: 首32CH={:?} 变化={} 其他帧={}", first_channels.unwrap_or([0; 32]), ch_changes, n_other)));
items
}
// ---------- RDM 广播发现重放探测: 向目标重放真机 RDM 广播请求, 抓取并分类应答 ----------
pub fn rdm_probe(target: &str, secs: u64) -> Item {
let req_t = load_template("rdm_disc_req");
let bind = "0.0.0.0:0";
let s = UdpSocket::bind(bind).expect("rdmprobe bind 失败");
s.set_broadcast(true).unwrap();
s.set_read_timeout(Some(Duration::from_millis(120))).unwrap();
let dst = format!("{}:31772", target);
println!(" rdmprobe → {} 运行 {}s", dst, secs);
let start = Instant::now();
let mut seq: u32 = 0x090f;
let mut sent = 0u64;
let mut resp_lens: std::collections::HashMap<usize, u64> = std::collections::HashMap::new();
let mut first_resp: Option<Vec<u8>> = None;
let mut delays: Vec<u32> = Vec::new();
let mut last_send = Instant::now() - Duration::from_secs(10);
let mut pending: Option<(Instant, u32)> = None;
while start.elapsed().as_secs() < secs {
// 每 500ms 发一次请求
if last_send.elapsed() > Duration::from_millis(500) {
let mut r = req_t.clone();
p32(&mut r, 0x20, seq);
let _ = s.send_to(&r, &dst);
pending = Some((Instant::now(), seq));
seq = seq.wrapping_add(1);
sent += 1;
last_send = Instant::now();
}
let mut buf = [0u8; 2048];
match s.recv_from(&mut buf) {
Ok((n, src)) => {
let b = &buf[..n].to_vec();
*resp_lens.entry(n).or_insert(0) += 1;
if first_resp.is_none() { first_resp = Some(b.clone()); }
if let Some((t0, sq)) = pending {
if n >= 0x22 && g32(b, 4) == 4 {
let echo = g16(b, 0x20);
let expect = ((sq >> 16) as u16) ^ 0; // seq低16位在0x22
let _ = expect;
delays.push((t0.elapsed().as_millis() as u32));
println!(" [{}] 应答 src={} len={} seqEcho=0x{:04x} (请求seq低16=0x{:04x}) 延迟={}ms",
chrono_t(start), src, n, echo, (sq & 0xffff) as u16, t0.elapsed().as_millis());
} else {
println!(" [{}] 应答 src={} len={} type={}", chrono_t(start), src, n, if n>=8 { g32(b,4) } else { 0 });
}
pending = None;
}
}
Err(_) => {}
}
}
let mut lens_desc = String::new();
let mut keys: Vec<_> = resp_lens.keys().cloned().collect();
keys.sort();
for k in keys { lens_desc.push_str(&format!(" {}B×{}", k, resp_lens[&k])); }
let avg_delay = if delays.is_empty() { 0 } else { delays.iter().sum::<u32>() / delays.len() as u32 };
Item::new("RDMP", sent > 0 && !resp_lens.is_empty(),
format!("请求={} 应答[{}] 平均延迟={}ms", sent, lens_desc.trim(), avg_delay))
}
// ---------- RDM DISC 脉冲编码 (E1.20) ----------
/// 虚拟灯具: 全新 UID (同厂 0107 系列, 主机未注册过的序列号)
const FIXTURES: [[u8; 6]; 3] = [
[0x01, 0x07, 0x34, 0x67, 0x01, 0x30],
[0x01, 0x07, 0x34, 0x67, 0x01, 0x31],
[0x01, 0x07, 0x34, 0x67, 0x01, 0x32],
];
/// 镜像映射: 虚拟 UID → 真机灯具 UID (参数数据按镜像灯具从 rdm_pool 取,
/// 使 3 台虚拟灯具拥有各自自洽的 DMX 地址/标签/传感器数, 与 .1 总线同构)
const FIXTURE_MIRROR: [[u8; 6]; 3] = [
[0x01, 0x07, 0x34, 0x67, 0x01, 0x25], // NFS1K2DxxxBx DMX101-102 23传感器
[0x01, 0x07, 0x34, 0x88, 0x09, 0x00], // NFM680SxxxGx DMX111 17传感器
[0x08, 0x99, 0x50, 0x00, 0x26, 0xc9], // HMKJ DMX1-32 4传感器
];
fn mirror_uid(uid: &[u8; 6]) -> [u8; 6] {
for (i, f) in FIXTURES.iter().enumerate() {
if f == uid { return FIXTURE_MIRROR[i]; }
}
*uid
}
// E1.20 checksum: 从 CC(0x2A) 起 msgLen(0x2C) 字节的 16-bit 和, BE 写于 [0x2A+msgLen]
// (冷启动抓包验证: 真机 1396/1396 全部满足此规则)
fn rdm_checksum(r: &mut [u8]) {
if r.len() < 0x2E || r[0x2A] != 0xCC { return; }
let msg_len = r[0x2C] as usize;
if r.len() < 0x2A + msg_len + 2 { return; }
let mut sum: u16 = 0;
for i in 0x2A..0x2A + msg_len { sum = sum.wrapping_add(r[i] as u16); }
r[0x2A + msg_len] = (sum >> 8) as u8;
r[0x2A + msg_len + 1] = (sum & 0xFF) as u8;
}
fn rdm_encode_uid(uid: &[u8; 6]) -> [u8; 17] {
let mut out = [0u8; 17];
out[0] = 0xAA; // 分隔符
let mut sum: u16 = 0;
for (i, &b) in uid.iter().enumerate() {
let b1 = 0xAA | (b & 0x55);
let b2 = 0x55 | (b & 0xAA);
out[1 + 2 * i] = b1;
out[2 + 2 * i] = b2;
sum += b1 as u16 + b2 as u16;
}
let hi = (sum >> 8) as u8;
let lo = (sum & 0xFF) as u8;
out[13] = 0xAA | (hi & 0x55);
out[14] = 0x55 | (hi & 0xAA);
out[15] = 0xAA | (lo & 0x55);
out[16] = 0x55 | (lo & 0xAA);
out
}
// ---------- 259 应答的 Connected To 动态化: 从发现请求提取请求方主机 IP+MAC ----------
fn patch_259_host(r: &mut [u8], src: std::net::SocketAddr, req: &[u8]) {
if let std::net::IpAddr::V4(v4) = src.ip() {
let o = v4.octets();
let ip_le = [o[3], o[2], o[1], o[0]];
// host IP (0x4F-0x52) = 请求方 IP (LE)
if r.len() > 0x53 { r[0x4F..0x53].copy_from_slice(&ip_le); }
// host MAC (0x53-0x58): 请求里 hostIP(LE) 后 +8 处 (IP4 + 00ffffff掩码 + MAC6)
for i in 0..req.len().saturating_sub(14) {
if req[i..i + 4] == ip_le && req[i + 4..i + 8] == [0x00, 0xff, 0xff, 0xff] {
if r.len() > 0x59 { r[0x53..0x59].copy_from_slice(&req[i + 8..i + 14]); }
break;
}
}
}
}
// ---------- bridge: AECH 双向桥 (非123网段设备的认领桥) ----------
// 监听 listen_ip:listen_port 收远端设备的 AECH, 以源 IP=123.113 转发给真机, 再把真机 AERE 回传给远端设备
pub fn bridge(listen_ip: [u8; 4], listen_port: u16, real_ip: [u8; 4], secs: u64) {
let listen_addr = format!("{}.{}.{}.{}:{}", listen_ip[0], listen_ip[1], listen_ip[2], listen_ip[3], listen_port);
let listen = open_reuse(&listen_addr);
let to_real = open_reuse("192.168.123.113:0"); // 源=113, 真机才应答
let real_addr = format!("{}.{}.{}.{}:31772", real_ip[0], real_ip[1], real_ip[2], real_ip[3]);
println!(" AECH 桥 @ {} ↔ {} (真机源 123.113)", listen_addr, real_addr);
let mut pending: Option<std::net::SocketAddr> = None;
let start = Instant::now();
let mut n_fwd = 0u64; let mut n_back = 0u64;
while start.elapsed().as_secs() < secs {
let mut buf = [0u8; 2048];
// 远端设备 -> 真机 (AECH)
if let Ok((n, src)) = listen.recv_from(&mut buf) {
let b = &buf[..n];
if b.len() >= 8 && &b[0..4] == b"ENet" && g32(b, 4) == 2 && find(b, b"AECH") {
pending = Some(src);
let _ = to_real.send_to(b, &real_addr);
n_fwd += 1;
println!(" [{}] 桥: {} → 真机 (AECH #{})", chrono_t(start), src, n_fwd);
}
}
// 真机 -> 远端设备 (AERE)
if let Ok((n, src)) = to_real.recv_from(&mut buf) {
let b = &buf[..n];
if b.len() >= 8 && &b[0..4] == b"ENet" && g32(b, 4) == 2 {
if let Some(p) = pending {
let _ = listen.send_to(b, p);
n_back += 1;
println!(" [{}] 桥: 真机 → {} (AERE #{})", chrono_t(start), p, n_back);
}
}
}
}
}
fn chrono_t(start: Instant) -> String { format!("{:.1}s", start.elapsed().as_secs_f64()) }
fn patch_dev(b: &mut [u8], ip: [u8; 4], mac: [u8; 6]) {
// 找 Butler IP LE (01 7b a8 c0) 替换 — 字段为小端存储: 192.168.123.200 → c8 7b a8 c0
for i in 0..b.len().saturating_sub(4) {
if b[i] == 0x01 && b[i + 1] == 0x7b && b[i + 2] == 0xa8 && b[i + 3] == 0xc0 {
b[i] = ip[3]; b[i + 1] = ip[2]; b[i + 2] = ip[1]; b[i + 3] = ip[0];
break;
}
}
// 找 Butler MAC 替换
for i in 0..b.len().saturating_sub(6) {
if b[i] == 0x00 && b[i + 1] == 0x16 && b[i + 2] == 0x1c && b[i + 3] == 0xf1 && b[i + 4] == 0x2e && b[i + 5] == 0x39 {
b[i..i + 6].copy_from_slice(&mac);
break;
}
}
}
// ---------- T10/T11: RDM 探索 ----------
pub fn t10_t11_rdm(secs: u64) -> Vec<Item> {
let mut items = Vec::new();
let s = open();
let req = load_template("rdm_req");
// T10: 发送 RDM 请求事件帧模板 (DISC 帧), 解析 Butler RDM 应答
println!(" 发送 RDM 请求模板帧并监听 {}s…", secs);
let deadline = Instant::now() + Duration::from_secs(secs);
let mut uids: HashSet<String> = HashSet::new();
let mut rdm_n = 0u64;
let mut disc_n = 0u64;
let mut req_seq = g32(&req, 0x22);
while Instant::now() < deadline {
let mut r = req.clone();
p32(&mut r, 0x22, req_seq);
req_seq = req_seq.wrapping_add(1);
let _ = s.send_to(&r, BUTLER);
for (_, b) in recv_window(&s, Duration::from_millis(60)) {
match classify(&b) {
Rx::RdmResp(_, rlen) => {
rdm_n += 1;
if rlen >= 17 && b[0x2A] == 0xAA { disc_n += 1; } // 发现脉冲
if rlen >= 18 && b[0x2A] == 0xCC {
let uid = hexs(&b[0x32..0x38]);
uids.insert(uid);
}
}
_ => {}
}
}
}
items.push(Item::new("T10", rdm_n > 0,
format!("RDM应答={} (发现脉冲={}, 设备UID={})", rdm_n, disc_n,
uids.iter().cloned().collect::<Vec<_>>().join(" "))));
// T11: 写地址 (PID 0x0330) —— e:cue 私有封装下 PID 未知, 仅记录探索结论
items.push(Item::new("T11", false,
"写地址 SET DMX_START_ADDRESS: e:net 私有 PID 未确认, 需结合 GUI 写地址抓包比对 (见文档 §5.4)".to_string()));
items
}

10
validation_report.json Normal file
View File

@ -0,0 +1,10 @@
{
"target": "192.168.123.1",
"results": [
{ "id": "U1", "pass": true, "detail": "发现应答=2426 遥测=21662 RDM脉冲=208904 RDM111=278460 RDM119=0" },
{ "id": "U2", "pass": false, "detail": "AECHA 认领交换=0 轮 (AEREP 已回)" },
{ "id": "U3", "pass": true, "detail": "明文 DMX 帧接收=206609" },
{ "id": "U4", "pass": false, "detail": "事件帧=0 ACK发送=0" },
{ "id": "U5", "pass": true, "detail": "通道值: 首32CH=[175, 174, 173, 172, 171, 170, 169, 168, 167, 166, 165, 164, 163, 162, 161, 160, 159, 158, 157, 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, 146, 145, 144] 变化=205895 其他帧=0" }
]
}