Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions ipaddr.gemspec
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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"]

Expand All @@ -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"
Expand Down
46 changes: 44 additions & 2 deletions lib/ipaddr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@
# p ipaddr3 #=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0>

class IPAddr
VERSION = "1.2.7"

# 32 bit mask for IPv4
IN4MASK = 0xffffffff
# 128 bit mask for IPv6
Expand Down Expand Up @@ -443,6 +441,50 @@ 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
case size
when 1
# For /32 networks, yield the single address
yield self
when 2
# For /31 networks, both addresses are usable
each
else
# 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
# For IPv6, include all addresses
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
Expand Down
3 changes: 3 additions & 0 deletions lib/ipaddr/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class IPAddr
VERSION = "1.2.7"
end
162 changes: 162 additions & 0 deletions test/test_ipaddr.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -571,4 +572,165 @@ 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
# 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, "Failed on IPv4 /24 network")

# 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
# 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, "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) # If the code returns 0, keep this assertion

actual = []
ip.each_host { |addr| actual << addr.to_s }
expected = ["192.168.0.1"]
assert_equal(expected, 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) # If the code returns 0, keep this assertion

actual = []
ip.each_host { |addr| actual << addr.to_s }
expected = ["192.168.0.0", "192.168.0.1"]
assert_equal(expected, actual)
end
end