Public/ConvertFrom-OspfMessage.ps1

function ConvertFrom-OspfMessage {
    <#
    .SYNOPSIS
        Parses an OSPFv2 packet (byte[]) into an object. Malformed input yields Valid=$false and an Error text instead of throwing.
    .DESCRIPTION
        Use -HasIpHeader for bytes captured from a raw socket. With -AuthKey, AuthValid reports whether
        the simple password or MD5 digest verifies.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline)][byte[]]$Message,
        [switch]$HasIpHeader,
        [string]$AuthKey
    )
    begin { $buf = [System.Collections.Generic.List[byte]]::new() }
    process { if ($null -ne $Message) { $buf.AddRange($Message) } }
    end {
        $data = $buf.ToArray()
        try {
            if ($HasIpHeader) {
                $ihl = ($data[0] -band 0x0F) * 4
                $data = [byte[]]$data[$ihl..($data.Length - 1)]
            }
            if ($data.Length -lt 24) { throw 'Truncated OSPF header' }
            $type = [int]$data[1]
            $len = [int](Get-OspfUInt $data 2 2)
            if ($len -lt 24 -or $len -gt $data.Length) { throw "Invalid OSPF length $len (have $($data.Length) bytes)" }
            $au = [int](Get-OspfUInt $data 14 2)
            $o = [ordered]@{
                Valid          = $true
                Error          = $null
                Version        = [int]$data[0]
                Type           = $type
                TypeName       = $script:PacketTypeNames[$type]
                Length         = $len
                RouterId       = Get-OspfIp $data 4
                AreaId         = Get-OspfIp $data 8
                Checksum       = [int](Get-OspfUInt $data 12 2)
                ChecksumValid  = if ($au -eq 2) { $null } else { Test-OspfPacketChecksum $data }
                AuthType       = if ($script:AuthTypeNames.ContainsKey($au)) { $script:AuthTypeNames[$au] } else { "$au" }
                AuthValid      = $null
                KeyId          = $null
                CryptoSequence = $null
            }
            if ($au -eq 2) {
                $o.KeyId = [int]$data[18]
                $o.CryptoSequence = Get-OspfUInt $data 20 4
            }
            if ($PSBoundParameters.ContainsKey('AuthKey') -or $au -eq 0) { $o.AuthValid = Test-OspfPacketAuth -Data $data -AuthKey $AuthKey }
            switch ($type) {
                1 {
                    $o.NetworkMask = Get-OspfIp $data 24
                    $o.HelloInterval = [int](Get-OspfUInt $data 28 2)
                    $o.Options = [int]$data[30]
                    $o.Priority = [int]$data[31]
                    $o.DeadInterval = Get-OspfUInt $data 32 4
                    $o.DesignatedRouter = Get-OspfIp $data 36
                    $o.BackupDesignatedRouter = Get-OspfIp $data 40
                    $n = for ($p = 44; $p + 4 -le $len; $p += 4) { Get-OspfIp $data $p }
                    $o.Neighbors = @($n)
                }
                2 {
                    $o.InterfaceMtu = [int](Get-OspfUInt $data 24 2)
                    $o.Options = [int]$data[26]
                    $f = [int]$data[27]
                    $o.Flags = $f
                    $o.Init = [bool]($f -band 4)
                    $o.More = [bool]($f -band 2)
                    $o.Master = [bool]($f -band 1)
                    $o.Sequence = Get-OspfUInt $data 28 4
                    $h = for ($p = 32; $p + 20 -le $len; $p += 20) { Read-OspfLsaHeader $data $p }
                    $o.LsaHeaders = @($h)
                }
                3 {
                    $r = for ($p = 24; $p + 12 -le $len; $p += 12) {
                        $t = [int](Get-OspfUInt $data $p 4)
                        [pscustomobject]@{ Type = $t; TypeName = $script:LsaTypeNames[$t]; LinkStateId = (Get-OspfIp $data ($p + 4)); AdvertisingRouter = (Get-OspfIp $data ($p + 8)) }
                    }
                    $o.Requests = @($r)
                }
                4 {
                    $count = [int](Get-OspfUInt $data 24 4)
                    $lsas = [System.Collections.Generic.List[object]]::new()
                    $p = 28
                    for ($i = 0; $i -lt $count -and $p + 20 -le $len; $i++) {
                        $lsa = Read-OspfLsa $data $p
                        $lsas.Add($lsa)
                        $p += $lsa.Length
                    }
                    $o.LsaCount = $count
                    $o.Lsas = $lsas.ToArray()
                }
                5 {
                    $h = for ($p = 24; $p + 20 -le $len; $p += 20) { Read-OspfLsaHeader $data $p }
                    $o.LsaHeaders = @($h)
                }
                default { throw "Unknown OSPF packet type $type" }
            }
            $o.Bytes = $data
            [pscustomobject]$o
        }
        catch {
            [pscustomobject]@{ Valid = $false; Error = $_.Exception.Message; Bytes = $data }
        }
    }
}