File Coverage

lib/Net/IP/RangeCompare.pm
Criterion Covered Total %
statement 304 310 98.0
branch 117 144 81.2
condition 13 15 86.6
subroutine 68 70 97.1
pod 37 41 90.2
total 539 580 92.9


line stmt bran cond sub pod time code
1              
2             package Net::IP::RangeCompare;
3 12     12   539560 use strict;
  12         30  
  12         454  
4 12     12   3928 use Data::Dumper;
  12         46198  
  12         682  
5 12     12   72 use warnings;
  12         28  
  12         423  
6 12     12   62 use Carp qw(croak);
  12         21  
  12         1193  
7 12     12   67 use Scalar::Util qw(looks_like_number blessed);
  12         22  
  12         1694  
8 12     12   65 use vars qw($error $VERSION @ISA @EXPORT_OK %EXPORT_TAGS %HELPER);
  12         28  
  12         1819  
9 12         2258 use Data::Range::Compare qw(HELPER_CB
10             key_helper
11             key_start
12             key_end
13             key_generated
14             key_missing
15             key_data
16             add_one
17             sub_one
18             cmp_values
19             sort_largest_range_end_first
20             sort_largest_range_start_first
21             sort_smallest_range_start_first
22             sort_smallest_range_end_first
23             sort_in_consolidate_order
24             sort_in_presentation_order
25 12     12   13518 );
  12         47553  
26 12         2340 use Data::IPV4::Range::Parse qw(
27              
28             ALL_BITS
29             MAX_CIDR
30             MIN_CIDR
31             int_to_ip
32             ip_to_int
33             sort_quad
34             sort_notations
35             broadcast_int
36             base_int
37             size_from_mask
38             hostmask
39             cidr_to_int
40             parse_ipv4_cidr
41             parse_ipv4_range
42             parse_ipv4_ip
43             auto_parse_ipv4_range
44              
45 12     12   13180 );
  12         26986  
46              
47 12     12   92 use constant key_start_ip =>key_start;
  12         23  
  12         790  
48 12     12   176 use constant key_end_ip =>key_end;
  12         27  
  12         3411  
49              
50             %HELPER=HELPER_CB;
51              
52             $VERSION=4.025;
53             use overload
54 12         129 '""' => \¬ation
55 12     12   68 ,'fallback' => 1;
  12         21  
56              
57              
58             require Exporter;
59             @ISA=qw(Exporter Data::Range::Compare );
60              
61             @EXPORT_OK=qw(
62             hostmask
63             cidr_to_int
64             ip_to_int
65             int_to_ip
66             size_from_mask
67             base_int
68             broadcast_int
69             cmp_int
70             sort_quad
71             sort_notations
72             add_one
73             sub_one
74              
75             sort_ranges
76             sort_largest_first_int_first
77             sort_smallest_last_int_first
78             sort_largest_last_int_first
79             sort_smallest_first_int_first
80              
81             get_overlapping_range
82             get_common_range
83             grep_overlap
84             grep_non_overlap
85              
86             consolidate_ranges
87             range_start_end_fill
88             fill_missing_ranges
89             range_compare
90             compare_row
91             range_compare_force_cidr
92             );
93              
94             %EXPORT_TAGS = (
95             ALL=>\@EXPORT_OK
96             ,HELPER=>[qw(
97             hostmask
98             cidr_to_int
99             ip_to_int
100             int_to_ip
101             sort_quad
102             size_from_mask
103             base_int
104             broadcast_int
105             cmp_int
106             sort_notations
107             add_one
108             sub_one
109             )]
110             ,SORT=>[qw(
111             sort_ranges
112             sort_largest_first_int_first
113             sort_smallest_last_int_first
114             sort_largest_last_int_first
115             sort_smallest_first_int_first
116             )]
117             ,OVERLAP=>[qw(
118             get_overlapping_range
119             get_common_range
120             grep_overlap
121             grep_non_overlap
122             )]
123             ,PROCESS=>[qw(
124             consolidate_ranges
125             range_start_end_fill
126             fill_missing_ranges
127             range_compare
128             compare_row
129             range_compare_force_cidr
130             )]
131             );
132              
133             ## compatibilty stuffs
134             *sort_ranges=\&sort_in_consolidate_order;
135             *sort_largest_first_int_first=\&sort_largest_range_start_first;
136             *sort_smallest_last_int_first=\&sort_smallest_range_end_first;
137             *sort_largest_last_int_first =\&sort_largest_range_end_first;
138             *sort_smallest_first_int_first=\&sort_smallest_range_start_first;
139              
140             sub new {
141 228     228 1 20150 my ($s,$start,$end,@args)=@_;
142 228 100       633 return undef unless looks_like_number $start;
143 227 100       489 return undef unless looks_like_number $end;
144 226 100       440 return undef if $start>$end;
145 223         863 $s->SUPER::new(\%HELPER,$start,$end,@args);
146             }
147              
148             sub parse_new_range {
149 88     88 1 5565 my ($s,@args)=@_;
150 88         101 my $string;
151 88 100       185 if($#args >0) {
152 1         5 $string=join ' - ',@args;
153             } else {
154 87 100       193 return undef unless defined($args[0]);
155 86         95 $string=$args[0];
156 86         194 my $class=blessed($string);
157 86 100       201 if(ref($string)) {
158 47 100 66     257 return $string if $class and $class eq 'Net::IP::RangeCompare';
159 1 50       4 if($class) {
160 0         0 $string .='';
161 0 0       0 return undef unless $string=~ m/
162             ^
163             \d(\.\d{1,3}){0,3}
164             \s*[\/-]\s*
165             \d(\.\d{1,3}){0,3}
166             $
167             /x;
168             } else {
169 1         4 return undef;
170             }
171             }
172             }
173 40         122 my ($start,$end)=auto_parse_ipv4_range($string);
174 40 100       1783 return undef unless looks_like_number($end);
175 39         109 $s->new($start,$end);
176             }
177             *new_from_ip=\&parse_new_range;
178             *new_from_range=\&parse_new_range;
179             *new_from_cidr=\&parse_new_range;
180              
181             ##########################################################
182             #
183             # OO Stubs
184              
185 2108     2108 1 7146 sub first_int () { $_[0]->[key_start_ip] }
186 1160     1160 1 4827 sub last_int () { $_[0]->[key_end_ip] }
187 6     6 1 1120 sub first_ip () { int_to_ip($_[0]->[key_start_ip]) }
188 6     6 1 1045 sub last_ip () { int_to_ip($_[0]->[key_end_ip]) }
189 157     157 0 16608 sub missing () {$_[0]->[key_missing] }
190 2     2 0 107 sub generated () {$_[0]->[key_generated] }
191 0     0 1 0 sub error () { $error }
192 4     4 1 43 sub size () { 1 + $_[0]->last_int - $_[0]->first_int }
193              
194             sub data () {
195 2     2 1 628 my ($s)=@_;
196             # always return the data ref if it exists
197 2 100       8 return $s->[key_data] if ref($s->[key_data]);
198 1         3 $s->[key_data]={};
199 1         3 $s->[key_data]
200             }
201              
202             sub notation {
203 298     298 1 6710 join ' - '
204             ,int_to_ip($_[0]->first_int)
205             ,int_to_ip($_[0]->last_int)
206             }
207              
208             sub get_cidr_notation () {
209 2     2 1 18 my ($s)=@_;
210 2         4 my $n=$s;
211 2         3 my $return_ref=[];
212 2         3 my ($range,$cidr);
213 2         6 while($n) {
214 3         29 ($range,$cidr,$n)=$n->get_first_cidr;
215 3         19 push @$return_ref,$cidr;
216             }
217 2         12 join(', ',@$return_ref);
218             }
219              
220             sub overlap ($) {
221 45     45 1 320 my ($range_a,$range_b)=@_;
222 45         140 my $class=blessed $range_a;
223 45         100 $range_b=$class->parse_new_range($range_b);
224              
225             # return true if range_b's start range is contained by range_a
226 45 100 100     91 return 1 if
227             $range_a->cmp_first_int($range_b)!=1
228             &&
229             $range_a->cmp_last_int($range_b)!=-1;
230              
231             # return true if range_b's end range is contained by range_a
232 36 50 66     207 return 1 if
233             #$range_a->first_int <=$range_b->last_int
234             cmp_int($range_a->first_int,$range_b->last_int )!=1
235             &&
236             #$range_a->last_int >=$range_b->last_int;
237             cmp_int($range_a->last_int,$range_b->last_int)!=-1;
238              
239 36 100 100     187 return 1 if
240             #$range_b->first_int <=$range_a->first_int
241             $range_b->cmp_first_int($range_a)!=1
242             &&
243             #$range_b->last_int >=$range_a->first_int;
244             $range_b->cmp_last_int($range_a)!=-1;
245              
246             # return true if range_b's end range is contained by range_a
247 34 100 100     174 return 1 if
248             #$range_b->first_int <=$range_a->last_int
249             cmp_int($range_b->first_int,$range_a->last_int )!=1
250             &&
251             #$range_b->last_int >=$range_a->last_int;
252             cmp_int($range_b->last_int,$range_a->last_int)!=-1;
253              
254             # return undef by default
255             undef
256 30         157 }
257              
258 26     26 1 75 sub next_first_int () { add_one($_[0]->last_int) }
259 1     1 1 378 sub previous_last_int () { sub_one($_[0]->first_int) }
260             sub get_first_cidr () {
261 32     32 1 487 my ($s)=@_;
262 32         90 my $class=blessed $s;
263 32         39 my $first_cidr;
264             my $output_cidr;
265 32         92 for(my $cidr=MAX_CIDR;$cidr>-1;--$cidr) {
266 1017         2386 $output_cidr=MAX_CIDR - $cidr;
267 1017         2031 my $mask=cidr_to_int($output_cidr);
268              
269 1017         10473 my $hostmask=hostmask($mask);
270 1017         4440 my $size=size_from_mask($mask);
271              
272 1017 100       6071 next if $s->mod_first_int($size);
273              
274              
275 432         660 my $last_int=$s->first_int + $hostmask;
276 432 100       685 next if cmp_int($last_int,$s->last_int)==1;
277              
278 32         135 $first_cidr=$class->new($s->first_int,$last_int);
279              
280 32         211 last;
281             }
282 32         61 my $cidr_string=join('/',int_to_ip($first_cidr->first_int),$output_cidr);
283              
284 32 100       333 if($first_cidr->cmp_last_int($s)==0) {
285 16         104 return ( $first_cidr ,$cidr_string);
286             } else {
287             return (
288 16         92 $first_cidr
289             ,$cidr_string
290             ,$class->new(
291             $first_cidr->next_first_int
292             ,$s->last_int
293             )
294             );
295             }
296              
297             }
298              
299             sub is_cidr () {
300 1     1 0 10 my ($s)=@_;
301 1         3 my ($range,$cidr,$next)=$s->get_first_cidr;
302 1 50       9 my $is_cidr=defined($next) ? 0 : 1;
303 1         49 $is_cidr
304             }
305              
306             sub is_range () {
307 1     1 0 2 my ($s)=@_;
308 1         2 my ($range,$cidr,$next)=$s->get_first_cidr;
309 1 50       11 my $is_range=defined($next) ? 1 : 0;
310 1         7 $is_range
311             }
312              
313             sub nth ($) {
314 3     3 1 682 my ($s,$offset)=@_;
315 3         9 my $int=$s->first_int + $offset;
316 3 100       7 return undef if cmp_int($int,$s->last_int)==1;
317 2         13 int_to_ip($int);
318             }
319              
320             sub _internal_ip_list_func ($) {
321 6     6   12 my ($s,$mode)=@_;
322 6         6 my $next=$s;
323 6         7 my @list;
324             my $ip;
325 0         0 my $cidr;
326 6         207 while($next) {
327 15         125 ($ip,$cidr,$next)=$next->get_first_cidr;
328 15 100       144 if($mode eq 'first_int') {
    100          
    100          
    100          
    100          
    50          
329 3         7 push @list,$ip->first_int;
330             } elsif($mode eq 'first_ip') {
331 3         6 push @list,$ip->first_ip;
332             } elsif($mode eq 'last_ip') {
333 3         7 push @list,$ip->last_ip;
334             } elsif($mode eq 'last_int') {
335 3         5 push @list,$ip->last_int;
336             } elsif($mode eq 'netmask_int') {
337 1         8 my ($cidr_int)=($cidr=~ /(\d+)$/);
338 1         4 push @list,cidr_to_int($cidr_int);
339             } elsif($mode eq 'netmask') {
340 2         12 my ($cidr_int)=($cidr=~ /(\d+)$/);
341 2         7 push @list,int_to_ip(cidr_to_int($cidr_int));
342             }
343             }
344 6         87 @list;
345             }
346              
347 1     1 1 16 sub netmask_int_list { $_[0]->_internal_ip_list_func('netmask_int') }
348 1     1 1 14 sub netmask_list { $_[0]->_internal_ip_list_func('netmask') }
349 1     1 1 20 sub base_list_int () { $_[0]->_internal_ip_list_func('first_int') }
350 1     1 1 906 sub base_list_ip () { $_[0]->_internal_ip_list_func('first_ip') }
351 1     1 1 958 sub broadcast_list_int () { $_[0]->_internal_ip_list_func('last_int') }
352 1     1 1 850 sub broadcast_list_ip () { $_[0]->_internal_ip_list_func('last_ip') }
353              
354             sub enumerate {
355 3     3 1 27 my ($s,$cidr)=@_;
356 3 100       7 $cidr=MAX_CIDR unless $cidr;
357 3         7 my $mask=cidr_to_int($cidr);
358 3         34 my $hostmask=hostmask($mask);
359 3         10 my $n=$s;
360 3         10 my $class=blessed $s;
361             sub {
362 9 100   9   2163 return undef unless $n;
363             #my $cidr_end=($n->first_int & $mask) + $hostmask;
364 6         83 my $cidr_end=broadcast_int($n->first_int , $mask);
365 6         46 my $return_ref;
366 6 100       12 if(cmp_int($cidr_end,$n->last_int)!=-1) {
367 3         10 $return_ref=$n;
368 3         4 $n=undef;
369             } else {
370 3         14 $return_ref=$class->new(
371             $n->first_int
372             ,$cidr_end
373             );
374 3         20 $n=$class->new(
375             $return_ref->next_first_int
376             ,$n->last_int
377             );
378             }
379 6         29 $return_ref;
380             }
381 3         16 }
382              
383             sub enumerate_size {
384 2     2 1 16 my ($s,$inc)=@_;
385 2         9 my $class=blessed $s;
386 2 100       7 $inc=1 unless $inc;
387 2         3 my $done;
388             sub {
389 8 100   8   2033 return undef if $done;
390 6         15 my $first=$s->first_int;
391 6         10 my $next=$first + $inc;
392 6         6 my $last;
393 6 100       14 if(cmp_int($s->last_int,$next)!=-1) {
394 4         18 $last=$next;
395             } else {
396 2         43 $last=$s->last_int;
397             }
398 6         17 my $new_range=$class->new($first,$last);
399 6 100       45 $done=1 if $s->cmp_last_int($new_range)==0;
400 6         31 $s=$class->new($new_range->next_first_int,$s->last_int);
401 6         43 $new_range;
402             }
403 2         14 }
404              
405             sub cmp_first_int($) {
406 84     84 1 841 my ($s,$cmp)=@_;
407 84         142 cmp_int($s->first_int,$cmp->first_int)
408             }
409              
410             sub cmp_last_int($) {
411 86     86 1 1342 my ($s,$cmp)=@_;
412 86         161 cmp_int($s->last_int,$cmp->last_int)
413             }
414              
415 1017     1017 1 1667 sub mod_first_int ($) { $_[0]->first_int % $_[1] }
416              
417             *cmp_int=\&cmp_values;
418              
419             sub get_common_range {
420 30 50   30 1 260 shift if $_[0] eq 'Net::IP::RangeCompare';
421 30 50       83 shift if $_[0] eq \%HELPER;
422 30         39 my $ranges=shift;
423 30 50       64 croak 'empty range reference' if $#$ranges==-1;
424 30         121 my $range=Net::IP::RangeCompare->SUPER::get_common_range(\%HELPER,$ranges);
425 30 100       1565 return undef if cmp_values($range->first_int,$range->last_int)==1;
426 29         176 $range;
427             }
428              
429             sub grep_non_overlap {
430 1     1 1 291 my ($range,$list)=@_;
431 1         4 my $result=[];
432 1         7 my $cmp=Net::IP::RangeCompare->parse_new_range($range);
433 1 50       11 return $result unless defined($cmp);
434 3         20 @$result=(grep {
435 1         3 my $range=Net::IP::RangeCompare->parse_new_range($_);
436 3 50       25 defined($range) ? !$cmp->overlap($range) : 0
437             } @$list);
438 1         4 $result;
439             }
440             sub grep_overlap {
441 2     2 1 478 my ($range,$list)=@_;
442 2         5 my $result=[];
443 2         13 my $cmp=Net::IP::RangeCompare->parse_new_range($range);
444 2 50       171 return $result unless defined($cmp);
445 4         36 @$result=(grep {
446 2         5 my $range=Net::IP::RangeCompare->parse_new_range($_);
447 4 50       40 defined($range) ? $cmp->overlap($range) : 0
448             } @$list);
449 2         13 $result;
450             }
451              
452             sub get_overlapping_range {
453 5 100   5 1 110 shift if $_[0] eq 'Net::IP::RangeCompare';
454 5 100       28 shift if $_[0] eq \%HELPER;
455 5         55 Net::IP::RangeCompare->SUPER::get_overlapping_range(\%HELPER,@_);
456             }
457              
458             sub consolidate_ranges {
459 22 100   22 1 382 shift if $_[0] eq 'Net::IP::RangeCompare';
460 22 100       64 shift if $_[0] eq \%HELPER;
461 22         91 Net::IP::RangeCompare->SUPER::consolidate_ranges(\%HELPER,@_)
462             }
463              
464             sub fill_missing_ranges {
465 2 50   2 1 24 shift if $_[0] eq 'Net::IP::RangeCompare';
466 2 50       6 shift if $_[0] eq \%HELPER;
467 2         15 Net::IP::RangeCompare->SUPER::fill_missing_ranges(\%HELPER,@_);
468             }
469              
470             sub range_start_end_fill {
471 1 50   1 1 17 shift if $_[0] eq 'Net::IP::RangeCompare';
472 1 50       5 shift if $_[0] eq \%HELPER;
473 1         11 Net::IP::RangeCompare->SUPER::range_start_end_fill(\%HELPER,@_);
474             }
475              
476             sub range_compare {
477 6 50   6 1 723 shift if $_[0] eq 'Net::IP::RangeCompare';
478 6 50       19 shift if $_[0] eq \%HELPER;
479 6         36 my $sub=Net::IP::RangeCompare->SUPER::range_compare(\%HELPER,@_) ;
480             sub {
481 30     30   2756 my @row=$sub->();
482 30 100       875 return () unless @row;
483 24         52 return (get_common_range(\@row),@row);
484             }
485 6         110 }
486              
487             sub range_compare_force_cidr {
488 1     1 1 15 my $sub=range_compare(@_);
489              
490 1         3 my ($common,@row)=$sub->();
491 1         5 my ($cidr,$notation,$next)=$common->get_first_cidr;
492             sub {
493 7 100   7   47 return () unless @row;
494 6         14 my @return_row=($cidr,$notation,@row);
495 6 100       13 if($next) {
496 1         18 ($cidr,$notation,$next)=$next->get_first_cidr;
497             } else {
498 5         7 ($common,@row)=$sub->();
499 5 100       16 if(@row) {
500 4         8 ($cidr,$notation,$next)=$common->get_first_cidr
501             } else {
502 1         2 $next=undef;
503             }
504             }
505             @return_row
506 6         35 }
507 1         20 }
508              
509             sub compare_row {
510 50 100   50 1 4288 shift if $_[0] eq 'Net::IP::RangeCompare';
511 50 100       146 shift if $_[0] eq \%HELPER;
512 50         77 my ($ref,$row, $cols)=@_;
513 50 100       132 return Net::IP::RangeCompare->SUPER::init_compare_row(\%HELPER,$ref)
514             unless defined($cols);
515 46         185 Net::IP::RangeCompare->SUPER::compare_row(\%HELPER,$ref,$row, $cols);
516             }
517              
518             =pod
519              
520             =back
521              
522             =cut
523              
524             ############################################
525             #
526             # End of the package
527             1;
528              
529             ############################################
530             #
531             # Helper package
532             package Net::IP::RangeCompare::Simple;
533              
534 12     12   53498 use strict;
  12         39  
  12         485  
535 12     12   67 use warnings;
  12         20  
  12         1203  
536 12     12   150 use Carp qw(croak);
  12         22  
  12         1007  
537 12     12   63 use constant key_sources=>0;
  12         24  
  12         834  
538 12     12   60 use constant key_columns=>1;
  12         24  
  12         875  
539 12     12   67 use constant key_compare=>2;
  12         30  
  12         651  
540 12     12   66 use constant key_changed=>3;
  12         22  
  12         14076  
541              
542             sub new {
543 1     1   347 my ($class)=@_;
544 1         2 my $ref=[];
545 1         3 $ref->[key_sources]={};
546 1         2 $ref->[key_changed]={};
547 1         3 $ref->[key_columns]=[];
548 1         3 $ref->[key_compare]=undef;
549              
550 1         4 bless $ref,$class;
551             }
552              
553              
554             sub add_range ($$) {
555 7     7   356 my ($s,$key,$range)=@_;
556 7 50       18 croak "Key is not defined" unless defined($key);
557 7 50       12 croak "Range is not defined" unless defined($range);
558              
559 7         16 my $obj=Net::IP::RangeCompare->parse_new_range($range);
560 7 50       50 croak "Could not parse: $range" unless $obj;
561              
562 7         57 my $list;
563              
564 7 100       17 if(exists $s->[key_sources]->{$key}) {
565 4         9 $list=$s->[key_sources]->{$key};
566             } else {
567 3         8 $s->[key_sources]->{$key}=[];
568 3         4 $list=$s->[key_sources]->{$key};
569             }
570 7         10 push @$list,$obj;
571 7         10 $s->[key_changed]->{$key}=1;
572 7         34 $obj
573             }
574              
575             sub get_ranges_by_key ($) {
576 2     2   372 my ($s,$key)=@_;
577 2 50       7 croak "key was not defined" unless defined($key);
578              
579 2 100       8 return [@{$s->[key_sources]->{$key}}]
  1         4  
580             if exists $s->[key_sources]->{$key};
581            
582 1         5 return undef;
583             }
584              
585             sub compare_ranges {
586 2     2   505 my ($s,@keys)=@_;
587 2         7 my %exclude=map { ($_,1) } @keys;
  1         4  
588 2 50       4 croak "no ranges defined" unless keys %{$s->[key_sources]};
  2         7  
589            
590 2         3 my $columns=$s->[key_columns];
591 2         5 @$columns=();
592 2         3 my $compare_ref=[];
593 2         3 while(my ($key,$ranges)=each %{$s->[key_sources]}) {
  8         22  
594 6 100       18 next if exists $exclude{$key};
595 5         6 push @$columns,$key;
596 5 100       15 $s->[key_sources]->{$key}=Net::IP::RangeCompare::consolidate_ranges($ranges)
597             if $s->[key_changed]->{$key};
598 5         33 $s->[key_changed]->{$key}=0;
599 5         10 push @$compare_ref,$s->[key_sources]->{$key};
600              
601             }
602 2 50       6 croak "no ranges defined" if $#$columns==-1;
603              
604 2         7 $s->[key_compare]=Net::IP::RangeCompare::range_compare(
605             $compare_ref
606             ,consolidate_ranges=>0
607             );
608              
609 2         10 1
610             }
611              
612             sub get_row () {
613 14     14   125 my ($s)=@_;
614              
615 14 50       19 croak "no ranges defined" unless keys %{$s->[key_sources]};
  14         44  
616              
617             #make sure we have something to compare
618 14 100       38 $s->compare_ranges
619             unless $s->[key_compare];
620 14         12 my %row;
621 14         23 my (@cols)=$s->[key_compare]->();
622 14 100       38 return () unless @cols;
623 12         13 my $common;
624              
625 12         13 ($common,@row{@{$s->[key_columns]}})=@cols;
  12         34  
626              
627 12         62 $common,%row
628              
629             }
630              
631             sub get_keys () {
632 0     0     keys %{$_[0]->[key_sources]}
  0            
633             }
634              
635             ############################################
636             #
637             # End of the package
638             1;
639              
640             __END__