psipnet.psm1

class IP {
    [bool[]] hidden $ipbitmask
    [string] hidden $ip

    IP([string]$ip) {
        $this.ip = $ip.Trim()
        if($this.ip.Contains(".")) {
            $this.ipbitmask = [IP]::GetIP32BitMask($this.ip)    
        }
        elseif($this.ip.Contains(":")) {
            $this.ip = $this.ip.ToUpper()
            $this.ipbitmask = [IP]::GetIP128BitMask($this.ip)    
        }
        else {
            throw "Invalid IP Address: $ip"
        }

    }

    [string[]] hidden static GetIPv6Segments([string]$ip) {
        $ipa = $ip.Trim().Split(':')
        if($ipa.Count -ne 8) {
            $ipa = $ip.Trim().Split('::')
            if($ipa.Count -ne 2) {
                throw "Invalid IP Address: $ip"
            }
            
            $ipa1 = $ipa[0].Split(':')
            $ipa2 = $ipa[1].Split(':')
            $ipa = @()
            foreach($i in $ipa1) {
                if($i -eq "") {
                    $i = "0"
                }
                $ipa += $i
            }
            for($i = 0; $i -lt (8 - ($ipa1.Count + $ipa2.Count)); $i++) {
                $ipa += "0"
            }
            foreach($i in $ipa2) {
                if($i -eq "") {
                    $i = "0"
                }
                $ipa += $i
            }

            if($ipa.Count -ne 8) {
                throw "Invalid IP Address: $ip"
            }
        }
        return $ipa
    }

    [bool[]] hidden static GetIP128BitMask([string]$ip) {
        $bm = new-object bool[] 128

        for($i = 0; $i -lt 128; $i++) {
            $bm[$i] = $false
        }

        $ipa = [IP]::GetIPv6Segments($ip)

        for($i = 0; $i -lt 8; $i++) {
            if($ipa[$i] -eq "") {
                continue
            }
            $seg =  [Convert]::ToInt32($ipa[$i], 16)

            for($ii = 0; $seg -gt 0; $ii++) {
                $bm[($i * 16) + 16 - $ii - 1] = [bool]($seg % 2)
                $seg = [Math]::Floor($seg / 2)
            }

            $s = ""
            for($ii = 0; $ii -lt 16; $ii++) {
                if($bm[($i * 16) + $ii]) {
                    $s += "1"
                }
                else {
                    $s += "0"
                }
            }
        }

        return $bm
    }

    [bool[]] hidden static GetIP32BitMask([string]$ip) {
        $bm = new-object bool[] 32

        for($i = 0; $i -lt 32; $i++) {
            $bm[$i] = $false
        }

        $ipa = $ip.Trim().Split('.')
        if($ipa.Count -ne 4) {
            throw "Invalid IP Address: $ip"
        }

        for($i = 0; $i -lt 4; $i++) {
            $seg = [int]$ipa[$i]
            for($ii = 0; $seg -gt 0; $ii++) {
                #Write-Host ("" + (($i * 8) + 8 - $ii - 1) + " " + ($seg % 2) + " $seg")
                $bm[($i * 8) + 8 - $ii - 1] = [bool]($seg % 2)
                $seg = [Math]::Floor($seg / 2)
            }

            $s = ""
            for($ii = 0; $ii -lt 8; $ii++) {
                if($bm[($i * 8) + $ii]) {
                    $s += "1"
                }
                else {
                    $s += "0"
                }
            }
        }
        return $bm
    }

    [string] GetIP() {
        return $this.ip
    }

    [bool[]] GetBitMask() {
        return $this.ipbitmask
    }

    [string] GetBitMaskString() {
        if($this.ipbitmask.Count -eq 32) {
            $s = ""
            for($i = 0; $i -lt 32; $i++) {
                if(($i % 8) -eq 0  -and  $i -gt 0) {
                    $s += "."
                }
                if($this.ipbitmask[$i]) {
                    $s += "1"
                }
                else {
                    $s += "0"
                }
            }
            return $s
        }
        elseif($this.ipbitmask.Count -eq 128) {
            $s = ""
            for($i = 0; $i -lt 128; $i++) {
                if(($i % 16) -eq 0  -and  $i -gt 0) {
                    $s += ":"
                }
                if($this.ipbitmask[$i]) {
                    $s += "1"
                }
                else {
                    $s += "0"
                }
            }
            return $s
        }
        else {
            return ""
        }
    }

    [bool] IsIPv4() {
        return $this.ipbitmask.Count -eq 32
    }

    [bool] IsIPv6() {
        return $this.ipbitmask.Count -eq 128
    }

    [bool] BelongsToSubnet([string]$subnet) {
        return ([Subnet]::new($subnet)).ContainsIP($this)
    }

    [bool] BelongsToSubnet([Subnet]$subnet) {
        return $subnet.ContainsIP($this)
    }

    [string] ToString() {
        return ( "" + $this.ip )
    }

    [IP] ConvertToIPv6() {
        if($this.IsIPv6()) {
            return $this
        }
        
        if($this.IsIPv4()) {
            $ipstr = "::FFFF"
            $i = 0
            foreach($p in $this.ip.Split(".")) {
                if(($i % 2) -eq 0) {
                    $ipstr += ":"
                }
                $s = ([int]$p).ToString("X")
                if($s.Length -lt 2) {
                    $s = "0$s"
                }
                $ipstr += "$s"
                $i++
            }
            return [IP]::new($ipstr.ToUpper())
        }

        throw "IP cannot be converted to version 6"
    }

    [string] Normalize() {
        return [Subnet]::GetIPFromBitmask($this.ipbitmask).ToString()
    }

    [string] Compress() {
        if($this.IsIPv6()) {
            $s = [Subnet]::GetIPFromBitmask($this.ipbitmask).ToString()

            if($s.Contains(":0:0:0:0:0:0:")) {
                $s = $s.Replace(":0:0:0:0:0:0:", "::", 1)
            }
            elseif($s.Contains(":0:0:0:0:0:")) {
                $s = $s.Replace(":0:0:0:0:0:", "::", 1)
            }
            elseif($s.Contains(":0:0:0:0:")) {
                $s = $s.Replace(":0:0:0:0:", "::", 1)
            }
            elseif($s.Contains(":0:0:0:")) {
                $s = $s.Replace(":0:0:0:", "::", 1)
            }
            elseif($s.Contains(":0:0:")) {
                $s = $s.Replace(":0:0:", "::", 1)
            }
            elseif($s.Contains(":0:")) {
                $s = $s.Replace(":0:", "::", 1)
            }

            if($s.StartsWith("0::")) {
               $s = $s.Substring(1)
            }
            if($s.EndsWith("::0")) {
                $s = $s.Substring(0, $s.Length -1)
             }
 
            return $s
        }
        elseif($this.IsIPv4()) {
            return [Subnet]::GetIPFromBitmask($this.ipbitmask).ToString()
        }

        throw "IP cannot be compressed"
    }
}


class Subnet {
    [IP] hidden $ip
    [IP] hidden $mask
    [int] hidden $cidr

    Subnet([string]$subnet) {
        $parts = $subnet -split "/"
        if($parts.Count -ne 2) {
            throw "Invalid subnet: $subnet"
        }
        $parts[0] = $parts[0].Trim()
        $parts[1] = $parts[1].Trim()
        $this.ip = [IP]::new($parts[0])

        if($parts[1] -match '^\d+$' ) {
            $this.cidr = ([int]$parts[1])
            $this.mask = [Subnet]::GetMaskIPFromCIDR($parts[1], $this.ip.GetBitMask().Count)
        }
        else {
            $this.cidr = -1
            $this.mask = [IP]::new($parts[1])
        }
        if($this.ip.IsIPv4() -and (-not $this.mask.IsIPv4())) {
            throw "Invalid subnet: $subnet"
        }
        if($this.ip.IsIPv6() -and (-not $this.mask.IsIPv6())) {
            throw "Invalid subnet: $subnet"
        }
    }

    [IP] hidden static  GetMaskIPFromCIDR([int]$int, [int]$len) {
        $bm = new-object bool[] $len

        for($i = 0; $i -lt $len; $i++) {
            $bm[$i] = $false
        }

        for($i = 0; $i -lt $int; $i++) {
            $bm[$i] = $true
        }

        return  [IP]::new([Subnet]::GetIPFromBitmask($bm))
    }

    [string] hidden static GetIPFromBitmask([bool[]] $bm) {
        if($bm.Count -eq 32) {
            $a=@()
            for($i = 0; $i -lt 4; $i++) {
                $d = 0;
                for($ii = 0; $ii -lt 8; $ii++) {
                    if($bm[($i*8) + $ii]) {
                        $d += [Math]::pow(2, 8 - $ii - 1)
                    }
                }
                $a += $d
            }
            return ($a -join ".")
        }
        elseif($bm.Count -eq 128) {
            $s = ""
            for($i = 0; $i -lt 32; $i++) {
                if(($i % 4) -eq 0  -and  $i -gt 0) {
                    $s+=":"
                }

                $d = 0
                for($ii = 0; $ii -lt 4 ; $ii++) {
                    if($bm[($i * 4) + $ii]) {
                        $d += [Math]::pow(2, 4 - $ii -1)
                    }
                }
                $s += ([int]$d).ToString("X")
            }
            # removed unwanted zeros
            $a = $s -split ":"
            for($i=0 ; $i -lt 8; $i++) {
                while($a[$i][0] -eq "0"  -and  $a[$i] -ne "0") {
                    $a[$i] = $a[$i].Substring(1)
                }
            }
            return ($a -join ":")
        }
        else {
            throw ( "Invalid IP adress with " + $bm.Count + " bits" )
        }
    }

    [string] ToString() {
        if($this.cidr -ne -1) {
            return ( $this.ip.GetIP() + " / " + $this.cidr)
        }
        return ( $this.ip.GetIP() + " / " + $this.mask.GetIP())
    }

    [IP] GetSubnetIP() {
        return $this.ip
    }

    [IP] GetSubnetMask() {
        return $this.mask
    }

    [IP] GetFirstIP() {
        $ipbm = $this.ip.GetBitMask()
        $maskbm = $this.mask.GetBitMask()

        $len = $ipbm.Count
        $bm = new-object bool[] $len

        for($i = 0; $i -lt $len; $i++) {
            if($maskbm[$i]) {
                $bm[$i] = $ipbm[$i]
            }
            else {
                $bm[$i] = $false
            }
        }

        return [IP]::new([Subnet]::GetIPFromBitmask($bm))
    }

    [IP] GetLastIP() {
        $ipbm = $this.ip.GetBitMask()
        $maskbm = $this.mask.GetBitMask()

        $len = $ipbm.Count
        $bm = new-object bool[] $len

        for($i = 0; $i -lt $len; $i++) {
            if($maskbm[$i]) {
                $bm[$i] = $ipbm[$i]
            }
            else {
                $bm[$i] = $true
            }
        }

        return [IP]::new([Subnet]::GetIPFromBitmask($bm))
    }

    [bool] ContainsIP([string]$ip) {
        return $this.ContainsIP([IP]::new($ip))
    }

    [bool] ContainsIP([IP]$ip) {
        $bm = $ip.GetBitMask()
        $ipbm = $this.ip.GetBitMask()
        $maskbm = $this.mask.GetBitMask()

        $len = $ipbm.Count

        # different ip versions? return false
        if($bm.Count -ne $len) {
            return $false
        }

        for($i = 0; $i -lt $len; $i++) {
            if($maskbm[$i]) {
                if($bm[$i] -ne $ipbm[$i]) {
                    return $false
                }
            }
        }

        return $true
    }

    [bool] Intersects([string]$snet) {
        return $this.Intersects([Subnet]::new($snet))
    }

    [bool] Intersects([Subnet]$snet) {
        $ipbm = $this.ip.GetBitMask()
        $maskbm = $this.mask.GetBitMask()

        $ipbm2 = $snet.GetSubnetIP().GetBitMask()
        $maskbm2 = $snet.GetSubnetMask().GetBitMask()

        $len = $ipbm.Count
        # different ip versions? return false
        if($ipbm2.Count -ne $len) {
            return $false
        }

        for($i = 0; $i -lt $len; $i++) {
            if($maskbm[$i] -and $maskbm2[$i]) {
                if($ipbm2[$i] -ne $ipbm[$i]) {
                    return $false
                }
            }
        }

        return $true
    }

    [bool] IsIPv4() {
        return $this.ip.GetBitMask().Count -eq 32
    }

    [bool] IsIPv6() {
        return $this.ip.GetBitMask().Count -eq 128
    }

    [bool] IsCIDR() {
        return $this.cidr -ne -1
    }

    [Subnet] ConvertToIPv6() {
        if($this.IsIPv6()) {
            return $this
        }
        
        if($this.IsIPv4()) {
            if($this.cidr -ne -1) {
                return [Subnet]::new($this.ip.ConvertToIPv6().GetIP() + "/" + ($this.cidr + 96))
            }

            $bm = new-object bool[] 128
            for($i = 0; $i -lt 96; $i++) {
                $bm[$i] = $true
            }
    
            $bmm = $this.mask.GetBitMask()
            for($i = 0; $i -lt 32; $i++) {
                $bm[$i + 96] = $bmm[$i]
            }

            return [Subnet]::new($this.ip.ConvertToIPv6().GetIP() + "/" + [Subnet]::GetIPFromBitmask($bm))
        }

        throw "IP cannot be converted to version 6" 
    }

    [int] GetAddressSpaceBits() {
        $bm = $this.mask.GetBitMask()
        $len = $bm.Count
        $c = 0;
        for($i=0; $i -lt $len; $i++) {
            if($bm[$i] -eq $false) {
                $c++
            }
        }

        return $c
    }

    [string] GetAddressSpace() {
        $c = $this.GetAddressSpaceBits()
        if($c -eq 0) {
            return "0"
        }

        # calculate number
        $b = New-Object "System.Numerics.BigInteger" 1
        while($c -gt 0) {
            $b *= 2
            $c--
        }

        # beautify with '
        $b = [string]$b
        $len = $b.Length
        $s = ""
        for($i = 0; $i -lt $len; $i++) {
            $ii = $len - $i 
            if((($ii) % 3) -eq 0  -and  $ii -ne $len) {
                $s +=  "'"
            }
            $s += $b[$i]
        }
        
        return $s
    }

    [SubnetIPIterator] GetIPIterator() {
        return [SubnetIPIterator]::new($this)
    }

    [SubnetSubnetIterator] GetSubnetIterator([int]$cidrbits) {
        return [SubnetSubnetIterator]::new($this, $cidrbits)
    }
}

class SubnetIPIterator : System.Collections.IEnumerator {
    [Subnet] hidden $subnet
    [BitArrayIterator] hidden $baiter
    [int[]] hidden $bapos

    SubnetIPIterator([Subnet] $subnet) {
        $this.subnet = $subnet

        $len = $this.subnet.GetAddressSpaceBits()
        if($len -lt 1) {
            throw "Subnetmask it scoped to a single IP."
        }
        $this.baiter = [BitArrayIterator]::new($len)

        $this.bapos = new-object int[] $len
        $bm = $this.subnet.GetSubnetMask().GetBitMask()
        $bmlen = $bm.Count
        $ii=0;
        for($i=0; $i -lt $bmlen -and $ii -lt $len; $i++) {
            if($bm[$i] -eq $false) {
                $this.bapos[$ii] = $i
                $ii++
            }
        }
    }

    [void] Reset() {
        $this.baiter.Reset()
    }

    # Enumerators are positioned before the first element until the first MoveNext() call.
    [bool] MoveNext() {
        return $this.baiter.MoveNext()
    }

    [object] get_Current() {
        return $this.GetIP()
    }

    [void] set_Currrent([object] $value) {
        if(-not $this.SetIP([IP] $value)) {
            throw "Failed to set the IP"
        }
    }

    [IP] GetIP() {
        $bm = $this.Subnet.GetSubnetIP().GetBitMask()
        $ba = $this.baiter.GetPosition()
        $len = $ba.Count

        for($i=0; $i -lt $len; $i++) {
            $bm[$this.bapos[$i]] = $ba[$i]
        }

        return [IP]::new([Subnet]::GetIPFromBitmask($bm))
    }


    [bool] SetIP([string] $ip) {
        return $this.SetIP([IP]::new($ip))
    }

    [bool] SetIP([IP] $ip) {
        if(-not $this.Subnet.ContainsIP($ip)) {
            return $false
        }

        $bm = $ip.GetBitMask()
        $ba = $this.baiter.GetPosition()
        $len = $ba.Count

        for($i=0; $i -lt $len; $i++) {
            $ba[$i] = $bm[$this.bapos[$i]]
        }

        return $this.SetPosition($ba)
    }

    [bool[]] GetPosition() {
        return $this.baiter.GetPosition()
    }

    [bool] SetPosition([bool[]]$bits) {
        return $this.baiter.SetPosition($bits)
    }

    [bool] SetPositionFirstBits([int]$num, [bool]$val) {
        if($num -lt 0) {
            return $false
        }
        $bits = $this.baiter.GetPosition()
        if($num -gt $bits.Count) {
            return $false
        }
        for($i = 0 ; $i -lt $num; $i++) {
            $bits[$i] = $val
        }
        return $this.baiter.SetPosition($bits)
    }

    [bool] SetPositionLastBits([int]$num, [bool]$val) {
        if($num -lt 0) {
            return $false
        }
        $bits = $this.baiter.GetPosition()
        if($num -gt $bits.Count) {
            return $false
        }
        $len = $bits.Count
        for($i = 0 ; $i -lt $num; $i++) {
            $bits[$len - $i -1] = $val
        }
        return $this.baiter.SetPosition($bits)
    }

    [bool] Skip([int]$num) {
        return $this.baiter.Skip($num)
    }
}

class SubnetSubnetIterator : System.Collections.IEnumerator {
    [Subnet] hidden $subnet
    [BitArrayIterator] hidden $baiter
    [int[]] hidden $bapos
    [string] hidden $subnetmask

    SubnetSubnetIterator([Subnet] $subnet, [int]$cidrbits) {
        $this.subnet = $subnet

        if($this.subnet.IsIPv4()) {
            $bits = 32 - $cidrbits
        }
        elseif($this.subnet.IsIPv6()) {
            $bits = 128 - $cidrbits
        }
        else {
            throw "Not Implemented"
        }
        if($bits -lt 0) {
            throw "Invalid cidr bits number"
        }

        $len = $this.subnet.GetAddressSpaceBits()
        if($len -lt 1) {
            throw "Subnetmask it scoped to a single IP."
        }
        if($bits -ge $len) {
            throw "Subnetmask just has $len bits, cannot iterate with $bits"
        }
        $this.baiter = [BitArrayIterator]::new($len)
        $this.baiter.SetBitSteps($bits + 1)

        $this.bapos = new-object int[] $len
        $bm = $this.subnet.GetSubnetMask().GetBitMask()
        $bmlen = $bm.Count
        $ii=0;
        for($i=0; $i -lt $bmlen -and $ii -lt $len; $i++) {
            if($bm[$i] -eq $false) {
                $this.bapos[$ii] = $i
                $ii++
            }
        }

        if($this.subnet.IsCIDR()) {
            $this.subnetmask = $cidrbits.ToString()
        }
        else {
            for($i=0; $i -lt $len - $bits; $i++) {
                $bm[$this.bapos[$i]] = $true
            }

            $this.subnetmask = [IP]::new([Subnet]::GetIPFromBitmask($bm)).ToString()
        }
    }

    [void] Reset() {
        $this.baiter.Reset()
    }

    # Enumerators are positioned before the first element until the first MoveNext() call.
    [bool] MoveNext() {
        return $this.baiter.MoveNext()
    }

    [object] get_Current() {
        return $this.GetSubnet()
    }

    [Subnet] GetSubnet() {
        $bm = $this.Subnet.GetSubnetIP().GetBitMask()
        $ba = $this.baiter.GetPosition()
        $len = $ba.Count

        for($i=0; $i -lt $len; $i++) {
            $bm[$this.bapos[$i]] = $ba[$i]
        }
        
        return [Subnet]::new([Subnet]::GetIPFromBitmask($bm) + "/" + $this.subnetmask)
    }

    [bool[]] GetPosition() {
        return $this.baiter.GetPosition()
    }

    [bool] SetPosition([bool[]]$bits) {
        return $this.baiter.SetPosition($bits)
    }

    [bool] SetPositionFirstBits([int]$num, [bool]$val) {
        if($num -lt 0) {
            return $false
        }
        $bits = $this.baiter.GetPosition()
        if($num -gt $bits.Count) {
            return $false
        }
        for($i = 0 ; $i -lt $num; $i++) {
            $bits[$i] = $val
        }
        return $this.baiter.SetPosition($bits)
    }

    [bool] SetPositionLastBits([int]$num, [bool]$val) {
        if($num -lt 0) {
            return $false
        }
        $bits = $this.baiter.GetPosition()
        if($num -gt $bits.Count) {
            return $false
        }
        $len = $bits.Count
        for($i = 0 ; $i -lt $num; $i++) {
            $bits[$len - $i -1] = $val
        }
        return $this.baiter.SetPosition($bits)
    }

    [bool] Skip([int]$num) {
        return $this.baiter.Skip($num)
    }
}

class BitArrayIterator : System.Collections.IEnumerator {
    [bool[]] hidden $bitarray
    [bool] hidden $minusOne = $true
    [int] hidden $steps = 1

    BitArrayIterator([int]$bits) {
        if($bits -lt 1) {
            throw "Invalid Value"
        }

        $this.bitarray = new-object bool[] $bits
        $this.Reset()
    }

    [void] Reset() {
        $len = $this.bitarray.Count
        for($i = 0; $i -lt $len; $i++) {
            $this.bitarray[$i] = $false
        }
        $this.minusOne = $true
    }

    [int] GetBitSteps() {
        return $this.steps
    }

    [bool] SetBitSteps([int]$steps) {
        if($steps -gt 0   -and  $steps -lt $this.bitarray.Count) {
            $this.steps = $steps
            return $true
        }
        return $false
    }

    # Enumerators are positioned before the first element until the first MoveNext() call.
    [bool] MoveNext() {
        if($this.minusOne -eq $true) {
            $this.minusOne = $false
            return $true
        }
        $len = $this.bitarray.Count
        for($i = $len - $this.steps; $i -ge 0; $i--) {
            if($this.bitarray[$i] -eq $true) {
                $this.bitarray[$i] = $false
            }
            else {
                $this.bitarray[$i] = $true
                return $true;
            }
        }
        return $false
    }

    [object] get_Current() {
        return $this.bitarray
    }

    [bool] Skip([int]$num) {
        if($num -lt 1) {
            return $false
        }
        if($this.minusOne) {
            $this.minusOne = $false
            if($num -eq 1) {
                return $true
            }
            $num--
        }
        for($i = 0; $i -lt $num; $i++) {
            if(-not $this.MoveNext()) {
                return $false
            }
        }
        return $true
    }

    [bool[]] GetPosition() {
        return $this.bitarray
    }

    [bool] SetPosition([bool[]]$bits) {
        if($bits.Count -eq $this.bitarray.Count) {
            $this.bitarray = $bits
            return $true
        }
        return $false
    }
}


<#
 .Synopsis
  Creates a new IP Address Object

 .Description
  Creates a new IP Address Object

 .Parameter ip
  The IP Address in V4 or V6 Format

 .Example
   $ip = New-IPAddress "192.168.1.2"
   $ip.BelongsToSubnet("192.168.1.0/24")

 .Example
   $ip = New-IPAddress "::FFFF:0A0A:0A0A"

 .Example
   $ip = New-IPAddress "192.168.1.2"
   $ip.ConvertToIPv6()
#>

function New-IPAddress {
    param(
        [Parameter(Position=0, Mandatory=$true)]
        [string]$ip
    )

    return [IP]::new($ip)
}

<#
 .Synopsis
  Returns the compressed IP

 .Description
  Returns the compressed IP

 .Parameter ip
  The IP Address in V4 or V6 Format

 .Parameter Soft
  Switch that does a softer compression (just useful for IPv6)

 .Example
   Compress-IPAdress "0000:0000:0000:0000:0000:0000:0000:FF00"

 .Example
   Compress-IPAdress "192.168.000.001"
#>

function Compress-IPAdress {
    param(
        [Parameter(Position=0, Mandatory=$true)]
        [string]$ip,
        [switch]$Soft
    )

    if($Soft) {
        return [IP]::new($ip).Normalize()
    }
    else {
        return [IP]::new($ip).Compress()
    }
}

<#
 .Synopsis
  Converts a IP to a new IP V6 Address Object

 .Description
  Converts a IP to a new IP V6 Address Object

 .Parameter ip
  The IP Address in V4 or V6 Format

 .Parameter SoftCompression
  Switch that does a softer compression (just useful for IPv6)

 .Example
   ConvertTo-IPv6Address "192.168.0.1"
#>

function ConvertTo-IPv6Address {
    param(
        [Parameter(Position=0, Mandatory=$true)]
        [string]$ip
    )

    return [IP]::new($ip).ConvertToIPv6()
}

<#
 .Synopsis
  Creates a new IP Subnet Object

 .Description
  Creates a new IP Subnet Object

 .Parameter subnet
  The IP Subnet in V4 or V6 Format

 .Example
   $snet = New-IPSubnet "192.168.1.0/24"
   $snet.ConvertToIPv6()

 .Example
   $snet = New-IPSubnet "::FFFF:0A0A:0A00/120"
   Write-Host ( "First IP is " + $snet.GetFirstIP() )
   Write-Host ( "Last IP is " + $snet.GetLastIP() )
   Write-Host ( "Address Space is " + $snet.GetAddressSpace() )

 .Example
   $snet = New-IPSubnet "192.168.1.0/255.255.255.0"
   $snet.ContainsIP("192.168.1.2")

 .Example
   $snet = New-IPSubnet "::FFFF:0A0A:0A00 / FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FF00"
   $snet.ContainsIP("::FFFF:0A0A:0A0A")
#>

function New-IPSubnet {
    param(
        [Parameter(Position=0, Mandatory=$true)]
        [string]$subnet
    )

    return [Subnet]::new($subnet)
}


<#
 .Synopsis
  Displays the information of an IP Subnet

 .Description
  Displays the information of an IP Subnet

 .Parameter subnet
  The IP Subnet in V4 or V6 Format

 .Example
   Get-IPSubnetInfo "192.168.1.0/24"

 .Example
   Get-IPSubnetInfo "::FFFF:0A0A:0A00/120"

 .Example
   Get-IPSubnetInfo "192.168.1.0/255.255.255.0"

 .Example
   Get-IPSubnetInfo "::FFFF:0A0A:0A00 / FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FF00"
#>

function Get-IPSubnetInfo {
    param(
        [Parameter(Position=0, Mandatory=$true)]
        [string]$subnet
    )

    $snet = [Subnet]::new($subnet)
    return @{
        "Subnet"  = ([string]$snet)
        "IPv4" = $snet.IsIPv4()
        "IPv6" = $snet.IsIPv6()
        "FirstIP" = $snet.GetFirstIP()
        "LastIP" = $snet.GetLastIP()
        "IPBitArray" = $snet.GetSubnetIP().GetBitMask()
        "IPBitString" = $snet.GetSubnetIP().GetBitMaskString()
        "MaskBitArray" = $snet.GetSubnetMask().GetBitMask()
        "MaskBitString" = $snet.GetSubnetMask().GetBitMaskString()
        "IPv6Representation" = ([string]$snet.ConvertToIPv6())
        "AddressSpace" = $snet.GetAddressSpace()
    }
}

<#
 .Synopsis
  Displays the information of an IP Address

 .Description
  Displays the information of an IP Address

 .Parameter ip
  The IP Address in V4 or V6 Format

 .Example
   Get-IPAddressInfo "192.168.1.2"

 .Example
   Get-IPAddressInfo "::FFFF:0A0A:0A0A"

#>

function Get-IPAddressInfo {
    param(
        [Parameter(Position=0, Mandatory=$true)]
        [string]$ip
    )

    $a = [IP]::new($ip)
    return @{
        "IP"  = $a.GetIP()
        "IPv4" = $a.IsIPv4()
        "IPv6" = $a.IsIPv6()
        "IPBitArray" = $a.GetBitMask()
        "IPBitString" = $a.GetBitMaskString()
        "IPv6Representation" = ([string]$a.ConvertToIPv6())
    }
}

<#
 .Synopsis
  Creates a new ip iterator for a Subnet

 .Description
  Creates a new ip iterator for a Subnet

 .Parameter subnet
  The IP Subnet in V4 or V6 Format

 .Parameter Skip
  Number of entries, that should be skipped

 .Parameter StartAtIp
  IP where to start the iteration

 .Example
   foreach($ip in (New-IPSubnetIterator "192.168.1.0/24")) {
      Write-Host " * $ip"
   }

 .Example
   $iter = New-IPSubnetIterator "192.168.1.0/24" -Skip 200
   while($iter.MoveNext()) {
       Write-Host $iter.Current
       $iter.Skip(1) | Out-Null # skip one in every iteration, so we will just get even IPs
   }

 .Example
   foreach($ip in (New-IPSubnetIterator "10.0.0.0/8" -StartAtIp "10.255.255.250")) {
    Write-Host " * $ip"
   }
#>

function New-IPSubnetIterator {
    param(
        [Parameter(Position=0, Mandatory=$true)]
        [string]$subnet,
        [int]$Skip = 0,
        [string]$StartAtIp = ""
    )

    $iter = [SubnetIPIterator]::new([Subnet]::new($subnet))
    if($StartAtIp.Trim() -ne "") {
        $iter.SetIP($StartAtIp) | Out-Null
    }
    if($Skip -gt 0) {
        $iter.Skip($Skip) | Out-Null
    }

    return $iter
}

<#
 .Synopsis
  Creates a new subnet iterator for a Subnet

 .Description
  Creates a new subnet iterator for a Subnet

 .Parameter subnet
  The IP Subnet in V4 or V6 Format

 .Parameter Skip
  Number of entries, that should be skipped

 .Example
   foreach($snet in (New-IPSubnetSliceIterator "192.168.1.0/24" 28)) {
      Write-Host " * $snet"
   }

 .Example
   # skip first 2
   $iter = New-IPSubnetSliceIterator "192.168.1.0/24" 28 -Skip 2
   while($iter.MoveNext()) {
       Write-Host $iter.Current
   }
#>

function New-IPSubnetSliceIterator {
    param(
        [Parameter(Position=0, Mandatory=$true)]
        [string]$subnet,
        [Parameter(Position=1, Mandatory=$true)]
        [int]$cidrbits,
        [int]$Skip = 0
    )

    $iter = [SubnetSubnetIterator]::new([Subnet]::new($subnet), $cidrbits)
    if($Skip -gt 0) {
        $iter.Skip($Skip) | Out-Null
    }

    return $iter
}

Export-ModuleMember -Function New-IPAddress, New-IPSubnet, Get-IPSubnetInfo, Get-IPAddressInfo, New-IPSubnetIterator, New-IPSubnetSliceIterator, Compress-IPAdress, ConvertTo-IPv6Address