File Coverage

blib/lib/Net/CIDR/Set/IPv6.pm
Criterion Covered Total %
statement 65 76 85.5
branch 33 56 58.9
condition 12 20 60.0
subroutine 14 15 93.3
pod 0 4 0.0
total 124 171 72.5


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