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   51 use warnings;
  9         12  
  9         403  
4 9     9   34 use strict;
  9         13  
  9         129  
5 9     9   27 use Carp;
  9         12  
  9         430  
6              
7 9     9   35 use namespace::autoclean;
  9         13  
  9         42  
8              
9             # ABSTRACT: Encode / decode IPv6 addresses
10              
11             our $VERSION = '0.21';
12              
13 41     41 0 92 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   128 my $ip = shift;
34 112 100       168 return pack( 'H*', '0' x 33 ) if $ip eq '::';
35 109 50 66     187 return if $ip =~ /\A:/ and $ip !~ s/\A::/:/;
36 109 50 66     415 return if $ip =~ /:\z/ and $ip !~ s/::\z/:/;
37 109         225 my @nums = split /:/, $ip, -1;
38 109 50       140 return unless @nums <= 8;
39 109         149 my ( $empty, $ipv4, $str ) = ( 0, '', '' );
40 109         125 for ( @nums ) {
41 469 50       547 return if $ipv4;
42 469 100       803 $str .= "0" x ( 4 - length ) . $_, next if /\A[A-Fa-f0-9]{1,4}\z/;
43 99 50       129 do { return if $empty++ }, $str .= "X", next if $_ eq '';
  99 50       160  
44 0 0       0 next if $ipv4 = _pack_ipv4( $_ );
45 0         0 return;
46             }
47 109 50 33     161 return if $ipv4 and @nums > 6;
48 109 50       231 $str =~ s/X/"0" x (($ipv4 ? 25 : 33)-length($str))/e if $empty;
  99 100       239  
49 109         388 return pack( "H*", "00" . $str ) . $ipv4;
50             }
51              
52             sub _unpack {
53 45     45   320 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   52 my $ip = shift;
60 45 100       259 if ( my @runs = $ip =~ /((?:(?:\A|:)(?:0000))+:?)/g ) {
61 38         41 my $max = $runs[0];
62 38         74 for ( @runs[ 1 .. $#runs ] ) {
63 2 50       6 $max = $_ if length( $max ) < length;
64             }
65 38         357 $ip =~ s/$max/::/;
66             }
67 45         147 $ip =~ s/:0{1,3}/:/g;
68 45         183 return $ip;
69             }
70              
71             sub _width2bits {
72 60     60   113 my ( $width, $size ) = @_;
73 60         203 return pack 'B*',
74             ( '1' x ( $width + 8 ) ) . ( '0' x ( $size - $width ) );
75             }
76              
77             sub _is_cidr {
78 33     33   42 my ( $lo, $hi ) = @_;
79 33         56 my $mask = ~( $lo ^ $hi );
80 33         61 my $bits = unpack 'B*', $mask;
81 33 100       61 return unless $hi eq ($lo | $hi);
82 27 100       104 return unless $bits =~ /\A(1*)0*\z/;
83 23         66 return length( $1 ) - 8;
84             }
85              
86             sub _encode {
87 96     96   129 my ( $self, $ip ) = @_;
88 96 100       390 if ( $ip =~ m{\A([0-9A-Fa-f:]+)/(0|[1-9][0-9]*)\z} ) {
    100          
    100          
89 60         101 my $mask = $2;
90 60 50       91 return unless my $addr = _pack( $1 );
91 60 50       103 return unless my $bits = _width2bits( $mask, 128 );
92 60         170 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         55 my ( $from, $to ) = ( $1, $2 );
96 26 50       31 return unless my $lo = _pack( $from );
97 26 50       33 return unless my $hi = _pack( $to );
98 26         42 return ( $lo, Net::CIDR::Set::_inc( $hi ) );
99             }
100             elsif ( $ip =~ m{\A[0-9A-Fa-f:]+\z} ) {
101 2         45 return $self->_encode( "$ip/128" );
102             }
103             else {
104 8         141 return;
105             }
106             }
107              
108             sub encode {
109 94     94 0 140 my ( $self, $ip ) = @_;
110 94 100       142 my @r = $self->_encode( $ip )
111             or croak "Can't decode $ip as an IPv6 address";
112 86         197 return @r;
113             }
114              
115             sub decode {
116 34     34 0 36 my $self = shift;
117 34         35 my $lo = shift;
118 34         64 my $hi = Net::CIDR::Set::_dec( shift );
119 34   100     74 my $generic = shift || 0;
120 34 50 66     117 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         28 return join '/', _unpack( $lo ), $w;
127             }
128             else {
129             # General range
130 11         15 return join '-', _unpack( $lo ), _unpack( $hi );
131             }
132             }
133              
134 1     1 0 2 sub nbits { 128 }
135              
136             1;
137              
138             __END__