The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
use strict;
use warnings;

use Test::More 0.88;

use List::AllUtils qw( each_array );
use Math::Int128 qw(uint128);
use Net::Works::Address;
use Net::Works::Network;

{
    _test_remove_reserved_subnets(
        [ '0.0.0.1', '1.2.3.4' ] => [ [ '1.0.0.0', '1.2.3.4' ] ] );

    _test_remove_reserved_subnets( [ '9.10.11.12', '9.255.255.255' ] =>
            [ [ '9.10.11.12', '9.255.255.255' ] ] );

    _test_remove_reserved_subnets(
        [ '9.0.0.0', '12.0.0.0' ] => [
            [ '9.0.0.0',  '9.255.255.255' ],
            [ '11.0.0.0', '12.0.0.0' ],
        ]
    );

    _test_remove_reserved_subnets(
        [ '9.0.0.0', '10.255.255.255' ] => [
            [ '9.0.0.0', '9.255.255.255' ],
        ]
    );

    _test_remove_reserved_subnets(
        [ '10.2.3.4', '12.255.255.255' ] => [
            [ '11.0.0.0', '12.255.255.255' ],
        ]
    );

    _test_remove_reserved_subnets( [ '10.0.0.0', '10.10.10.10' ] => [] );

    _test_remove_reserved_subnets(
        [ '0.0.0.0', '255.255.255.255' ] => [
            [ '1.0.0.0',      '9.255.255.255' ],
            [ '11.0.0.0',     '100.63.255.255' ],
            [ '100.128.0.0',  '126.255.255.255' ],
            [ '128.0.0.0',    '169.253.255.255' ],
            [ '169.255.0.0',  '172.15.255.255' ],
            [ '172.32.0.0',   '191.255.255.255' ],
            [ '192.0.0.8',    '192.0.1.255' ],
            [ '192.0.3.0',    '192.88.98.255' ],
            [ '192.88.100.0', '192.167.255.255' ],
            [ '192.169.0.0',  '198.17.255.255' ],
            [ '198.20.0.0',   '198.51.99.255' ],
            [ '198.51.101.0', '203.0.112.255' ],
            [ '203.0.114.0',  '223.255.255.255' ],
        ]
    );

    _test_remove_reserved_subnets(
        [ '::0', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ] => [
            map {
                [
                    map {
                        Net::Works::Address->new_from_string(
                            string  => $_,
                            version => 6
                            )->as_string()
                    } @{$_}
                ]
                } (
                [ '::1.0.0.0',      '::9.255.255.255' ],
                [ '::11.0.0.0',     '::100.63.255.255' ],
                [ '::100.128.0.0',  '::126.255.255.255' ],
                [ '::128.0.0.0',    '::169.253.255.255' ],
                [ '::169.255.0.0',  '::172.15.255.255' ],
                [ '::172.32.0.0',   '::191.255.255.255' ],
                [ '::192.0.0.8',    '::192.0.1.255' ],
                [ '::192.0.3.0',    '::192.88.98.255' ],
                [ '::192.88.100.0', '::192.167.255.255' ],
                [ '::192.169.0.0',  '::198.17.255.255' ],
                [ '::198.20.0.0',   '::198.51.99.255' ],
                [ '::198.51.101.0', '::203.0.112.255' ],
                [ '::203.0.114.0',  '::223.255.255.255' ],
                [
                    '::1:0:0',
                    'ff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
                ],
                [
                    '100:0000:0000:0001::',
                    '2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
                ],
                [
                    '2001:0200::',
                    '2001:db7:ffff:ffff:ffff:ffff:ffff:ffff',
                ],
                [
                    '2001:db9::',
                    'fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
                ],
                [
                    'fe00::',
                    'fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
                ],
                [
                    'fec0::',
                    'feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
                ],
                ),
        ],
    );
}

{
    my $start = '1.0.0.1';
    my $end   = '1.0.0.255';

    my @expect = (
        map { Net::Works::Network->new_from_string( string => $_ ) }
            qw(
            1.0.0.1/32
            1.0.0.2/31
            1.0.0.4/30
            1.0.0.8/29
            1.0.0.16/28
            1.0.0.32/27
            1.0.0.64/26
            1.0.0.128/25
            )
    );

    _test_range_as_subnets( $start, $end, \@expect );
}

{
    my $start = '10.0.0.0';
    my $end   = '12.0.0.0';

    my @expect = (
        map { Net::Works::Network->new_from_string( string => $_ ) }
            qw(
            11.0.0.0/8
            12.0.0.0/32
            )
    );

    _test_range_as_subnets( $start, $end, \@expect );
}

{
    my $start = '10.0.0.0';
    my $end   = '12.0.0.0';

    my @expect = (
        map { Net::Works::Network->new_from_string( string => $_ ) }
            qw(
            ::11.0.0.0/104
            ::12.0.0.0/128
            )
    );

    _test_range_as_subnets( $start, $end, \@expect, 6 );
}

{
    my $start = '0.0.0.1';
    my $end   = '19.255.255.255';

    my @expect = (
        map { Net::Works::Network->new_from_string( string => $_ ) }
            qw(
            1.0.0.0/8
            2.0.0.0/7
            4.0.0.0/6
            8.0.0.0/7
            11.0.0.0/8
            12.0.0.0/6
            16.0.0.0/6
            )
    );

    _test_range_as_subnets( $start, $end, \@expect );
}

{
    my $start = '::1';
    my $end   = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff';

    my @expect = (
        map {
            Net::Works::Network->new_from_string( string => $_, version => 6 )
            } qw(
            ::1.0.0.0/104
            ::2.0.0.0/103
            ::4.0.0.0/102
            ::8.0.0.0/103
            ::11.0.0.0/104
            ::12.0.0.0/102
            ::16.0.0.0/100
            ::32.0.0.0/99
            ::64.0.0.0/99
            ::96.0.0.0/102
            ::100.0.0.0/106
            ::100.128.0.0/105
            ::101.0.0.0/104
            ::102.0.0.0/103
            ::104.0.0.0/101
            ::112.0.0.0/101
            ::120.0.0.0/102
            ::124.0.0.0/103
            ::126.0.0.0/104
            ::128.0.0.0/99
            ::160.0.0.0/101
            ::168.0.0.0/104
            ::169.0.0.0/105
            ::169.128.0.0/106
            ::169.192.0.0/107
            ::169.224.0.0/108
            ::169.240.0.0/109
            ::169.248.0.0/110
            ::169.252.0.0/111
            ::169.255.0.0/112
            ::170.0.0.0/103
            ::172.0.0.0/108
            ::172.32.0.0/107
            ::172.64.0.0/106
            ::172.128.0.0/105
            ::173.0.0.0/104
            ::174.0.0.0/103
            ::176.0.0.0/100
            ::192.0.0.8/125
            ::192.0.0.16/124
            ::192.0.0.32/123
            ::192.0.0.64/122
            ::192.0.0.128/121
            ::192.0.1.0/120
            ::192.0.3.0/120
            ::192.0.4.0/118
            ::192.0.8.0/117
            ::192.0.16.0/116
            ::192.0.32.0/115
            ::192.0.64.0/114
            ::192.0.128.0/113
            ::192.1.0.0/112
            ::192.2.0.0/111
            ::192.4.0.0/110
            ::192.8.0.0/109
            ::192.16.0.0/108
            ::192.32.0.0/107
            ::192.64.0.0/108
            ::192.80.0.0/109
            ::192.88.0.0/114
            ::192.88.64.0/115
            ::192.88.96.0/119
            ::192.88.98.0/120
            ::192.88.100.0/118
            ::192.88.104.0/117
            ::192.88.112.0/116
            ::192.88.128.0/113
            ::192.89.0.0/112
            ::192.90.0.0/111
            ::192.92.0.0/110
            ::192.96.0.0/107
            ::192.128.0.0/107
            ::192.160.0.0/109
            ::192.169.0.0/112
            ::192.170.0.0/111
            ::192.172.0.0/110
            ::192.176.0.0/108
            ::192.192.0.0/106
            ::193.0.0.0/104
            ::194.0.0.0/103
            ::196.0.0.0/103
            ::198.0.0.0/108
            ::198.16.0.0/111
            ::198.20.0.0/110
            ::198.24.0.0/109
            ::198.32.0.0/108
            ::198.48.0.0/111
            ::198.50.0.0/112
            ::198.51.0.0/114
            ::198.51.64.0/115
            ::198.51.96.0/118
            ::198.51.101.0/120
            ::198.51.102.0/119
            ::198.51.104.0/117
            ::198.51.112.0/116
            ::198.51.128.0/113
            ::198.52.0.0/110
            ::198.56.0.0/109
            ::198.64.0.0/106
            ::198.128.0.0/105
            ::199.0.0.0/104
            ::200.0.0.0/103
            ::202.0.0.0/104
            ::203.0.0.0/114
            ::203.0.64.0/115
            ::203.0.96.0/116
            ::203.0.112.0/120
            ::203.0.114.0/119
            ::203.0.116.0/118
            ::203.0.120.0/117
            ::203.0.128.0/113
            ::203.1.0.0/112
            ::203.2.0.0/111
            ::203.4.0.0/110
            ::203.8.0.0/109
            ::203.16.0.0/108
            ::203.32.0.0/107
            ::203.64.0.0/106
            ::203.128.0.0/105
            ::204.0.0.0/102
            ::208.0.0.0/100
            ::1:0:0/96
            ::2:0:0/95
            ::4:0:0/94
            ::8:0:0/93
            ::10:0:0/92
            ::20:0:0/91
            ::40:0:0/90
            ::80:0:0/89
            ::100:0:0/88
            ::200:0:0/87
            ::400:0:0/86
            ::800:0:0/85
            ::1000:0:0/84
            ::2000:0:0/83
            ::4000:0:0/82
            ::8000:0:0/81
            ::1:0:0:0/80
            ::2:0:0:0/79
            ::4:0:0:0/78
            ::8:0:0:0/77
            ::10:0:0:0/76
            ::20:0:0:0/75
            ::40:0:0:0/74
            ::80:0:0:0/73
            ::100:0:0:0/72
            ::200:0:0:0/71
            ::400:0:0:0/70
            ::800:0:0:0/69
            ::1000:0:0:0/68
            ::2000:0:0:0/67
            ::4000:0:0:0/66
            ::8000:0:0:0/65
            0:0:0:1::/64
            0:0:0:2::/63
            0:0:0:4::/62
            0:0:0:8::/61
            0:0:0:10::/60
            0:0:0:20::/59
            0:0:0:40::/58
            0:0:0:80::/57
            0:0:0:100::/56
            0:0:0:200::/55
            0:0:0:400::/54
            0:0:0:800::/53
            0:0:0:1000::/52
            0:0:0:2000::/51
            0:0:0:4000::/50
            0:0:0:8000::/49
            0:0:1::/48
            0:0:2::/47
            0:0:4::/46
            0:0:8::/45
            0:0:10::/44
            0:0:20::/43
            0:0:40::/42
            0:0:80::/41
            0:0:100::/40
            0:0:200::/39
            0:0:400::/38
            0:0:800::/37
            0:0:1000::/36
            0:0:2000::/35
            0:0:4000::/34
            0:0:8000::/33
            0:1::/32
            0:2::/31
            0:4::/30
            0:8::/29
            0:10::/28
            0:20::/27
            0:40::/26
            0:80::/25
            0:100::/24
            0:200::/23
            0:400::/22
            0:800::/21
            0:1000::/20
            0:2000::/19
            0:4000::/18
            0:8000::/17
            1::/16
            2::/15
            4::/14
            8::/13
            10::/12
            20::/11
            40::/10
            80::/9
            100:0:0:1::/64
            100:0:0:2::/63
            100:0:0:4::/62
            100:0:0:8::/61
            100:0:0:10::/60
            100:0:0:20::/59
            100:0:0:40::/58
            100:0:0:80::/57
            100:0:0:100::/56
            100:0:0:200::/55
            100:0:0:400::/54
            100:0:0:800::/53
            100:0:0:1000::/52
            100:0:0:2000::/51
            100:0:0:4000::/50
            100:0:0:8000::/49
            100:0:1::/48
            100:0:2::/47
            100:0:4::/46
            100:0:8::/45
            100:0:10::/44
            100:0:20::/43
            100:0:40::/42
            100:0:80::/41
            100:0:100::/40
            100:0:200::/39
            100:0:400::/38
            100:0:800::/37
            100:0:1000::/36
            100:0:2000::/35
            100:0:4000::/34
            100:0:8000::/33
            100:1::/32
            100:2::/31
            100:4::/30
            100:8::/29
            100:10::/28
            100:20::/27
            100:40::/26
            100:80::/25
            100:100::/24
            100:200::/23
            100:400::/22
            100:800::/21
            100:1000::/20
            100:2000::/19
            100:4000::/18
            100:8000::/17
            101::/16
            102::/15
            104::/14
            108::/13
            110::/12
            120::/11
            140::/10
            180::/9
            200::/7
            400::/6
            800::/5
            1000::/4
            2000::/16
            2001:200::/23
            2001:400::/22
            2001:800::/22
            2001:c00::/24
            2001:d00::/25
            2001:d80::/27
            2001:da0::/28
            2001:db0::/29
            2001:db9::/32
            2001:dba::/31
            2001:dbc::/30
            2001:dc0::/26
            2001:e00::/23
            2001:1000::/20
            2001:2000::/19
            2001:4000::/18
            2001:8000::/17
            2002::/15
            2004::/14
            2008::/13
            2010::/12
            2020::/11
            2040::/10
            2080::/9
            2100::/8
            2200::/7
            2400::/6
            2800::/5
            3000::/4
            4000::/2
            8000::/2
            c000::/3
            e000::/4
            f000::/5
            f800::/6
            fe00::/9
            fec0::/10
            )
    );
    _test_range_as_subnets( $start, $end, \@expect );
}

done_testing();

sub _test_remove_reserved_subnets {
    my $range  = shift;
    my $expect = shift;

    my $version = $range->[0] =~ /:/ ? 6 : 4;

    # All these conversions are gross but normally this is handled by the
    # range_as_subnets method.
    my $first_ip = Net::Works::Address->new_from_string(
        string  => $range->[0],
        version => $version,
    )->as_integer();

    my $last_ip = Net::Works::Address->new_from_string(
        string  => $range->[1],
        version => $version
    )->as_integer();

    ## no critic (Subroutines::ProtectPrivateSubs)
    my @got = map {
        [
            map {
                Net::Works::Address->new_from_integer(
                    integer => $_,
                    version => $version
                    )
            } @{$_}
        ]
        } Net::Works::Network->_remove_reserved_subnets_from_range(
        $first_ip,
        $last_ip,
        $version,
        );

    is_deeply(
        \@got,
        $expect,
        "_remove_reserved_subnets_from_range returned expected result for $range->[0] - $range->[1]"
    );
}

sub _test_range_as_subnets {
    my $start          = shift;
    my $end            = shift;
    my $expect_subnets = shift;
    my $version        = shift;

    my @subnets
        = Net::Works::Network->range_as_subnets( $start, $end, $version );

    is(
        scalar @subnets,
        scalar @{$expect_subnets},
        'got the same number of subnets as we expect'
    );

    my $iter = each_array( @subnets, @{$expect_subnets} );

    while ( my ( $got, $expect ) = $iter->() ) {
        last unless $got && $expect;

        my $first_ip = eval { $expect->first()->as_ipv4_string() }
            // $expect->first()->as_string();

        is(
            $got->first()->as_string(),
            $expect->first()->as_string(),
            "subnet first matches expected first - split $start - $end ($first_ip)"
        );

        my $last_ip = eval { $expect->last()->as_ipv4_string() }
            // $expect->last()->as_string();

        is(
            $got->last()->as_string(),
            $expect->last()->as_string(),
            "subnet last matches expected last - split $start - $end ($last_ip)"
        );

        my $prefix_length = $expect->prefix_length();
        is(
            $got->prefix_length(),
            $prefix_length,
            "prefix length matches expected value - split $start - $end ($prefix_length)"
        );
    }
}