diff --git a/IP.pm b/IP.pm index 98922be..39756b7 100644 --- a/IP.pm +++ b/IP.pm @@ -32,7 +32,7 @@ ); require Exporter; -@EXPORT_OK = qw(Compact Coalesce Zero Zeros Ones V4mask V4net Exclude netlimit); +@EXPORT_OK = qw(Compact Coalesce Zero Zeros Ones V4mask V4net Subtract Exclude netlimit); @EXPORT_FAIL = qw($_netlimit); @ISA = qw(Exporter NetAddr::IP::Lite); @@ -58,6 +58,7 @@ Ones V4mask V4net + Subtract Exclude netlimit :aton DEPRECATED @@ -422,6 +423,29 @@ return &coalesce; } +sub Subtract { + my @add = ref $_[0] eq __PACKAGE__ ? ($_[0]) : @{$_[0]}; + my @sub = @{$_[1]}; + my @ret; + while (@add && @sub) { + if ($sub[0]->contains($add[0])) { + shift @add; + } elsif ($add[0]->contains($sub[0])) { + my ($low,$up) = Exclude(shift @add,shift @sub); + push @ret, @$low; + unshift @add, @$up; + } else { + if ($add[0]->bigint < $sub[0]->bigint) { + push @ret, shift @add; + } else { + shift @sub; + } + } + } + push @ret, @add; + return \@ret; +} + sub Exclude { return ([],[]) if $_[0] == $_[1]; return ([$_[0]],[]) unless $_[1]->within($_[0]); diff --git a/t/v4-subtract.t b/t/v4-subtract.t new file mode 100644 index 0000000..113934c --- /dev/null +++ b/t/v4-subtract.t @@ -0,0 +1,86 @@ +use Test::More; +use NetAddr::IP qw(Exclude Subtract); + +my @r = ( + ['10.0.0.0/8','10.0.0.0/9'], + ['10.0.0.0/8','10.0.0.0/8'], + ['10.0.0.0/8','10.128.0.0/9'], + ['10.0.0.0/8','10.128.0.0/10'], + ['10.0.0.0/8','10.0.0.0/32'], + ['192.168.0.0/24','192.168.0.255/32'], + ['192.168.0.0/23','192.168.0.127/32'], + ['1.2.3.4/32','1.2.3.4/32'], + ['0.0.0.0/0','0.0.0.0/32'], +); + +plan tests => 1 + 19 * scalar @r; + +SKIP: { + my $res = Subtract([],[]); + is("@$res", "", "Subtract([],[]) returns an empty list"); + + foreach my $case (@r) { + my $whole = [NetAddr::IP->new($case->[0])]; + my $part1 = [NetAddr::IP->new($case->[1])]; + my ($low,$up) = Exclude($whole->[0],$part1->[0]); + my $part2 = [@{$low},@{$up}]; + my $res = []; + + $res = Subtract($whole,$part1); + is("@$res", "@$part2", "Subtract(@$whole,@$part1) returns @$part2"); + + $res = $whole->[0]->Subtract($part1); + is("@$res", "@$part2", "($whole->[0])->Subtract(@$part1) returns @$part2"); + + $res = Subtract($whole,$part2); + is("@$res", "@$part1", "Subtract(@$whole,@$part2) returns @$part1"); + + $res = $whole->[0]->Subtract($part2); + is("@$res", "@$part1", "($whole->[0])->Subtract(@$part2) returns @$part1"); + + $res = Subtract($part1,$part2); + is("@$res", "@$part1", "Subtract(@$part1,@$part2) returns @$part1"); + + $res = $part1->[0]->Subtract($part2); + is("@$res", "@$part1", "($part1->[0])->Subtract(@$part2) returns @$part1"); + + $res = Subtract($part2,$part1); + is("@$res", "@$part2", "Subtract(@$part2,@$part1) returns @$part2"); + + $res = Subtract(Subtract($whole,$part1),$part2); + is("@$res", "", "Subtract(Subtract(@$whole,@$part1),$part2) returns an empty list"); + + $res = Subtract($whole->[0]->Subtract($part1),$part2); + is("@$res", "", "Subtract(($whole->[0])->Subtract(@$part1),$part2) returns an empty list"); + + $res = Subtract(Subtract($whole,$part2),$part1); + is("@$res", "", "Subtract(Subtract(@$whole,@$part2),$part1) returns an empty list"); + + $res = Subtract($whole->[0]->Subtract($part2),$part1); + is("@$res", "", "Subtract(($whole->[0])->Subtract(@$part2),$part1) returns an empty list"); + + $res = Subtract($whole,$whole); + is("@$res", "", "Subtract(@$whole,@$whole) returns an empty list"); + + $res = $whole->[0]->Subtract($whole); + is("@$res", "", "($whole->[0])->Subtract(@$whole) returns an empty list"); + + $res = Subtract($part1,$part1); + is("@$res", "", "Subtract(@$part1,@$part1) returns an empty list"); + + $res = $part1->[0]->Subtract($part1); + is("@$res", "", "($part1->[0])->Subract(@$part1) returns an empty list"); + + $res = Subtract($part2,$part2); + is("@$res", "", "Subtract(@$part2,@$part2) returns an empty list"); + + $res = Subtract($part1,$whole); + is("@$res", "", "Subtract(@$part1,@$whole) returns an empty list"); + + $res = $part1->[0]->Subtract($whole); + is("@$res", "", "($part1->[0])->Subtract(@$whole) returns an empty list"); + + $res = Subtract($part2,$whole); + is("@$res", "", "Subtract(@$part2,@$whole) returns an empty list"); + } +} diff --git a/t/v6-subtract.t b/t/v6-subtract.t new file mode 100644 index 0000000..9274688 --- /dev/null +++ b/t/v6-subtract.t @@ -0,0 +1,82 @@ +use Test::More; +use NetAddr::IP qw(:lower Exclude Subtract); + +my @r = ( + ['2001:db8::/32','2001:db8::/33'], + ['2001:db8::/64','2001:db8::/64'], + ['2001:db8::/128','2001:db8::/128'], + ['2001:db8::/32','2001:db8:8000::/33'], + ['2001:db8::/32','2001:db8:8000::/34'], +); + +plan tests => 1 + 19 * scalar @r; + +SKIP: { + my $res = Subtract([],[]); + is("@$res", "", "Subtract([],[]) returns an empty list"); + + foreach my $case (@r) { + my $whole = [NetAddr::IP->new($case->[0])]; + my $part1 = [NetAddr::IP->new($case->[1])]; + my ($low,$up) = Exclude($whole->[0],$part1->[0]); + my $part2 = [@{$low},@{$up}]; + my $res = []; + + $res = Subtract($whole,$part1); + is("@$res", "@$part2", "Subtract(@$whole,@$part1) returns @$part2"); + + $res = $whole->[0]->Subtract($part1); + is("@$res", "@$part2", "($whole->[0])->Subtract(@$part1) returns @$part2"); + + $res = Subtract($whole,$part2); + is("@$res", "@$part1", "Subtract(@$whole,@$part2) returns @$part1"); + + $res = $whole->[0]->Subtract($part2); + is("@$res", "@$part1", "($whole->[0])->Subtract(@$part2) returns @$part1"); + + $res = Subtract($part1,$part2); + is("@$res", "@$part1", "Subtract(@$part1,@$part2) returns @$part1"); + + $res = $part1->[0]->Subtract($part2); + is("@$res", "@$part1", "($part1->[0])->Subtract(@$part2) returns @$part1"); + + $res = Subtract($part2,$part1); + is("@$res", "@$part2", "Subtract(@$part2,@$part1) returns @$part2"); + + $res = Subtract(Subtract($whole,$part1),$part2); + is("@$res", "", "Subtract(Subtract(@$whole,@$part1),$part2) returns an empty list"); + + $res = Subtract($whole->[0]->Subtract($part1),$part2); + is("@$res", "", "Subtract(($whole->[0])->Subtract(@$part1),$part2) returns an empty list"); + + $res = Subtract(Subtract($whole,$part2),$part1); + is("@$res", "", "Subtract(Subtract(@$whole,@$part2),$part1) returns an empty list"); + + $res = Subtract($whole->[0]->Subtract($part2),$part1); + is("@$res", "", "Subtract(($whole->[0])->Subtract(@$part2),$part1) returns an empty list"); + + $res = Subtract($whole,$whole); + is("@$res", "", "Subtract(@$whole,@$whole) returns an empty list"); + + $res = $whole->[0]->Subtract($whole); + is("@$res", "", "($whole->[0])->Subtract(@$whole) returns an empty list"); + + $res = Subtract($part1,$part1); + is("@$res", "", "Subtract(@$part1,@$part1) returns an empty list"); + + $res = $part1->[0]->Subtract($part1); + is("@$res", "", "($part1->[0])->Subract(@$part1) returns an empty list"); + + $res = Subtract($part2,$part2); + is("@$res", "", "Subtract(@$part2,@$part2) returns an empty list"); + + $res = Subtract($part1,$whole); + is("@$res", "", "Subtract(@$part1,@$whole) returns an empty list"); + + $res = $part1->[0]->Subtract($whole); + is("@$res", "", "($part1->[0])->Subtract(@$whole) returns an empty list"); + + $res = Subtract($part2,$whole); + is("@$res", "", "Subtract(@$part2,@$whole) returns an empty list"); + } +}