I have done the following
Steps to reproduce
CIDR keeps the address exactly as parsed, so a block written from a host address keeps its host bits set. Containment then compares that unmasked value against the masked probe.
Add this to Tests/ContainerizationExtrasTests/TestCIDR.swift and run swift test --filter TestCIDR:
@Test func reproduce() throws {
let block = try CIDR("192.168.1.100/24")
#expect(block.contains(try IPAddress("192.168.1.100"))) // fails
#expect(block.contains(try IPAddress("192.168.1.0"))) // fails
// the type it wraps gets this right
let concrete = try CIDRv4("192.168.1.100/24")
#expect(concrete.contains(try IPv4Address("192.168.1.100"))) // passes
}
Current behavior
The wrapper disagrees with the type it wraps, in both families:
| expression |
CIDR |
CIDRv4 / CIDRv6 |
192.168.1.100/24 contains 192.168.1.100 |
false |
true |
192.168.1.100/24 contains 192.168.1.0 |
false |
true |
10.1.2.3/16 contains 10.1.99.99 |
false |
true |
2001:db8::1234/64 contains 2001:db8::1 |
false |
true |
CIDR.contains compares an unmasked stored address (Sources/ContainerizationExtras/CIDR.swift:116 and :118):
return network.value == (ip.value & prefix.prefixMask32)
whereas CIDRv4.contains masks both sides:
(address.value & prefix.prefixMask32) == (ip.value & prefix.prefixMask32)
A block already written in network form (192.168.1.0/24) is unaffected, which is why the current tests pass: every containment case in TestCIDR.swift uses a network address.
There is a second defect in the same accessors. lower drops the IPv6 zone while upper keeps it (CIDR.swift:94 vs :105, and CIDRv6.swift:81 vs :87):
2001:db8::5%lo0/126 -> lower = 2001:db8::4 (zone lost)
upper = 2001:db8::7%lo0
contains(lower) = false
Because contains compares zones, the missing zone on its own is enough to place the bound outside its own block.
Expected behavior
CIDR should agree with CIDRv4 and CIDRv6 on both containment and bounds: mask the stored address before comparing, and carry the address's zone into lower the way upper already does.
Environment
macOS 26.6 (25G72), Xcode 26.6, Swift 6.3, arm64
main @ ff44a5b683c80fceab875dba8a20ed24d7648c07
Relevant log output
✘ Expectation failed: block.contains(try IPAddress(ip))
cidr → "192.168.1.100/24", ip → "192.168.1.100"
✘ Expectation failed: (wrapper.contains(.v4(ip)) → false) == (concrete.contains(ip) → true)
cidr → "192.168.1.100/24"
✘ Expectation failed: (block → 2001:db8::5%lo0/126).contains(block.lower → 2001:db8::4)
Code of Conduct
I have done the following
mainatff44a5b683c80fceab875dba8a20ed24d7648c07Steps to reproduce
CIDRkeeps the address exactly as parsed, so a block written from a host address keeps its host bits set. Containment then compares that unmasked value against the masked probe.Add this to
Tests/ContainerizationExtrasTests/TestCIDR.swiftand runswift test --filter TestCIDR:Current behavior
The wrapper disagrees with the type it wraps, in both families:
CIDRCIDRv4/CIDRv6192.168.1.100/24contains192.168.1.100falsetrue192.168.1.100/24contains192.168.1.0falsetrue10.1.2.3/16contains10.1.99.99falsetrue2001:db8::1234/64contains2001:db8::1falsetrueCIDR.containscompares an unmasked stored address (Sources/ContainerizationExtras/CIDR.swift:116and:118):whereas
CIDRv4.containsmasks both sides:A block already written in network form (
192.168.1.0/24) is unaffected, which is why the current tests pass: every containment case inTestCIDR.swiftuses a network address.There is a second defect in the same accessors.
lowerdrops the IPv6 zone whileupperkeeps it (CIDR.swift:94vs:105, andCIDRv6.swift:81vs:87):Because
containscompares zones, the missing zone on its own is enough to place the bound outside its own block.Expected behavior
CIDRshould agree withCIDRv4andCIDRv6on both containment and bounds: mask the stored address before comparing, and carry the address's zone intolowerthe wayupperalready does.Environment
Relevant log output
✘ Expectation failed: block.contains(try IPAddress(ip)) cidr → "192.168.1.100/24", ip → "192.168.1.100" ✘ Expectation failed: (wrapper.contains(.v4(ip)) → false) == (concrete.contains(ip) → true) cidr → "192.168.1.100/24" ✘ Expectation failed: (block → 2001:db8::5%lo0/126).contains(block.lower → 2001:db8::4)Code of Conduct