Search code examples
powershellbinary-operators

Why doesn't this bor and bnot expression give the expected result in Powershell?


why doesn't this bor bnot give the expected result in powershell?

To find the last address in an ipv6 subnet one needs to do a "binary or" and a "binary not" operation.

The article I'm reading (https://www.codeproject.com/Articles/660429/Subnetting-with-IPv6-Part-1-2) describes it like this:

(2001:db8:1234::) | ~(ffff:ffff:ffff::) = 2001:db8:1234:ffff:ffff:ffff:ffff:ffff

Where | is a "binary or" and
~ is a "binary not"


In powershell however, I try it like:

$mask = 0xffffffff
$someOctet = 0x0000
"{0:x4}" -f ($someOctet -bor -bnot ($mask) )

and I get 0000 instead of ffff

Why is this?


Solution

  • The tutorial is doing a -not of the entire subnet mask, so ff00 inverts to 00ff and similar for longer Fs and 0s; you aren't doing that, so you don't get the same results.

    The fully expanded calculation that you show is doing this:

    1. (2001:0db8:1234:0000:0000:0000:0000:0000) | ~(ffff:ffff:ffff:0000:0000:0000:0000:0000) 
    
    2. (2001:0db8:1234:0000:0000:0000:0000:0000) |  (0000:0000:0000:ffff:ffff:ffff:ffff:ffff) 
    
    3. = 2001:db8:1234:ffff:ffff:ffff:ffff:ffff
    

    Note how in step 1. to step 2, the not is inverting the pattern of Fs and 0s, switching the subnet mask around, and switching it around between the bit where the prefix ends and the bit where the host part begins.

    Then step 3 or takes only the set bits from the left to keep those numbers the same (neither zero'd nor ffff'd), and all the set bits from the right (to ffff those, maxing them to the max IP address within that prefix).

    In other words, it makes no sense to do this "an octet at a time". This is a whole IP address (or whole prefix) + whole subnet mask operation.

    Where the tutorial says:

    & (AND), | (OR), ~ (NOT or bit INVERTER): We will use these three bitwise operators in our calculations. I think everybody is familiar -at least from university digital logic courses- and knows how they operate. I will not explain the details here again. You can search for 'bitwise operators' for further information.

    If you aren't very familiar with what they do, it would be worth studying that more, before trying to apply them to IP subnetting. Because you are basically asking why 0 or (not 1) is 0 and the answer is because that's how Boolean logic "or" and "not" work.


    Edit for your comment

    [math]::pow(2,128) is a lot bigger than [decimal]::maxvalue, so I don't think Decimal will do.

    I don't know what a recommended way to do it is, but I imagine if you really wanted to do it all within PowerShell with -not you'd have to process it with [bigint] (e.g. [bigint]::Parse('20010db8123400000000000000000000', 'hex')).

    But more likely, you'd do something more long-winded like:

    # parse the address and mask into IP address objects
    # which saves you having to expand the short version to 
    $ip = [ipaddress]::Parse('fe80::1')
    $mask = [ipaddress]::Parse('ffff::')
    
    
    # Convert them into byte arrays, then convert those into BitArrays
    $ipBits = [System.Collections.BitArray]::new($ip.GetAddressBytes())
    $maskBits = [System.Collections.BitArray]::new($mask.GetAddressBytes())
    
    
    # ip OR (NOT mask) calculation using BitArray's own methods
    $result = $ipBits.Or($maskBits.Not())
    
    
    # long-winded way to get the resulting BitArray back to an IP
    # via a byte array
    
    $byteTemp = [byte[]]::new(16)
    $result.CopyTo($byteTemp, 0)
    $maxIP = [ipaddress]::new($byteTemp)
    
    $maxIP.IPAddressToString
    
    # fe80:ffff:ffff:ffff:ffff:ffff:ffff:ffff