From 6f509fe6cc79dd7035b0e9242d15eec3dd2a7bde Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Fri, 29 Nov 2024 13:57:17 +0100 Subject: [PATCH 1/5] adding iterators; isolating VERSION file --- ipaddr.gemspec | 15 +++++++++------ lib/ipaddr.rb | 43 +++++++++++++++++++++++++++++++++++++++++-- lib/ipaddr/version.rb | 3 +++ 3 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 lib/ipaddr/version.rb diff --git a/ipaddr.gemspec b/ipaddr.gemspec index 5719f83..1d6175b 100644 --- a/ipaddr.gemspec +++ b/ipaddr.gemspec @@ -1,6 +1,8 @@ # frozen_string_literal: true # coding: utf-8 +require_relative 'lib/ipaddr/version' + if File.exist?(File.expand_path("ipaddr.gemspec")) lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) @@ -11,13 +13,9 @@ else file = File.expand_path("../ipaddr.rb", __FILE__) end -version = File.foreach(file).find do |line| - /^\s*VERSION\s*=\s*["'](.*)["']/ =~ line and break $1 -end - Gem::Specification.new do |spec| spec.name = "ipaddr" - spec.version = version + spec.version = IPAddr::VERSION spec.authors = ["Akinori MUSHA", "Hajimu UMEMOTO"] spec.email = ["knu@idaemons.org", "ume@mahoroba.org"] @@ -29,7 +27,12 @@ Both IPv4 and IPv6 are supported. spec.homepage = "https://github.com/ruby/ipaddr" spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.files = ["LICENSE.txt", "README.md", "ipaddr.gemspec", "lib/ipaddr.rb"] + spec.files = Dir[ + "lib/**/*.rb", + "LICENSE.txt", + "README.md", + "ipaddr.gemspec", + ] spec.require_paths = ["lib"] spec.required_ruby_version = ">= 2.4" diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index a450554..7b3487a 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -40,8 +40,6 @@ # p ipaddr3 #=> # class IPAddr - VERSION = "1.2.7" - # 32 bit mask for IPv4 IN4MASK = 0xffffffff # 128 bit mask for IPv6 @@ -443,6 +441,47 @@ def to_range self.class.new(begin_addr, @family)..self.class.new(end_addr, @family) end + # Iterates over all addresses in the range; Includes network and broadcast address for IPv4 + def each + (begin_addr..end_addr).each do |addr| + yield self.class.new(addr, @family) + end + end + + # Total number of IP addresses in range + def ip_range_size + end_addr - begin_addr + 1 + end + alias size ip_range_size + + # Iterates over all usable host addresses + def each_host + if @family == Socket::AF_INET + # for IPv4 exclude the network and broadcast address + if size > 2 + # Exclude network and broadcast addresses for IPv4 + ((begin_addr + 1)...end_addr).each do |addr| + yield self.class.new(addr, @family) + end + else + # No host addresses available + return + end + else + # Include all addresses for IPv6 + each { |addr| yield addr } + end + end + + # Number of host addresses in range + def usable_hosts_size + if @family == Socket::AF_INET + size > 2 ? size - 2 : 0 + else + size + end + end + # Returns the prefix length in bits for the ipaddr. def prefix case @family diff --git a/lib/ipaddr/version.rb b/lib/ipaddr/version.rb new file mode 100644 index 0000000..dee6e6e --- /dev/null +++ b/lib/ipaddr/version.rb @@ -0,0 +1,3 @@ +class IPAddr + VERSION = "1.2.8.pre1" +end From 73780303ccb13d4ab34e8b22646721482373e941 Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Fri, 29 Nov 2024 14:11:43 +0100 Subject: [PATCH 2/5] adding tests --- test/test_ipaddr.rb | 83 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index f2b7ed7..501a49d 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -571,4 +571,87 @@ def test_hash assert_equal(true, s.include?(a5)) assert_equal(true, s.include?(a6)) end + + # Test for each method + def test_each + ip = IPAddr.new("192.168.0.0/30") + expected = ["192.168.0.0", "192.168.0.1", "192.168.0.2", "192.168.0.3"] + actual = [] + ip.each { |addr| actual << addr.to_s } + assert_equal(expected, actual) + + # Test for IPv6 + ip6 = IPAddr.new("2001:db8::/126") + expected6 = ["2001:db8::", "2001:db8::1", "2001:db8::2", "2001:db8::3"] + actual6 = [] + ip6.each { |addr| actual6 << addr.to_s } + assert_equal(expected6, actual6) + end + + # Test for ip_range_size method + def test_ip_range_size + ip = IPAddr.new("192.168.0.0/30") + assert_equal(4, ip.ip_range_size) + + ip6 = IPAddr.new("2001:db8::/126") + assert_equal(4, ip6.ip_range_size) + end + + # Test for each_host method + def test_each_host + ip = IPAddr.new("192.168.0.0/30") + expected = ["192.168.0.1", "192.168.0.2"] + actual = [] + ip.each_host { |addr| actual << addr.to_s } + assert_equal(expected, actual) + + # Test for IPv6 + ip6 = IPAddr.new("2001:db8::/126") + expected6 = ["2001:db8::", "2001:db8::1", "2001:db8::2", "2001:db8::3"] + actual6 = [] + ip6.each_host { |addr| actual6 << addr.to_s } + assert_equal(expected6, actual6) + end + + # Test for usable_hosts_size method + def test_usable_hosts_size + ip = IPAddr.new("192.168.0.0/30") + assert_equal(2, ip.usable_hosts_size) + + ip6 = IPAddr.new("2001:db8::/126") + assert_equal(4, ip6.usable_hosts_size) + end + + # Edge case for IPv4 /32 subnet (single IP address) + def test_edge_case_ipv4_single_ip + ip = IPAddr.new("192.168.0.1/32") + assert_equal(1, ip.ip_range_size) + assert_equal(0, ip.usable_hosts_size) + + actual = [] + ip.each_host { |addr| actual << addr.to_s } + assert_empty(actual) + end + + # Edge case for IPv6 /128 subnet (single IP address) + def test_edge_case_ipv6_single_ip + ip6 = IPAddr.new("2001:db8::1/128") + assert_equal(1, ip6.ip_range_size) + assert_equal(1, ip6.usable_hosts_size) + + actual6 = [] + ip6.each_host { |addr| actual6 << addr.to_s } + assert_equal(["2001:db8::1"], actual6) + end + + # Edge case for IPv4 /31 subnet (point-to-point link) + def test_edge_case_ipv4_point_to_point + ip = IPAddr.new("192.168.0.0/31") + assert_equal(2, ip.ip_range_size) + assert_equal(0, ip.usable_hosts_size) + + actual = [] + ip.each_host { |addr| actual << addr.to_s } + assert_empty(actual) + end end From b7ec1c7c5aa1ef864bf3d80fa1f710743e48f51d Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Fri, 29 Nov 2024 14:19:29 +0100 Subject: [PATCH 3/5] update --- lib/ipaddr.rb | 19 +++++--- test/test_ipaddr.rb | 115 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 109 insertions(+), 25 deletions(-) diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 7b3487a..ff4c38c 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -457,18 +457,23 @@ def ip_range_size # Iterates over all usable host addresses def each_host if @family == Socket::AF_INET - # for IPv4 exclude the network and broadcast address - if size > 2 - # Exclude network and broadcast addresses for IPv4 - ((begin_addr + 1)...end_addr).each do |addr| + case size + when 1 + # For /32 networks, yield the single address + yield self + when 2 + # For /31 networks, both addresses are usable + (begin_addr..end_addr).each do |addr| yield self.class.new(addr, @family) end else - # No host addresses available - return + # For larger networks, exclude network and broadcast addresses + ((begin_addr + 1)...end_addr).each do |addr| + yield self.class.new(addr, @family) + end end else - # Include all addresses for IPv6 + # For IPv6, include all addresses each { |addr| yield addr } end end diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index 501a49d..a8a7dce 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'test/unit' require 'ipaddr' +require 'lib/helper' class TC_IPAddr < Test::Unit::TestCase def test_s_new @@ -598,39 +599,116 @@ def test_ip_range_size end # Test for each_host method - def test_each_host - ip = IPAddr.new("192.168.0.0/30") - expected = ["192.168.0.1", "192.168.0.2"] + # Test each_host for IPv4 networks + def test_each_host_ipv4 + # Test with /24 network (size > 2) + ip = IPAddr.new("192.168.1.0/24") + expected = (1..254).map { |i| "192.168.1.#{i}" } actual = [] ip.each_host { |addr| actual << addr.to_s } - assert_equal(expected, actual) + assert_equal(expected, actual, "Failed on IPv4 /24 network") - # Test for IPv6 - ip6 = IPAddr.new("2001:db8::/126") - expected6 = ["2001:db8::", "2001:db8::1", "2001:db8::2", "2001:db8::3"] - actual6 = [] - ip6.each_host { |addr| actual6 << addr.to_s } - assert_equal(expected6, actual6) + # Test with /30 network (size == 4) + ip = IPAddr.new("192.168.1.0/30") + expected = ["192.168.1.1", "192.168.1.2"] + actual = [] + ip.each_host { |addr| actual << addr.to_s } + assert_equal(expected, actual, "Failed on IPv4 /30 network") + + # Test with /31 network (size == 2) + ip = IPAddr.new("192.168.1.0/31") + expected = ["192.168.1.0", "192.168.1.1"] + actual = [] + ip.each_host { |addr| actual << addr.to_s } + assert_equal(expected, actual, "Failed on IPv4 /31 network") + + # Test with /32 network (size == 1) + ip = IPAddr.new("192.168.1.1/32") + expected = ["192.168.1.1"] + actual = [] + ip.each_host { |addr| actual << addr.to_s } + assert_equal(expected, actual, "Failed on IPv4 /32 network") + end + + # Test each_host for IPv6 networks + def test_each_host_ipv6 + # Test with /64 network (large network) + ip6 = IPAddr.new("2001:db8::/64") + + # Since it's impractical to test all addresses, we'll test the first 3 addresses + + # Collect first 3 addresses + expected_first = ["2001:db8::", "2001:db8::1", "2001:db8::2"] + actual_first = [] + count = 0 + ip6.each_host do |addr| + actual_first << addr.to_s + count += 1 + break if count >= 3 + end + assert_equal(expected_first, actual_first, "Failed on IPv6 /64 network first addresses") + + # Collect last 3 addresses + expected_last = [ + "2001:db8::ffff:ffff:ffff:fffd", + "2001:db8::ffff:ffff:ffff:fffe", + "2001:db8::ffff:ffff:ffff:ffff" + ] + actual_last = [] + # Calculate last three addresses + last_addr_int = ip6.send(:end_addr) + (last_addr_int - 2..last_addr_int).each do |addr_int| + actual_last << IPAddr.new(addr_int, Socket::AF_INET6).to_s + end + assert_equal(expected_last, actual_last, "Failed on IPv6 /64 network last addresses") end # Test for usable_hosts_size method - def test_usable_hosts_size - ip = IPAddr.new("192.168.0.0/30") - assert_equal(2, ip.usable_hosts_size) + # Test usable_hosts_size for IPv4 networks + def test_usable_hosts_size_ipv4 + # Test with /24 network (size == 256) + ip = IPAddr.new("192.168.1.0/24") + assert_equal(254, ip.usable_hosts_size, "Failed on IPv4 /24 network") + + # Test with /30 network (size == 4) + ip = IPAddr.new("192.168.1.0/30") + assert_equal(2, ip.usable_hosts_size, "Failed on IPv4 /30 network") + # Test with /31 network (size == 2) + ip = IPAddr.new("192.168.1.0/31") + assert_equal(0, ip.usable_hosts_size, "Failed on IPv4 /31 network") + + # Test with /32 network (size == 1) + ip = IPAddr.new("192.168.1.1/32") + assert_equal(0, ip.usable_hosts_size, "Failed on IPv4 /32 network") + end + + # Test usable_hosts_size for IPv6 networks + def test_usable_hosts_size_ipv6 + # Test with /64 network + ip6 = IPAddr.new("2001:db8::/64") + expected_size = 2**64 # Total number of addresses in /64 + assert_equal(expected_size, ip6.usable_hosts_size, "Failed on IPv6 /64 network") + + # Test with /126 network (size == 4) ip6 = IPAddr.new("2001:db8::/126") - assert_equal(4, ip6.usable_hosts_size) + assert_equal(4, ip6.usable_hosts_size, "Failed on IPv6 /126 network") + + # Test with /128 network (size == 1) + ip6 = IPAddr.new("2001:db8::1/128") + assert_equal(1, ip6.usable_hosts_size, "Failed on IPv6 /128 network") end # Edge case for IPv4 /32 subnet (single IP address) def test_edge_case_ipv4_single_ip ip = IPAddr.new("192.168.0.1/32") assert_equal(1, ip.ip_range_size) - assert_equal(0, ip.usable_hosts_size) + assert_equal(0, ip.usable_hosts_size) # If the code returns 0, keep this assertion actual = [] ip.each_host { |addr| actual << addr.to_s } - assert_empty(actual) + expected = ["192.168.0.1"] + assert_equal(expected, actual) end # Edge case for IPv6 /128 subnet (single IP address) @@ -648,10 +726,11 @@ def test_edge_case_ipv6_single_ip def test_edge_case_ipv4_point_to_point ip = IPAddr.new("192.168.0.0/31") assert_equal(2, ip.ip_range_size) - assert_equal(0, ip.usable_hosts_size) + assert_equal(0, ip.usable_hosts_size) # If the code returns 0, keep this assertion actual = [] ip.each_host { |addr| actual << addr.to_s } - assert_empty(actual) + expected = ["192.168.0.0", "192.168.0.1"] + assert_equal(expected, actual) end end From e58def3de984ae8a6cfd4cd77fd5fa4c4e4fa893 Mon Sep 17 00:00:00 2001 From: Tilo Date: Fri, 29 Nov 2024 15:47:11 +0100 Subject: [PATCH 4/5] Update lib/ipaddr/version.rb --- lib/ipaddr/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ipaddr/version.rb b/lib/ipaddr/version.rb index dee6e6e..7970b53 100644 --- a/lib/ipaddr/version.rb +++ b/lib/ipaddr/version.rb @@ -1,3 +1,3 @@ class IPAddr - VERSION = "1.2.8.pre1" + VERSION = "1.2.7" end From 0304d2928aadc6089326db906007c4bdca6755c8 Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Mon, 16 Dec 2024 13:31:17 -0800 Subject: [PATCH 5/5] simplify --- lib/ipaddr.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index ff4c38c..ea6e3a7 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -463,9 +463,7 @@ def each_host yield self when 2 # For /31 networks, both addresses are usable - (begin_addr..end_addr).each do |addr| - yield self.class.new(addr, @family) - end + each else # For larger networks, exclude network and broadcast addresses ((begin_addr + 1)...end_addr).each do |addr|