# Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" # ---- R1-R3: RDM 帧 (rdm3.pcap) ---- Write-Host "`n=== 载入 RDM 抓包 ===" $rdmPcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\rdm3.pcap" if (Test-Path $rdmPcap) { $pkts3 = Read-Pcap $rdmPcap $msgs3 = @(); foreach ($p in $pkts3) { $u = UdpPayload $p; if ($u) { $msgs3 += $u } } $rdm = @($msgs3 | Where-Object { # Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Src -eq '192.168.123.1' -and (LE32 # Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Payload 4) -eq 4 -and (Hex # Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Payload 0x1A 4) -eq '4275746c' -and (Hex # Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Payload 0x2A 2) -eq 'cc01' }) Check "R1 RDM帧=Butler→主机+cc01" ($rdm.Count -gt 0) ("| n={0} 长度分布={1}" -f $rdm.Count, (($rdm | Group-Object { # Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Payload.Length } | ForEach-Object { "$(# Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Name)x$(# Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Count)" }) -join ',')) $badRdmLen = @($rdm | Where-Object { [int](LE16 # Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Payload 0x28) -ne (# Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Payload.Length - 0x2A) }) Check "R2 RDM长度字段0x28" ($badRdmLen.Count -eq 0) ("| {0}/{1} 一致" -f ($rdm.Count - $badRdmLen.Count), $rdm.Count) $uids = @($rdm | ForEach-Object { (Hex # Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Payload 0x32 6) } | Sort-Object -Unique) Check "R3 灯具UID(Src@0x32)" ($uids.Count -ge 1) ("| UID: {0}" -f ($uids -join ' ')) $disc = @($msgs3 | Where-Object { # Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Src -eq '192.168.123.1' -and (Hex # Verify-EnetProtocol.ps1 — 按 enet_protocol.md 的断言逐条验证 pcap (v2) param( [string]$Pcap = "C:\Users\tonycao\AppData\Local\Temp\opencode\dmx40s_classic.pcap", [string]$PcapDiscovery = "C:\Users\tonycao\AppData\Local\Temp\opencode\discovery_classic.pcap" ) $ErrorActionPreference = 'Stop' $script:pass = 0; $script:fail = 0 function Check($name, $cond, $detail) { if ($cond) { $script:pass++; Write-Host (" [PASS] {0} {1}" -f $name, $detail) } else { $script:fail++; Write-Host (" [FAIL] {0} {1}" -f $name, $detail) } } function Read-Pcap($path) { $b = [IO.File]::ReadAllBytes($path) function U32([byte[]]$x, [int]$o, [bool]$sw) { if ($sw) { return ([uint32]$x[$o] -shl 24) -bor ([uint32]$x[$o+1] -shl 16) -bor ([uint32]$x[$o+2] -shl 8) -bor [uint32]$x[$o+3] } return [BitConverter]::ToUInt32($x, $o) } $magic = U32 $b 0 $false $swap = ($magic -eq 0xa1b2c3d4) $pkts = New-Object System.Collections.Generic.List[object] $o = 24 while ($o + 16 -le $b.Length) { $incl = U32 $b ($o+8) $swap if ($o + 16 + $incl -gt $b.Length) { break } $data = New-Object byte[] $incl [Array]::Copy($b, $o+16, $data, 0, $incl) $pkts.Add([pscustomobject]@{ Len = $incl; Data = $data }) $o += 16 + $incl } return $pkts } function UdpPayload($pkt) { $d = $pkt.Data if ($d.Length -lt 42) { return $null } $ethType = ([uint32]$d[12] -shl 8) -bor $d[13] $off = 14 if ($ethType -eq 0x8100) { $ethType = ([uint32]$d[16] -shl 8) -bor $d[17]; $off = 18 } if ($ethType -ne 0x0800) { return $null } $ihl = ($d[$off] -band 0x0F) * 4 if ($d[$off + 9] -ne 17) { return $null } $ipStart = $off; $udp = $off + $ihl $src = "{0}.{1}.{2}.{3}" -f $d[$ipStart+12], $d[$ipStart+13], $d[$ipStart+14], $d[$ipStart+15] $dst = "{0}.{1}.{2}.{3}" -f $d[$ipStart+16], $d[$ipStart+17], $d[$ipStart+18], $d[$ipStart+19] $sport = ([uint32]$d[$udp] -shl 8) -bor $d[$udp+1] $dport = ([uint32]$d[$udp+2] -shl 8) -bor $d[$udp+3] $pl = New-Object byte[] ($d.Length - $udp - 8) [Array]::Copy($d, $udp + 8, $pl, 0, $pl.Length) return [pscustomobject]@{ Src=$src; Dst=$dst; Sport=$sport; Dport=$dport; Payload=$pl } } function LE32([byte[]]$x, [int]$o) { return [uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8) -bor ([uint32]$x[$o+2] -shl 16) -bor ([uint32]$x[$o+3] -shl 24) } function LE16([byte[]]$x, [int]$o) { return [uint32]([uint32]$x[$o] -bor ([uint32]$x[$o+1] -shl 8)) } function Hex([byte[]]$x, [int]$o, [int]$n) { ($x[$o..($o+$n-1)] | ForEach-Object { $_.ToString('x2') }) -join '' } Write-Host "=== 载入 $Pcap ===" $pkts = Read-Pcap $Pcap Write-Host ("总包数: {0}" -f $pkts.Count) $msgs = @(); foreach ($p in $pkts) { $u = UdpPayload $p; if ($u) { $msgs += $u } } # ---- A1: 31772 帧魔数只允许 ENet / EUCE ---- $m31772 = @($msgs | Where-Object { $_.Sport -eq 31772 -or $_.Dport -eq 31772 }) $magic = $m31772 | Group-Object { Hex $_.Payload 0 4 } $magicStr = ($magic | ForEach-Object { "{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A1 魔数仅ENet/EUCE" (($magic | Where-Object { $_.Name -notin @('454e6574','45554345') }).Count -eq 0) "| $magicStr" # ---- A2: ENet 帧 0x14 长度字段 == 负载长 ---- $enet = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) $badLen = @($enet | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "A2 ENet长度字段" ($badLen.Count -eq 0) ("| {0}/{1} 帧一致" -f ($enet.Count - $badLen.Count), $enet.Count) # ---- A3: ENet 帧类型仅 1/2/4 ---- $types = $enet | Group-Object { LE32 $_.Payload 4 } $typeStr = ($types | ForEach-Object { "type{0}=x{1}" -f $_.Name, $_.Count }) -join ' ' Check "A3 类型仅01/02/04" (($types | Where-Object { $_.Name -notin @('1','2','4') }).Count -eq 0) "| $typeStr" # ---- A4: 形态A DMX 帧 ---- $a = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 0x0101 }) Check "A4a 负载1084B" (($a | Where-Object { $_.Payload.Length -ne 1084 }).Count -eq 0) ("| 形态A帧={0}" -f $a.Count) Check "A4b 数据区1024B" ((1084 - 0x3C) -eq 1024) "| 1084-60=1024" $seqs = @($a | ForEach-Object { [int](LE16 $_.Payload 0x1A) }) $mono = $true; for ($i=1; $i -lt $seqs.Count; $i++) { if ($seqs[$i] -ne (($seqs[$i-1]+1) -band 0xFFFF)) { $mono = $false } } Check "A4c DMX序号(0x1A,2B)严格递增" $mono ("| 0x{0:X4}→0x{1:X4} n={2}" -f $seqs[0], $seqs[-1], $seqs.Count) $tsSame = 0 for ($i=1; $i -lt $a.Count; $i++) { if ((Hex $a[$i].Payload 0x22 8) -eq (Hex $a[$i-1].Payload 0x22 8)) { $tsSame++ } } Check "A4d 时间戳逐帧变化" ($tsSame -eq 0) "| 重复=0" # ---- A5: 形态B 事件帧(主机: 子类型0200在0x20, 序号0x22) + Butler ACK/状态(0x18=0200, 'Butl'0x1A, 序号0x20) ---- $hostB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x20) -eq 2 -and $_.Src -eq '192.168.123.113' }) $devB = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 4 -and (LE16 $_.Payload 0x18) -eq 2 -and $_.Src -eq '192.168.123.1' }) Check "A5a 方向分布" ($hostB.Count -gt 0 -and $devB.Count -gt 0) ("| 主机事件={0} 设备ACK/状态={1}" -f $hostB.Count, $devB.Count) $hostSeq = @($hostB | Where-Object { $_.Payload.Length -ge 0x26 } | ForEach-Object { [int](LE32 $_.Payload 0x22) }) $hbMono = $true; $swaps = 0 for ($i=1; $i -lt $hostSeq.Count; $i++) { if ($hostSeq[$i] -eq ($hostSeq[$i-1] - 1)) { $swaps++ } # 相邻交换(乱序) elseif ($hostSeq[$i] -lt $hostSeq[$i-1]) { $hbMono = $false } # 其它回退=失败 } Check "A5b 事件序号递增(允许相邻交换)" $hbMono ("| 0x{0:X8}→0x{1:X8} 交换={2}" -f $hostSeq[0], $hostSeq[-1], $swaps) $ackSeq = @($devB | Where-Object { $_.Payload.Length -ge 0x24 } | ForEach-Object { [int](LE32 $_.Payload 0x20) }) $matched = @($ackSeq | Where-Object { $hostSeq -contains $_ }).Count Check "A5c ACK序号∈事件序号集" ($matched -gt 0) ("| ACK/状态={0} 命中={1}" -f $ackSeq.Count, $matched) $butl = @($devB | Where-Object { $_.Payload.Length -ge 0x1E -and (Hex $_.Payload 0x1A 4) -eq '4275746c' }) Check "A5d 设备帧含'Butl'" ($butl.Count -eq $devB.Count) ("| {0}/{1}" -f $butl.Count, $devB.Count) # ---- A6: 1947 密钥报文 ---- $k = @($msgs | Where-Object { $_.Dport -eq 1947 }) if ($k.Count -gt 0) { $h0 = [Text.Encoding]::ASCII.GetString($k[0].Payload) $allSame = @($k | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -ne $h0 }).Count -eq 0 Check "A6 1947负载恒定" $allSame ("| n={0} Base64={1}" -f $k.Count, $h0) Check "A6b Base64解码30B" ([Convert]::FromBase64String($h0).Length -eq 30) "| 静态令牌" } else { Check "A6 1947负载恒定" $false "| 无样本" } # ---- A7: A·B·B 节奏 ---- $sess = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and (LE32 $_.Payload 4) -eq 4 -and $_.Src -eq '192.168.123.113' } | Select-Object -First 21) $pat = ($sess | ForEach-Object { if ((LE16 $_.Payload 0x20) -eq 0x0101) { 'A' } else { 'B' } }) -join '' Check "A7 ABB节奏开头" $pat.StartsWith('ABB') "| 前21帧: $pat" # ---- C1: type=02 主机标识心跳 ---- $hb = @($enet | Where-Object { (LE32 $_.Payload 4) -eq 2 }) $hbOk = @($hb | Where-Object { $_.Payload.Length -eq 36 -and (Hex $_.Payload 0x1A 4) -eq '717ba8c0' -and (Hex $_.Payload 0x1E 6) -eq '300ed559bb36' }) Check "C1 type02心跳=36B含主机IP/MAC" ($hbOk.Count -eq $hb.Count -and $hb.Count -gt 0) ("| n={0} 命中={1}" -f $hb.Count, $hbOk.Count) # ---- C2: EUCE 帧 ---- $e = @($m31772 | Where-Object { (Hex $_.Payload 0 4) -eq '45554345' }) $eOk = @($e | Where-Object { (LE32 $_.Payload 0x0C) -eq 5 }) Check "C2 EUCE帧版本05@0x0C" ($eOk.Count -eq $e.Count -and $e.Count -gt 0) ("| n={0} 命中={1}" -f $e.Count, $eOk.Count) # ---- 发现阶段 ---- Write-Host "`n=== 载入 $PcapDiscovery ===" $pkts2 = Read-Pcap $PcapDiscovery $msgs2 = @(); foreach ($p in $pkts2) { $u = UdpPayload $p; if ($u) { $msgs2 += $u } } $d1 = @($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' -and $_.Dport -eq 31772 }) $disc = @($d1 | Where-Object { (LE32 $_.Payload 4) -eq 1 }) Check "B1 type=01发现帧存在" ($disc.Count -gt 0) ("| type01={0}" -f $disc.Count) $resp = @($msgs2 | Where-Object { $_.Src -eq '192.168.123.1' -and $_.Dport -eq 31772 }) $hasName = @($resp | Where-Object { [Text.Encoding]::ASCII.GetString($_.Payload) -match 'Butler S2' }) Check "B2 应答含'Butler S2'" ($hasName.Count -gt 0) ("| Butler应答={0} 含名={1}" -f $resp.Count, $hasName.Count) $badL2 = @(($msgs2 | Where-Object { (Hex $_.Payload 0 4) -eq '454e6574' }) | Where-Object { [int](LE32 $_.Payload 0x14) -ne $_.Payload.Length }) Check "B3 发现帧长度字段" ($badL2.Count -eq 0) "| 全部一致" $hasHost = $false foreach ($u in ($msgs2 | Where-Object { $_.Payload.Length -gt 300 })) { if ([Text.Encoding]::Unicode.GetString($u.Payload) -match 'tonycao') { $hasHost = $true } } Check "B4 宣告帧UTF-16主机名" $hasHost "| >300B帧存在" Write-Host "`n========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" } .Payload 0x2A 4) -eq 'aaaa5dbb' }) Check "R4 DISC发现响应脉冲" ($disc.Count -gt 0) ("| n={0}" -f $disc.Count) } else { Check "R1 RDM帧" $false "| rdm3.pcap 不存在" } Write-Host " ========================================" Write-Host ("验证结果: PASS={0} FAIL={1}" -f $script:pass, $script:fail) if ($script:fail -eq 0) { Write-Host ">>> 协议文档全部断言验证通过 <<<" } else { Write-Host ">>> 存在未通过断言 <<<" }