diff --git a/lib/ipaddress/ipv4.rb b/lib/ipaddress/ipv4.rb index a798a24..adfc100 100644 --- a/lib/ipaddress/ipv4.rb +++ b/lib/ipaddress/ipv4.rb @@ -326,6 +326,18 @@ def network? def network self.class.parse_u32(network_u32, @prefix) end + + # + # Returns a new IPv4 object containing only the host part of this IP. + # + # ip = IPAddress("172.16.10.64/24") + # + # ip.hostpart.to_s + # #=> "0.0.0.64" + # + def hostpart + self.class.parse_u32(hostpart_u32, 32) + end # # Returns a new IPv4 object with the @@ -505,6 +517,18 @@ def hosts def network_u32 @u32 & @prefix.to_u32 end + + # + # Returns this address' host part in unsigned 32bits format + # + # ip = IPAddress("10.0.0.42/24") + # + # ip.host_u32 + # #=> 42 + # + def hostpart_u32 + @u32 & ~@prefix.to_u32 + end # # Returns the broadcast address in Unsigned 32bits format @@ -565,8 +589,8 @@ def include_all?(*others) # def private? [self.class.new("10.0.0.0/8"), - self.class.new("172.16.0.0/12"), - self.class.new("192.168.0.0/16")].any? {|i| i.include? self} + self.class.new("172.16.0.0/12"), + self.class.new("192.168.0.0/16")].any? {|i| i.include? self} end # @@ -729,6 +753,97 @@ def -(oth) def +(oth) aggregate(*[self,oth].sort.map{|i| i.network}) end + + # + # Returns a new IPv4 object which is the result + # of advancing this IP address by a given value. + # In other words, this arithmetically adds IP addresses. + # + # Will raise an error if the resulting address is in a different subnet, + # except validating is set to false. + # + # Example: + # + # ip = IPAddress::IPv4.new("172.16.10.1/24") + # ip.add(5).to_string + # #=> "172.16.10.6/24" + def add(oth, validating=true) + oth = oth.to_i if oth.kind_of? IPAddress::IPv4 # oth shall be integer + + new_obj = self.class.parse_u32(self.to_i + oth, prefix) + + if validating and self.network_u32 != new_obj.network_u32 + raise ArgumentError, "Subnet (/#{@prefix}) is not large enough." + end + + return new_obj + end + + # + # Returns a new IPv4 object which is the result + # of decreasing this IP address by a given value. + # In other words, this arithmetically subtracts IP addresses. + # + # Will raise an error if the resulting address is in a different subnet, + # except validating is set to false. + # + # Example: + # + # ip = IPAddress::IPv4.new("172.16.10.10/24") + # ip.subtract(5).to_string + # #=> "172.16.10.5/24" + def subtract(oth, validating=true) + oth = oth.to_i if oth.kind_of? IPAddress::IPv4 # oth shall be integer + return add(-oth, validating) + end + + # + # Returns the network address of the n-th network succeeding this one. + # + # Example: + # + # ip = IPAddress::IPv4.new("172.16.10.0/24") + # ip.advance_network(24).to_string + # #=> "172.16.52.0/24" + def advance_network(amount) + IPAddress::IPv4.parse_u32(self.network.u32 + amount*self.size, @prefix) + end + + # + # Returns the network address of the network succeeding this one. + # + # Example: + # + # ip = IPAddress::IPv4.new("172.16.10.0/24") + # ip.next_network.to_string + # #=> "172.16.11.0/24" + def next_network + advance_network 1 + end + + # + # Returns the network address of the n-th network preceeding this one. + # + # Example: + # + # ip = IPAddress::IPv4.new("172.16.10.0/24") + # ip.regress_network(5).to_string + # #=> "172.16.5.0/24" + def regress_network(amount) + advance_network -amount + end + + # + # Returns the network address of the network preceeding this one. + # + # Example: + # + # ip = IPAddress::IPv4.new("172.16.10.0/24") + # ip.previous_network.to_string + # #=> "172.16.9.0/24" + def previous_network + regress_network 1 + end # # Checks whether the ip address belongs to a diff --git a/lib/ipaddress/ipv6.rb b/lib/ipaddress/ipv6.rb index 33d4d19..8e49b76 100644 --- a/lib/ipaddress/ipv6.rb +++ b/lib/ipaddress/ipv6.rb @@ -315,7 +315,110 @@ def reverse to_hex.reverse.gsub(/./){|c| c+"."} + "ip6.arpa" end alias_method :arpa, :reverse + + # + # Returns a new IPv6 object which is the result + # of advancing this IP address by a given value. + # In other words, this arithmetically adds IP addresses. + # + # Will raise an error if the resulting address is in a different subnet, + # except validating is set to false. + # + # Example: + # + # ip = IPAddress::IPv6.new("fc42:1337::/64") + # ip.add(5).to_string + # #=> "fc42:1337::5/64" + def add(oth, validating=true) + oth = oth.to_i if oth.kind_of? IPAddress::IPv6 # oth shall be integer + + new_obj = self.class.parse_u128(self.to_i + oth, prefix) + + if validating and self.network_u128 != new_obj.network_u128 + raise ArgumentError, "Subnet (/#{@prefix}) is not large enough." + end + + return new_obj + end + + # + # Returns a new IPv6 object which is the result + # of decreasing this IP address by a given value. + # In other words, this arithmetically subtracts IP addresses. + # + # Will raise an error if the resulting address is in a different subnet, + # except validating is set to false. + # + # Example: + # + # ip = IPAddress::IPv6.new("fc42:1337::a/64") + # ip.subtract(5).to_string + # #=> "fc42:1337::5/64" + def subtract(oth, validating=true) + oth = oth.to_i if oth.kind_of? IPAddress::IPv6 # oth shall be integer + return add(-oth, validating) + end + # + # Returns the network address of the n-th network succeeding this one. + # + # Example: + # + # ip = IPAddress::IPv6.new("fc42:1337:0:0::/64") + # ip.advance_network(5).to_string + # #=> "fc42:1337:0:5::/64" + def advance_network(amount) + IPAddress::IPv6.parse_u128(self.network.to_i + amount*self.size, @prefix) + end + + # + # Returns the network address of the network succeeding this one. + # + # Example: + # + # ip = IPAddress::IPv6.new("fc42:1337:0:0::/64") + # ip.next_network.to_string + # #=> "fc42:1337:0:1::/64" + def next_network + advance_network 1 + end + + # + # Returns the network address of the n-th network preceeding this one. + # + # Example: + # + # ip = IPAddress::IPv6.new("fc42:1337:0:5::/64") + # ip.regress_network(4).to_string + # #=> "fc42:1337:0:1::/64" + def regress_network(amount) + advance_network -amount + end + + # + # Returns the network address of the network preceeding this one. + # + # Example: + # + # ip = IPAddress::IPv6.new("fc42:1337:0:5::/64") + # ip.previous_network.to_string + # #=> "fc42:1337:0:4::/64" + def previous_network + regress_network 1 + end + + # + # Returns a new IPv6 object containing only the host part of this IP. + # + # ip = IPAddress::IPv6.new("fc42:1337:0:5::7/64") + # + # ip.hostpart.to_s + # #=> "::7" + # + def hostpart + self.class.parse_u128(hostpart_u128, 128) + end + # # Returns the network number in Unsigned 128bits format # @@ -327,6 +430,18 @@ def reverse def network_u128 to_u128 & @prefix.to_u128 end + + # + # Returns this address' host part in unsigned 128bits format + # + # ip = IPAddress::IPv6.new("fc42:1337:0:5::7/64") + # + # ip.host_u128 + # #=> 7 + # + def hostpart_u128 + to_u128 & ~@prefix.to_u128 + end # # Returns the broadcast address in Unsigned 128bits format diff --git a/test/ipaddress/ipv4_test.rb b/test/ipaddress/ipv4_test.rb index f144634..5f7ba9f 100644 --- a/test/ipaddress/ipv4_test.rb +++ b/test/ipaddress/ipv4_test.rb @@ -13,13 +13,13 @@ def setup "10.0.0.1/255.255.255.0" => ["10.0.0.1", 24]} @invalid_ipv4 = ["10.0.0.256", - "10.0.0.0.0", - "10.0.0", - "10.0"] + "10.0.0.0.0", + "10.0.0", + "10.0"] @valid_ipv4_range = ["10.0.0.1-254", - "10.0.1-254.0", - "10.1-254.0.0"] + "10.0.1-254.0", + "10.1-254.0.0"] @netmask_values = { "0.0.0.0/0" => "0.0.0.0", @@ -193,7 +193,7 @@ def test_method_each_host arr = [] ip.each_host {|i| arr << i.to_s} expected = ["10.0.0.1","10.0.0.2","10.0.0.3", - "10.0.0.4","10.0.0.5","10.0.0.6"] + "10.0.0.4","10.0.0.5","10.0.0.6"] assert_equal expected, arr end @@ -202,8 +202,8 @@ def test_method_each arr = [] ip.each {|i| arr << i.to_s} expected = ["10.0.0.0","10.0.0.1","10.0.0.2", - "10.0.0.3","10.0.0.4","10.0.0.5", - "10.0.0.6","10.0.0.7"] + "10.0.0.3","10.0.0.4","10.0.0.5", + "10.0.0.6","10.0.0.7"] assert_equal expected, arr end @@ -215,7 +215,7 @@ def test_method_size def test_method_hosts ip = @klass.new("10.0.0.1/29") expected = ["10.0.0.1","10.0.0.2","10.0.0.3", - "10.0.0.4","10.0.0.5","10.0.0.6"] + "10.0.0.4","10.0.0.5","10.0.0.6"] assert_equal expected, ip.hosts.map {|i| i.to_s} end @@ -354,7 +354,7 @@ def test_method_plus ip2 = @klass.new("172.16.12.2/24") assert_equal [ip1.network.to_string, ip2.network.to_string], - (ip1 + ip2).map{|i| i.to_string} + (ip1 + ip2).map{|i| i.to_string} ip1 = @klass.new("10.0.0.0/23") ip2 = @klass.new("10.0.2.0/24") @@ -388,21 +388,21 @@ def test_method_split assert_equal @ip.network, @ip.split(1).first arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", - "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27", - "172.16.10.192/27", "172.16.10.224/27"] + "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27", + "172.16.10.192/27", "172.16.10.224/27"] assert_equal arr, @network.split(8).map {|s| s.to_string} arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", - "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27", - "172.16.10.192/26"] + "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27", + "172.16.10.192/26"] assert_equal arr, @network.split(7).map {|s| s.to_string} arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", - "172.16.10.96/27", "172.16.10.128/26", "172.16.10.192/26"] + "172.16.10.96/27", "172.16.10.128/26", "172.16.10.192/26"] assert_equal arr, @network.split(6).map {|s| s.to_string} arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", - "172.16.10.96/27", "172.16.10.128/25"] + "172.16.10.96/27", "172.16.10.128/25"] assert_equal arr, @network.split(5).map {|s| s.to_string} arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26", - "172.16.10.192/26"] + "172.16.10.192/26"] assert_equal arr, @network.split(4).map {|s| s.to_string} arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/25"] assert_equal arr, @network.split(3).map {|s| s.to_string} @@ -417,7 +417,7 @@ def test_method_subnet assert_raise(ArgumentError) {@network.subnet(33)} assert_nothing_raised {@ip.subnet(30)} arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26", - "172.16.10.192/26"] + "172.16.10.192/26"] assert_equal arr, @network.subnet(26).map {|s| s.to_string} arr = ["172.16.10.0/25", "172.16.10.128/25"] assert_equal arr, @network.subnet(25).map {|s| s.to_string} @@ -433,6 +433,57 @@ def test_method_supernet assert_equal "172.16.8.0/22", @ip.supernet(22).to_string end + def test_method_add + ip = IPAddress::IPv4.new("172.16.10.1/24") + assert_equal ip.add(5), IPAddress::IPv4.new("172.16.10.6/24") + assert_equal ip.add(IPAddress::IPv4.new("0.0.0.5/6")), IPAddress::IPv4.new("172.16.10.6/24") + assert_equal ip.add(50), IPAddress::IPv4.new("172.16.10.51/24") + assert_equal ip.add(254), IPAddress::IPv4.new("172.16.10.255/24") + assert_raise(ArgumentError) {ip.add(255)} + assert_equal ip.add(255, false), IPAddress::IPv4.new("172.16.11.0/24") + assert_raise(ArgumentError) {ip.add(1000)} + ip = IPAddress::IPv4.new("172.16.10.1/30") + assert_equal ip.add(2), IPAddress::IPv4.new("172.16.10.3/30") + assert_raise(ArgumentError) {ip.add(3)} + end + + def test_method_subtract + ip = IPAddress::IPv4.new("172.16.10.10/24") + assert_equal ip.subtract(5), IPAddress::IPv4.new("172.16.10.5/24") + assert_equal ip.subtract(IPAddress::IPv4.new("0.0.0.5/32")), IPAddress::IPv4.new("172.16.10.5/24") + assert_equal ip.subtract(10), IPAddress::IPv4.new("172.16.10.0/24") + assert_raise(ArgumentError) {ip.subtract(11)} + assert_equal ip.subtract(11, false), IPAddress::IPv4.new("172.16.9.255/24") + assert_raise(ArgumentError) {ip.subtract(IPAddress::IPv4.new("0.0.0.11/16"))} + end + + def test_method_hostpart + ip = IPAddress::IPv4.new("172.16.10.64/24") + assert_equal ip.hostpart.to_s, "0.0.0.64" + ip = IPAddress::IPv4.new("172.16.10.130/25") + assert_equal ip.hostpart.to_s, "0.0.0.2" + end + + def test_method_advance_network + ip = IPAddress::IPv4.new("172.16.10.64/24") + assert_equal ip.advance_network(42), IPAddress::IPv4.new("172.16.52.0/24") + end + + def test_method_next_network + ip = IPAddress::IPv4.new("172.16.10.64/24") + assert_equal ip.next_network, IPAddress::IPv4.new("172.16.11.0/24") + end + + def test_method_regress_network + ip = IPAddress::IPv4.new("172.16.10.64/24") + assert_equal ip.regress_network(5), IPAddress::IPv4.new("172.16.5.0/24") + end + + def test_method_previous_network + ip = IPAddress::IPv4.new("172.16.10.64/24") + assert_equal ip.previous_network, IPAddress::IPv4.new("172.16.9.0/24") + end + def test_classmethod_parse_u32 @decimal_values.each do |addr,int| ip = @klass.parse_u32(int) @@ -481,7 +532,7 @@ def test_classmethod_summarize ip3 = @klass.new("10.0.4.0/24") ip4 = @klass.new("10.0.6.0/24") assert_equal ["10.0.0.0/22","10.0.4.0/24","10.0.6.0/24"], - @klass.summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} + @klass.summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} ip1 = @klass.new("10.0.1.1/24") ip2 = @klass.new("10.0.2.1/24") @@ -499,17 +550,17 @@ def test_classmethod_summarize assert_equal result, @klass.summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} ips = [@klass.new("10.0.0.12/30"), - @klass.new("10.0.100.0/24")] + @klass.new("10.0.100.0/24")] result = ["10.0.0.12/30", "10.0.100.0/24"] assert_equal result, @klass.summarize(*ips).map{|i| i.to_string} ips = [@klass.new("172.16.0.0/31"), - @klass.new("10.10.2.1/32")] + @klass.new("10.10.2.1/32")] result = ["10.10.2.1/32", "172.16.0.0/31"] assert_equal result, @klass.summarize(*ips).map{|i| i.to_string} ips = [@klass.new("172.16.0.0/32"), - @klass.new("10.10.2.1/32")] + @klass.new("10.10.2.1/32")] result = ["10.10.2.1/32", "172.16.0.0/32"] assert_equal result, @klass.summarize(*ips).map{|i| i.to_string} diff --git a/test/ipaddress/ipv6_test.rb b/test/ipaddress/ipv6_test.rb index 8e6213c..3ae51b3 100644 --- a/test/ipaddress/ipv6_test.rb +++ b/test/ipaddress/ipv6_test.rb @@ -258,6 +258,50 @@ def test_method_compare "2001:db8:1::2/64","2001:db8:2::1/64"] assert_equal arr, [ip1,ip2,ip3,ip4].sort.map{|s| s.to_string} end + + def test_method_add + ip = IPAddress::IPv6.new("fc42:1337::/64") + assert_equal ip.add(5), IPAddress::IPv6.new("fc42:1337::5/64") + assert_equal ip.add(IPAddress::IPv6.new("::5/42")), IPAddress::IPv6.new("fc42:1337::5/64") + assert_equal ip.add(50), IPAddress::IPv6.new("fc42:1337::32/64") + ip = IPAddress::IPv6.new("fc42:1337::/120") + assert_equal ip.add(2), IPAddress::IPv6.new("fc42:1337::2/120") + assert_raise(ArgumentError) {ip.add(256)} + assert_equal ip.add(256, false), IPAddress::IPv6.new("fc42:1337::100/120") + end + + def test_method_subtract + ip = IPAddress::IPv6.new("fc42:1337::5/64") + assert_equal ip.subtract(5), IPAddress::IPv6.new("fc42:1337::/64") + assert_equal ip.subtract(IPAddress::IPv6.new("::5/12")), IPAddress::IPv6.new("fc42:1337::0/64") + assert_raise(ArgumentError) {ip.subtract(11)} + assert_raise(ArgumentError) {ip.subtract(IPAddress::IPv6.new("::11/66"))} + end + + def test_method_hostpart + ip = IPAddress::IPv6.new("fc42:1337:0:5::7/64") + assert_equal ip.hostpart.to_s, "::7" + end + + def test_method_advance_network + ip = IPAddress::IPv6.new("fc42:1337:0:0::/64") + assert_equal ip.advance_network(5), IPAddress::IPv6.new("fc42:1337:0:5::/64") + end + + def test_method_next_network + ip = IPAddress::IPv6.new("fc42:1337:0:0::/64") + assert_equal ip.next_network, IPAddress::IPv6.new("fc42:1337:0:1::/64") + end + + def test_method_regress_network + ip = IPAddress::IPv6.new("fc42:1337:0:5::/64") + assert_equal ip.regress_network(4), IPAddress::IPv6.new("fc42:1337:0:1::/64") + end + + def test_method_previous_network + ip = IPAddress::IPv6.new("fc42:1337:0:5::/64") + assert_equal ip.previous_network, IPAddress::IPv6.new("fc42:1337:0:4::/64") + end def test_classmethod_expand compressed = "2001:db8:0:cd30::" diff --git a/test/test_helper.rb b/test/test_helper.rb index 61e381e..ebf13bd 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,4 +1,5 @@ require 'rubygems' +gem 'test-unit' if RUBY_VERSION >= "1.9.0" require 'test/unit' $LOAD_PATH.unshift(File.dirname(__FILE__))