File Coverage

blib/lib/Net/CIDR/Set/IPv6.pm
Criterion Covered Total %
statement 74 79 93.6
branch 40 56 71.4
condition 14 23 60.8
subroutine 15 16 93.7
pod 0 4 0.0
total 143 178 80.3


line stmt bran cond sub pod time code
1             package Net::CIDR::Set::IPv6;
2              
3 8     8   71 use warnings;
  8         78  
  8         596  
4 8     8   53 use strict;
  8         14  
  8         188  
5 8     8   39 use Carp;
  8         12  
  8         679  
6              
7 8     8   52 use namespace::autoclean;
  8         11  
  8         59  
8              
9             # ABSTRACT: Encode / decode IPv6 addresses
10              
11             our $VERSION = '0.19';
12              
13 37     37 0 117 sub new { bless \my $x, shift }
14              
15             sub _pack_ipv4 {
16 4     4   13 my @nums = split /[.]/, shift, -1;
17 4 100       20 return unless @nums == 4;
18 1         3 for ( @nums ) {
19 1 50 33     15 return unless /^\d{1,3}$/ and !/^0\d{1,2}$/ and $_ < 256;
      33        
20             }
21 0         0 return pack "CC*", 0, @nums;
22             }
23              
24             sub _426 {
25 0     0   0 my @nums = split /[.]/, shift, -1;
26 0 0       0 return if grep $_ > 255, @nums;
27 0         0 return join( ":", unpack( 'H*', pack 'C*', @nums ) =~ /..../g );
28             }
29              
30             sub _pack {
31 116     116   184 my $ip = shift;
32 116 100       248 return pack( 'H*', '0' x 33 ) if $ip eq '::';
33 113 50 66     270 return if $ip =~ /^:/ and $ip !~ s/^::/:/;
34 113 50 66     627 return if $ip =~ /:$/ and $ip !~ s/::$/:/;
35 113         288 my @nums = split /:/, $ip, -1;
36 113 50       238 return unless @nums <= 8;
37 113         241 my ( $empty, $ipv4, $str ) = ( 0, '', '' );
38 113         177 for ( @nums ) {
39 473 50       773 return if $ipv4;
40 473 100       1181 $str .= "0" x ( 4 - length ) . $_, next if /^[a-fA-F\d]{1,4}$/;
41 103 50       188 do { return if $empty++ }, $str .= "X", next if $_ eq '';
  99 100       225  
42 4 50       12 next if $ipv4 = _pack_ipv4( $_ );
43 4         576 return;
44             }
45 109 50 33     197 return if $ipv4 and @nums > 6;
46 109 50       377 $str =~ s/X/"0" x (($ipv4 ? 25 : 33)-length($str))/e if $empty;
  99 100       333  
47 109         524 return pack( "H*", "00" . $str ) . $ipv4;
48             }
49              
50             sub _unpack {
51 45     45   469 return _compress_ipv6(
52             join( ":", unpack( "xH*", shift ) =~ /..../g ) );
53             }
54              
55             # Replace longest run of null blocks with a double colon
56             sub _compress_ipv6 {
57 45     45   71 my $ip = shift;
58 45 100       408 if ( my @runs = $ip =~ /((?:(?:^|:)(?:0000))+:?)/g ) {
59 38         67 my $max = $runs[0];
60 38         102 for ( @runs[ 1 .. $#runs ] ) {
61 2 50       27 $max = $_ if length( $max ) < length;
62             }
63 38         528 $ip =~ s/$max/::/;
64             }
65 45         166 $ip =~ s/:0{1,3}/:/g;
66 45         329 return $ip;
67             }
68              
69             sub _width2bits {
70 60     60   94 my ( $width, $size ) = @_;
71 60         263 return pack 'B*',
72             ( '1' x ( $width + 8 ) ) . ( '0' x ( $size - $width ) );
73             }
74              
75             sub _is_cidr {
76 33     33   109 my ( $lo, $hi ) = @_;
77 33         78 my $mask = ~( $lo ^ $hi );
78 33         108 my $bits = unpack 'B*', $mask;
79 33 100       94 return unless $hi eq ($lo | $hi);
80 27 100       171 return unless $bits =~ /^(1*)0*$/;
81 23         95 return length( $1 ) - 8;
82             }
83              
84             sub _encode {
85 95     95   149 my ( $self, $ip ) = @_;
86 95 100       430 if ( $ip =~ m{^(.+?)/(.+)$} ) {
    100          
87 64         134 my $mask = $2;
88 64 100       120 return unless my $addr = _pack( $1 );
89 60 50       134 return unless my $bits = _width2bits( $mask, 128 );
90 60         207 return ( $addr & $bits, Net::CIDR::Set::_inc( $addr | ~$bits ) );
91             }
92             elsif ( $ip =~ m{^(.+?)-(.+)$} ) {
93 26         81 my ( $from, $to ) = ( $1, $2 );
94 26 50       48 return unless my $lo = _pack( $from );
95 26 50       49 return unless my $hi = _pack( $to );
96 26         75 return ( $lo, Net::CIDR::Set::_inc( $hi ) );
97             }
98             else {
99 5         19 return $self->_encode( "$ip/128" );
100             }
101             }
102              
103             sub encode {
104 90     90 0 173 my ( $self, $ip ) = @_;
105 90 100       184 my @r = $self->_encode( $ip )
106             or croak "Can't decode $ip as an IPv6 address";
107 86         262 return @r;
108             }
109              
110             sub decode {
111 34     34 0 57 my $self = shift;
112 34         50 my $lo = shift;
113 34         96 my $hi = Net::CIDR::Set::_dec( shift );
114 34   100     120 my $generic = shift || 0;
115 34 50 66     166 if ( $generic < 1 && $lo eq $hi ) {
    100 100        
116             # Single address
117 0         0 return _unpack( $lo );
118             }
119             elsif ( $generic < 2 && defined( my $w = _is_cidr( $lo, $hi ) ) ) {
120             # Valid CIDR range
121 23         55 return join '/', _unpack( $lo ), $w;
122             }
123             else {
124             # General range
125 11         27 return join '-', _unpack( $lo ), _unpack( $hi );
126             }
127             }
128              
129 1     1 0 3 sub nbits { 128 }
130              
131             1;
132              
133             __END__