use strict; use warnings; use Math::BigInt; use Math::Int128 qw(uint128); use Test::Fatal; use Test::More 0.88; use Net::Works::Address; { my $ip = Net::Works::Address->new_from_string( string => '1.2.3.4' ); is( $ip->as_string(), '1.2.3.4', '->as_string returns string passed to constructor' ); my $next = $ip->next_ip(); isa_ok( $next, 'Net::Works::Address', 'return value of ->next_ip' ); is( $next->as_string(), '1.2.3.5', 'next ip after 1.2.3.4 is 1.2.3.5' ); my $prev = $ip->previous_ip(); isa_ok( $prev, 'Net::Works::Address', 'return value of ->previous_ip' ); is( $prev->as_string(), '1.2.3.3', 'previous ip before 1.2.3.4 is 1.2.3.3' ); is( "$ip", '1.2.3.4', 'stringification of address object works' ); cmp_ok( $ip, '<', $next, 'numeric overloading (<) on address objects works' ); cmp_ok( $next, '>', $ip, 'numeric overloading (>) on address objects works' ); my $same_ip = Net::Works::Address->new_from_string( string => '1.2.3.4' ); cmp_ok( $ip, '==', $same_ip, 'numeric overloading (==) on address objects works' ); is( $ip <=> $same_ip, 0, 'comparison overloading (==) on address objects works' ); } { my @ips = map { Net::Works::Address->new_from_string( string => $_ ) } qw( ::123.0.0.4 ::1.2.3.4 2003:: ::255.255.255.255 abcd::1000 ::127.0.98.25 ::127.0.98.24 ); my @sorted = qw( ::1.2.3.4 ::123.0.0.4 ::127.0.98.24 ::127.0.98.25 ::255.255.255.255 2003:: abcd::1000 ); is_deeply( [ map { $_->as_string() } sort { $a <=> $b } @ips ], \@sorted, 'address objects sort numerically' ); is_deeply( [ map { $_->as_string() } sort { $a cmp $b } @ips ], \@sorted, 'address objects sort alphabetically' ); } { my $ip = Net::Works::Address->new_from_string( string => '192.168.0.255' ) ->next_ip(); is( $ip->as_string(), '192.168.1.0', '->next_ip wraps to the next ip address' ); } { my $ip = Net::Works::Address->new_from_string( string => 'ffff::a:1234' ); is( $ip->as_string(), 'ffff::a:1234', '->as_string returns string passed to constructor' ); my $prev = $ip->previous_ip(); is( $prev->as_string(), 'ffff::a:1233', 'previous ip before ffff::a:1234 is ffff::a:1233' ); my $next = $ip->next_ip(); is( $next->as_string(), 'ffff::a:1235', 'next ip after ffff::a:1234 is ffff::a:1235' ); } { my $ip = Net::Works::Address->new_from_string( string => 'ffff::0000:000a:1234' ); is( $ip->as_string(), 'ffff::a:1234', '->as_string returns compact form of IPv6' ); } { for my $address ( qw( 255.255.255.255 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff )) { my $ip = Net::Works::Address->new_from_string( string => $address ); like( exception { $ip->next_ip() }, qr/\Q$address is the last address in its range/, 'cannot call ->next_ip on the last address in a range' ); } } { for my $address (qw( 0.0.0.0 ::0 )) { my $ip = Net::Works::Address->new_from_string( string => $address ); like( exception { $ip->previous_ip() }, qr/\Q$address is the first address in its range/, 'cannot call ->previous_ip on the first address in a range' ); } } { my $ip = Net::Works::Address->new_from_integer( integer => 0, version => 4, ); is( $ip->as_string(), '0.0.0.0', 'new_from_integer(0), IPv4' ); is( $ip->as_integer(), 0, 'as_integer returns 0' ); is( $ip->as_bit_string(), '0' x 32, 'as_bit_string returns 0x32' ); $ip = Net::Works::Address->new_from_integer( integer => 2**32 - 1, version => 4, ); is( $ip->as_string(), '255.255.255.255', 'new_from_integer(2**32 - 1), IPv4' ); is( $ip->as_integer(), 2**32 - 1, 'as_integer returns 2**32 - 1' ); is( $ip->as_bit_string(), '1' x 32, 'as_bit_string returns 1x32' ); $ip = Net::Works::Address->new_from_integer( integer => 0, version => 6, ); is( $ip->as_string(), '::0', 'new_from_integer(0), IPv6' ); is( $ip->as_integer(), 0, 'as_integer returns 0, IPv6' ); is( $ip->as_bit_string(), '0' x 128, 'as_bit_string returns 0x128' ); $ip = Net::Works::Address->new_from_integer( integer => 2**32 - 1, version => 6, ); is( $ip->as_string(), '::255.255.255.255', 'new_from_integer(2**32 - 1), IPv6' ); is( $ip->as_bit_string(), ( '0' x 96 ) . ( '1' x 32 ), 'as_bit_string returns 0x96 . 1x32' ); is( $ip->as_integer(), 2**32 - 1, 'as_integer returns 2**32 - 1, IPv6' ); } for my $one ( uint128(1), Math::BigInt->bone() ) { subtest 'using ' . ref($one) . ' integer' => sub { my $max_128 = ( $one * 2 )**128 - $one; my $ip = Net::Works::Address->new_from_integer( integer => $max_128, version => 6, ); is( $ip->as_string(), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'new_from_integer(2**128 - 1), IPv6' ); is( $ip->as_integer(), $max_128, 'as_integer returns 2**128 - 1, IPv6' ); is( $ip->as_bit_string(), '1' x 128, 'as_bit_string returns 1x128' ); $ip = Net::Works::Address->new_from_integer( integer => $one, version => 4, ); is( $ip->as_string(), '0.0.0.1', 'as_string returns 0.0.0.1' ); }; } { my $e = exception { Net::Works::Address->new_from_integer( integer => uint128(2)**33, version => 4 ); }; like( $e, qr/\d+ \Qis not a valid integer for an IP address/, 'new_from_integer blows up when giving a value larger than 2**32 -1 with version => 4' ); } { my %tests = ( '::0' => '0.0.0.0', '::2' => '0.0.0.2', '::ffff' => '0.0.255.255', '::ffff:ffff' => '255.255.255.255', ); for my $raw ( sort keys %tests ) { my $ip = Net::Works::Address->new_from_string( string => $raw, version => 6, ); is( $ip->as_ipv4_string(), $tests{$raw}, "$raw as IPv4 is $tests{$raw}" ); } } { my $ip = Net::Works::Address->new_from_string( string => '::1:ffff:ffff', version => 6, ); like( exception { $ip->as_ipv4_string() }, qr/\QCannot represent IP address larger than 2**32-1 as an IPv4 string/, 'cannot represent an IPv6 address >= 2**32 as an IPv4 string' ); } done_testing();