File Coverage

blib/lib/Net/CIDR/Set/IPv6.pm
Criterion Covered Total %
statement 69 80 86.2
branch 36 58 62.0
condition 12 20 60.0
subroutine 14 16 87.5
pod 0 4 0.0
total 131 178 73.6


line stmt bran cond sub pod time code
1             package Net::CIDR::Set::IPv6;
2              
3 9     9   43 use warnings;
  9         14  
  9         397  
4 9     9   34 use strict;
  9         12  
  9         122  
5 9     9   28 use Carp;
  9         10  
  9         409  
6              
7 9     9   33 use namespace::autoclean;
  9         11  
  9         40  
8              
9             # ABSTRACT: Encode / decode IPv6 addresses
10              
11             our $VERSION = '0.22';
12              
13 41     41 0 82 sub new { bless \my $x, shift }
14              
15             my $DEC = "(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})";
16              
17             sub _pack_ipv4 {
18 0     0   0 my @nums = split /[.]/, shift, -1;
19 0 0       0 return unless @nums == 4;
20 0         0 for ( @nums ) {
21 0 0 0     0 return unless /\A${DEC}\z/ and !/\A0[1-9]+/;
22             }
23 0         0 return pack "CC*", 0, @nums;
24             }
25              
26             sub _426 {
27 0     0   0 my @nums = split /[.]/, shift, -1;
28 0 0       0 return if grep $_ > 255, @nums;
29 0         0 return join( ":", unpack( 'H*', pack 'C*', @nums ) =~ /..../g );
30             }
31              
32             sub _pack {
33 112     112   129 my $ip = shift;
34 112 100       169 return pack( 'H*', '0' x 33 ) if $ip eq '::';
35 109 50 66     193 return if $ip =~ /\A:/ and $ip !~ s/\A::/:/;
36 109 50 66     417 return if $ip =~ /:\z/ and $ip !~ s/::\z/:/;
37 109         210 my @nums = split /:/, $ip, -1;
38 109 50       154 return unless @nums <= 8;
39 109         153 my ( $empty, $ipv4, $str ) = ( 0, '', '' );
40 109         131 for ( @nums ) {
41 469 50       548 return if $ipv4;
42 469 100       828 $str .= "0" x ( 4 - length ) . $_, next if /\A[A-Fa-f0-9]{1,4}\z/;
43 99 50       142 do { return if $empty++ }, $str .= "X", next if $_ eq '';
  99 50       170  
44 0 0       0 next if $ipv4 = _pack_ipv4( $_ );
45 0         0 return;
46             }
47 109 50 33     155 return if $ipv4 and @nums > 6;
48 109 50       255 $str =~ s/X/"0" x (($ipv4 ? 25 : 33)-length($str))/e if $empty;
  99 100       229  
49 109         404 return pack( "H*", "00" . $str ) . $ipv4;
50             }
51              
52             sub _unpack {
53 45     45   345 return _compress_ipv6(
54             join( ":", unpack( "xH*", shift ) =~ /..../g ) );
55             }
56              
57             # Replace longest run of null blocks with a double colon
58             sub _compress_ipv6 {
59 45     45   49 my $ip = shift;
60 45 100       270 if ( my @runs = $ip =~ /((?:(?:\A|:)(?:0000))+:?)/g ) {
61 38         43 my $max = $runs[0];
62 38         68 for ( @runs[ 1 .. $#runs ] ) {
63 2 50       6 $max = $_ if length( $max ) < length;
64             }
65 38         378 $ip =~ s/$max/::/;
66             }
67 45         118 $ip =~ s/:0{1,3}/:/g;
68 45         218 return $ip;
69             }
70              
71             sub _width2bits {
72 60     60   84 my ( $width, $size ) = @_;
73 60         195 return pack 'B*',
74             ( '1' x ( $width + 8 ) ) . ( '0' x ( $size - $width ) );
75             }
76              
77             sub _is_cidr {
78 33     33   46 my ( $lo, $hi ) = @_;
79 33         63 my $mask = ~( $lo ^ $hi );
80 33         56 my $bits = unpack 'B*', $mask;
81 33 100       65 return unless $hi eq ($lo | $hi);
82 27 100       125 return unless $bits =~ /\A(1*)0*\z/;
83 23         80 return length( $1 ) - 8;
84             }
85              
86             sub _encode {
87 96     96   118 my ( $self, $ip ) = @_;
88 96 100       391 if ( $ip =~ m{\A([0-9A-Fa-f:]+)/(0|[1-9][0-9]*)\z} ) {
    100          
    100          
89 60         111 my $mask = $2;
90 60 50       92 return unless my $addr = _pack( $1 );
91 60 50       110 return unless my $bits = _width2bits( $mask, 128 );
92 60         162 return ( $addr & $bits, Net::CIDR::Set::_inc( $addr | ~$bits ) );
93             }
94             elsif ( $ip =~ m{\A([0-9A-Fa-f:]+)-([0-9A-Fa-f:]+)\z} ) {
95 26         57 my ( $from, $to ) = ( $1, $2 );
96 26 50       34 return unless my $lo = _pack( $from );
97 26 50       29 return unless my $hi = _pack( $to );
98 26         62 return ( $lo, Net::CIDR::Set::_inc( $hi ) );
99             }
100             elsif ( $ip =~ m{\A[0-9A-Fa-f:]+\z} ) {
101 2         7 return $self->_encode( "$ip/128" );
102             }
103             else {
104 8         144 return;
105             }
106             }
107              
108             sub encode {
109 94     94 0 137 my ( $self, $ip ) = @_;
110 94 100       146 my @r = $self->_encode( $ip )
111             or croak "Can't decode $ip as an IPv6 address";
112 86         214 return @r;
113             }
114              
115             sub decode {
116 34     34 0 34 my $self = shift;
117 34         43 my $lo = shift;
118 34         55 my $hi = Net::CIDR::Set::_dec( shift );
119 34   100     71 my $generic = shift || 0;
120 34 50 66     150 if ( $generic < 1 && $lo eq $hi ) {
    100 100        
121             # Single address
122 0         0 return _unpack( $lo );
123             }
124             elsif ( $generic < 2 && defined( my $w = _is_cidr( $lo, $hi ) ) ) {
125             # Valid CIDR range
126 23         35 return join '/', _unpack( $lo ), $w;
127             }
128             else {
129             # General range
130 11         18 return join '-', _unpack( $lo ), _unpack( $hi );
131             }
132             }
133              
134 1     1 0 2 sub nbits { 128 }
135              
136             1;
137              
138             __END__