Public/New-BfdControl.ps1

$script:BfdStates = @{ 0 = 'AdminDown'; 1 = 'Down'; 2 = 'Init'; 3 = 'Up' }
$script:BfdDiags = @{
    0 = 'No Diagnostic'; 1 = 'Control Detection Time Expired'; 2 = 'Echo Function Failed'
    3 = 'Neighbor Signaled Session Down'; 4 = 'Forwarding Plane Reset'; 5 = 'Path Down'
    6 = 'Concatenated Path Down'; 7 = 'Administratively Down'; 8 = 'Reverse Concatenated Path Down'
}

function New-BfdControl {
    <#
    .SYNOPSIS
        Builds a 24-byte BFD control packet (RFC 5880). Intervals are in microseconds.
    #>

    [CmdletBinding()]
    param(
        [ValidateSet('AdminDown', 'Down', 'Init', 'Up')][string]$State = 'Down',
        [ValidateRange(0, 31)][int]$Diagnostic = 0,
        [ValidateRange(0, 255)][int]$DetectMult = 3,
        [uint32]$MyDiscriminator = 1,
        [uint32]$YourDiscriminator = 0,
        [uint32]$DesiredMinTx = 1000000,
        [uint32]$RequiredMinRx = 1000000,
        [uint32]$RequiredMinEchoRx = 0,
        [switch]$Poll,
        [switch]$Final,
        [switch]$Demand,
        [int]$Version = 1,
        [ValidateSet('None', 'SimplePassword', 'KeyedSHA1')][string]$AuthType = 'None',
        [ValidateRange(0, 255)][int]$AuthKeyId = 0,
        [string]$AuthKey,
        [uint32]$AuthSequence = 0
    )
    $stateCode = @{ AdminDown = 0; Down = 1; Init = 2; Up = 3 }[$State]
    $flags = 0
    if ($Poll)   { $flags = $flags -bor 0x20 }
    if ($Final)  { $flags = $flags -bor 0x10 }
    if ($Demand) { $flags = $flags -bor 0x02 }

    $keyBytes = [byte[]]@()
    $authLen = 0
    if ($AuthType -ne 'None') {
        if (-not $AuthKey) { throw "-AuthKey is required with -AuthType $AuthType." }
        $keyBytes = [Text.Encoding]::UTF8.GetBytes($AuthKey)
        $max = if ($AuthType -eq 'SimplePassword') { 16 } else { 20 }
        if ($keyBytes.Length -gt $max) { throw "-AuthKey can be at most $max bytes for $AuthType." }
        $authLen = if ($AuthType -eq 'SimplePassword') { 3 + $keyBytes.Length } else { 28 }
        $flags = $flags -bor 0x04
    }

    $hdr = [byte[]](New-Packet -Field @(
            New-PacketField VerDiag    (($Version * 32) + $Diagnostic) -Bytes 1
            New-PacketField StateFlags (($stateCode * 64) + $flags) -Bytes 1
            New-PacketField DetectMult $DetectMult -Bytes 1
            New-PacketField Length     (24 + $authLen) -Bytes 1
            New-PacketField MyDisc     $MyDiscriminator -Bytes 4
            New-PacketField YourDisc   $YourDiscriminator -Bytes 4
            New-PacketField DesiredTx  $DesiredMinTx -Bytes 4
            New-PacketField RequiredRx $RequiredMinRx -Bytes 4
            New-PacketField EchoRx     $RequiredMinEchoRx -Bytes 4
        ))

    switch ($AuthType) {
        'SimplePassword' { , [byte[]]($hdr + [byte[]]@(1, $authLen, $AuthKeyId) + $keyBytes) }
        'KeyedSHA1' {
            $seq = [byte[]]@((($AuthSequence -shr 24) -band 255), (($AuthSequence -shr 16) -band 255), (($AuthSequence -shr 8) -band 255), ($AuthSequence -band 255))
            $auth = [byte[]]@(4, 28, $AuthKeyId, 0) + $seq
            $padded = [byte[]]::new(20); [Array]::Copy($keyBytes, $padded, $keyBytes.Length)
            $hash = [Security.Cryptography.SHA1]::HashData([byte[]]($hdr + $auth + $padded))
            , [byte[]]($hdr + $auth + $hash)
        }
        default { , $hdr }
    }
}

function ConvertFrom-BfdControl {
    [CmdletBinding()]
    param([Parameter(Mandatory, ValueFromPipeline)][byte[]]$Packet, [string]$AuthKey)
    process {
        if ($Packet.Length -lt 24) { throw 'BFD control packet must be at least 24 bytes.' }
        $stateCode = [int][Math]::Floor($Packet[1] / 64)
        $diag = [int]($Packet[0] % 32)
        $auth = $null
        if (($Packet[1] -band 0x04) -and $Packet.Length -ge 27) {
            $type = @{ 1 = 'SimplePassword'; 4 = 'KeyedSHA1' }[[int]$Packet[24]]
            $auth = [pscustomobject]@{
                Type     = if ($type) { $type } else { "Type $($Packet[24])" }
                KeyId    = [int]$Packet[26]
                Sequence = if ($Packet[24] -eq 4 -and $Packet.Length -ge 32) { [uint32](Get-NetUInt $Packet 28 4) } else { $null }
                Valid    = if ($AuthKey -and $type) { Test-BfdAuth -Packet $Packet -Type $type -KeyId $Packet[26] -Key $AuthKey } else { $null }
            }
        }
        [pscustomobject]@{
            PSTypeName        = 'PowerBGP.BfdControl'
            Auth              = $auth
            Version           = [int][Math]::Floor($Packet[0] / 32)
            Diagnostic        = $diag
            DiagnosticName    = $script:BfdDiags[$diag]
            State             = $script:BfdStates[$stateCode]
            Poll              = [bool]($Packet[1] -band 0x20)
            Final             = [bool]($Packet[1] -band 0x10)
            Demand            = [bool]($Packet[1] -band 0x02)
            DetectMult        = [int]$Packet[2]
            Length            = [int]$Packet[3]
            MyDiscriminator   = [uint32](Get-NetUInt $Packet 4 4)
            YourDiscriminator = [uint32](Get-NetUInt $Packet 8 4)
            DesiredMinTx      = [uint32](Get-NetUInt $Packet 12 4)
            RequiredMinRx     = [uint32](Get-NetUInt $Packet 16 4)
            RequiredMinEchoRx = [uint32](Get-NetUInt $Packet 20 4)
        }
    }
}