From 3a8e3d10f4c75d111ed0a7c6aa1b6d17a71d9923 Mon Sep 17 00:00:00 2001 From: Skorobogaty Dmitry Date: Thu, 2 Mar 2023 15:33:22 +0300 Subject: [PATCH 1/3] allow to substract subnets --- lib/ipaddress/ipv4.rb | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/ipaddress/ipv4.rb b/lib/ipaddress/ipv4.rb index 82546d6..5a0b3ed 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,42 @@ def subnet(subprefix) end end + # + # Subtract other from the current range + # Returns list of ranges that excludes :other + # + # Example: + # + # a = IPAddress '10.0.0.0/16' + # b = IPAddress '10.0.128.0/18' + # c = IPAddress '192.168.0.0/16' + # + # a.subtract(b) + # => + # [#, + # #, + # #] + # b.subtract(a) + # => [] + # a.subtract(c) + # => [#] + # + def subtract(other) + raise ArgumentError unless other.is_a? self.class + result = [] + if self.include? other + split_bits = 2**(self.prefix - other.prefix) + other_alike_subnets = self.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 << self.clone + end + result + end + # # Returns the difference between two IP addresses # in unsigned int 32 bits format From 0ed9b8baac83a013cc4c5a2d1586ec33e5ba714c Mon Sep 17 00:00:00 2001 From: Skorobogaty Dmitry Date: Thu, 16 Mar 2023 13:21:58 +0300 Subject: [PATCH 2/3] test for subtraction --- test/ipaddress/ipv4_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/ipaddress/ipv4_test.rb b/test/ipaddress/ipv4_test.rb index 5e8d5a9..fc8920b 100644 --- a/test/ipaddress/ipv4_test.rb +++ b/test/ipaddress/ipv4_test.rb @@ -491,6 +491,16 @@ 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 + a = IPAddress '10.0.0.0/16' + b = IPAddress '10.0.128.0/18' + c = IPAddress '192.168.0.0/16' + + assert !a.subtract(b).include?(b) + assert b.subtract(a).empty? + assert_equal a, a.subtract(c).first + end def test_method_supernet assert_raises(ArgumentError) {@ip.supernet(24)} From 187ae2d6e2b4cdfa6c974eaaa91a3980c585dff6 Mon Sep 17 00:00:00 2001 From: Skorobogaty Dmitry Date: Tue, 2 May 2023 15:29:40 +0300 Subject: [PATCH 3/3] address review comments --- lib/ipaddress/ipv4.rb | 32 +++++++++++++++++--------------- test/ipaddress/ipv4_test.rb | 32 ++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/lib/ipaddress/ipv4.rb b/lib/ipaddress/ipv4.rb index 5a0b3ed..a35fb42 100644 --- a/lib/ipaddress/ipv4.rb +++ b/lib/ipaddress/ipv4.rb @@ -807,36 +807,38 @@ def subnet(subprefix) # # Subtract other from the current range - # Returns list of ranges that excludes :other + # Result is a list of ranges that are all a part of the current range + # excluding the other range # # Example: # - # a = IPAddress '10.0.0.0/16' - # b = IPAddress '10.0.128.0/18' - # c = IPAddress '192.168.0.0/16' + # 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' # - # a.subtract(b) - # => + # whole_range.subtract(sub_range) + # => # [#, # #, - # #] - # b.subtract(a) + # #] + # sub_range.subtract(whole_range) # => [] - # a.subtract(c) - # => [#] + # whole_range.subtract(non_overlap_range) + # => [#] # def subtract(other) raise ArgumentError unless other.is_a? self.class + result = [] - if self.include? other - split_bits = 2**(self.prefix - other.prefix) - other_alike_subnets = self.split split_bits + 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 << self.clone + result << clone end result end diff --git a/test/ipaddress/ipv4_test.rb b/test/ipaddress/ipv4_test.rb index fc8920b..733bdb7 100644 --- a/test/ipaddress/ipv4_test.rb +++ b/test/ipaddress/ipv4_test.rb @@ -492,14 +492,30 @@ def test_method_subnet assert_equal arr, @network.subnet(24).map {|s| s.to_string} end - def test_method_subtract - a = IPAddress '10.0.0.0/16' - b = IPAddress '10.0.128.0/18' - c = IPAddress '192.168.0.0/16' - - assert !a.subtract(b).include?(b) - assert b.subtract(a).empty? - assert_equal a, a.subtract(c).first + 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