diff --git a/lib/ipaddress/ipv4.rb b/lib/ipaddress/ipv4.rb index 82546d6..a35fb42 100644 --- a/lib/ipaddress/ipv4.rb +++ b/lib/ipaddress/ipv4.rb @@ -516,7 +516,7 @@ def <=>(oth) to_u32 <=> oth.to_u32 end alias eql? == - + # # Returns the number of IP addresses included # in the network. It also counts the network @@ -805,6 +805,44 @@ def subnet(subprefix) end end + # + # Subtract other from the current range + # Result is a list of ranges that are all a part of the current range + # excluding the other range + # + # Example: + # + # whole_range = IPAddress '10.0.0.0/16' + # sub_range = IPAddress '10.0.128.0/18' + # non_overlap_range = IPAddress '192.168.0.0/16' + # + # whole_range.subtract(sub_range) + # => + # [#, + # #, + # #] + # sub_range.subtract(whole_range) + # => [] + # whole_range.subtract(non_overlap_range) + # => [#] + # + def subtract(other) + raise ArgumentError unless other.is_a? self.class + + result = [] + if include? other + split_bits = 2**(prefix - other.prefix) + other_alike_subnets = split split_bits + raise "Prefix of subnet and split doesn't match" unless other_alike_subnets.first.prefix == other.prefix + + other_alike_subnets.delete other + result = other_alike_subnets + elsif !other.include? self + result << clone + end + result + end + # # Returns the difference between two IP addresses # in unsigned int 32 bits format diff --git a/test/ipaddress/ipv4_test.rb b/test/ipaddress/ipv4_test.rb index 5e8d5a9..733bdb7 100644 --- a/test/ipaddress/ipv4_test.rb +++ b/test/ipaddress/ipv4_test.rb @@ -491,6 +491,32 @@ def test_method_subnet arr = ["172.16.10.0/24"] assert_equal arr, @network.subnet(24).map {|s| s.to_string} end + + def test_method_subtract_result_not_include_subtrahend + whole_range = IPAddress '10.0.0.0/16' + sub_range = IPAddress '10.0.128.0/18' + assert !whole_range.subtract(sub_range).include?(sub_range) + end + + def test_method_subtract_empty_result_if_subtract_whole + whole_range = IPAddress '10.0.0.0/16' + sub_range = IPAddress '10.0.128.0/18' + assert sub_range.subtract(whole_range).empty? + end + + def test_method_subtract_non_overlap_range + whole_range = IPAddress '10.0.0.0/16' + non_overlap_range = IPAddress '192.168.0.0/16' + assert_equal 1, whole_range.subtract(non_overlap_range).size + assert_equal whole_range, whole_range.subtract(non_overlap_range).first + end + + def test_method_subtract_inividual_address + whole_range = IPAddress '10.0.0.0/26' + one_ip_address = IPAddress '10.0.0.26' + assert_equal 2**(32 - 26) - 1, whole_range.subtract(one_ip_address).size + assert !whole_range.subtract(one_ip_address).include?(one_ip_address) + end def test_method_supernet assert_raises(ArgumentError) {@ip.supernet(24)}