Public/New-Packet.ps1

function ConvertTo-PacketFieldBytes {
    param($Field)
    $v = $Field.Value
    if ($v -is [byte[]]) { return , $v }
    if ($v -is [ipaddress]) { return , [byte[]]$v.GetAddressBytes() }
    if ($v -is [string]) {
        $hex = $v -replace '[:\s-]', ''
        if ($hex.Length % 2 -ne 0 -or $hex -notmatch '^[0-9A-Fa-f]*$') { throw "Field '$($Field.Name)': '$v' is not a valid hex string" }
        return , [byte[]][System.Convert]::FromHexString($hex)
    }
    if ($v -is [ValueType] -and $v -isnot [bool]) {
        if ($Field.Bytes -lt 1 -or $Field.Bytes -gt 8) { throw "Field '$($Field.Name)': integers need -Bytes between 1 and 8" }
        $n = [System.Numerics.BigInteger]$v
        $max = [System.Numerics.BigInteger]::Pow(2, 8 * $Field.Bytes)
        if ($n.Sign -lt 0 -or $n -ge $max) { throw "Field '$($Field.Name)': value $v does not fit in $($Field.Bytes) byte(s)" }
        $out = [byte[]]::new($Field.Bytes)
        for ($i = $Field.Bytes - 1; $i -ge 0; $i--) {
            $out[$i] = [byte]($n % 256)
            $n = [System.Numerics.BigInteger]::Divide($n, 256)
        }
        return , $out
    }
    throw "Field '$($Field.Name)': unsupported value type $($v.GetType().Name)"
}

function New-Packet {
    <#
    .SYNOPSIS
        Concatenates packet fields into a byte[]; optionally fills a length field with the total length.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][object[]]$Field,
        [string]$LengthField
    )
    $parts = [ordered]@{}
    $i = 0
    foreach ($f in $Field) {
        $parts["$i|$($f.Name)"] = [byte[]](ConvertTo-PacketFieldBytes $f)
        $i++
    }
    $total = 0
    foreach ($p in $parts.Values) { $total += $p.Length }
    if ($LengthField) {
        $key = @($parts.Keys | Where-Object { $_ -like "*|$LengthField" })
        if ($key.Count -eq 0) { throw "Length field '$LengthField' not found" }
        $lf = $Field | Where-Object { $_.Name -eq $LengthField } | Select-Object -First 1
        $parts[$key[0]] = [byte[]](ConvertTo-PacketFieldBytes (New-PacketField $LengthField $total -Bytes $lf.Bytes))
    }
    $l = [System.Collections.Generic.List[byte]]::new()
    foreach ($p in $parts.Values) { $l.AddRange($p) }
    , $l.ToArray()
}