diff --git a/.gitignore b/.gitignore index 1a10f15..9e673fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *.coverprofile .vscode -.idea/ \ No newline at end of file +.idea/ + +.codex +AGENTS.md \ No newline at end of file diff --git a/README.md b/README.md index f49fbf5..b41cde7 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,11 @@ func main() { fmt.Printf("Iterate through all %d IP addresses:\n", diff.Size()) ipIter := diff.IPIterator() for { - ip := ipIter.Next() - if ip == nil { + addr := ipIter.Next() + if !addr.IsValid() { break } - fmt.Println(ip) + fmt.Println(addr) } } ``` diff --git a/doc.go b/doc.go index e89c94c..dabc692 100644 --- a/doc.go +++ b/doc.go @@ -1,6 +1,5 @@ /* -Package iprange parses IPv4/IPv6 addresses from strings in IP range -format. +Package iprange parses IPv4/IPv6 addresses from strings in IP range format. The following IP range formats are supported: @@ -9,64 +8,39 @@ The following IP range formats are supported: 172.18.0.1-10 fd00::1-a 172.18.0.1-172.18.1.10 fd00::1-fd00::1:a -It takes a set of IP range strings, and returns a list of start-end IP -address pairs, which can then be automatically extended and normalized, -for instance: +It takes a set of IP range strings, and returns a list of start-end IP address +pairs, which can then be automatically extended and normalized, for instance: - v4Ranges, err := iprange.Parse("172.18.0.1", "172.18.0.0/24") // √ - v6Ranges, err := iprange.Parse("fd00::1", "fd00::/64") // √ - invalid, err := iprange.Parse("Invalid IP range string") // × - dual, err := iprange.Parse("172.18.0.1", "fd00::/64") // × + v4Ranges, err := iprange.Parse("172.18.0.1", "172.18.0.0/24") // valid + v6Ranges, err := iprange.Parse("fd00::1", "fd00::/64") // valid + invalid, err := iprange.Parse("Invalid IP range string") // invalid + dual, err := iprange.Parse("172.18.0.1", "fd00::/64") // invalid -When parsing an invalid IP range string, error errInvalidIPRangeFormat -will be returned, and dual-stack IP ranges are not allowed because this -approach is too complex and confusing. Use the following functions to -assert the errors: +When parsing an invalid IP range string, error errInvalidIPRangeFormat will be +returned, and dual-stack IP ranges are not allowed. Use the following functions +to assert the errors: func IsInvalidIPRangeFormat(err error) bool func IsDualStackIPRanges(err error) bool Use the interval methods of IPRanges to calculate the union, difference or -intersection of two IPRanges. They do not change the original parameters -(rr and rs), just calculate, and return the results. +intersection of two IPRanges. They do not change the original parameters (rs and +other), just calculate, and return the results. - func (rr *IPRanges) Union(rs *IPRanges) *IPRanges - func (rr *IPRanges) Diff(rs *IPRanges) *IPRanges - func (rr *IPRanges) Intersect(rs *IPRanges) *IPRanges + func (rs *IPRanges) Union(other *IPRanges) *IPRanges + func (rs *IPRanges) Diff(other *IPRanges) *IPRanges + func (rs *IPRanges) Intersect(other *IPRanges) *IPRanges -However, do not attempt to perform calculations on two IPRanges with -different IP versions, it won't work: - - res := v4Ranges.Diff(v6Ranges) // res will be equal to v4Ranges. - -The IPRanges can be converted into multiple net.IP (i.e. IP addresses) -or *net.IPNet (i.e. subnets) through their own iterators. Continuously -call the method Next() until nil is returned: +The IPRanges can be converted into multiple netip.Addr through iterator. +Continuously call the method Next() until an zero value is returned: ipIter := ranges.IPIterator() for { - ip := ipIter.Next() - if ip == nil { - break - } - // Do someting. - } - - cidrIter := ranges.CIDRIterator() - for { - cidr := cidrIter.Next() - if cidr == nil { - break - } - // Do someting. + addr := ipIter.Next() + if !addr.IsValid() { + break + } + // TODO } - -Finally, the inspiration for writing this package comes from - - CNI plugins: https://github.com/containernetworking/plugins - malfunkt/iprange: https://github.com/malfunkt/iprange - netaddr/netaddr: https://github.com/netaddr/netaddr - -both of which are great! */ package iprange diff --git a/example_test.go b/example_test.go index ca030a4..850feed 100644 --- a/example_test.go +++ b/example_test.go @@ -4,7 +4,7 @@ import ( "fmt" "log" "math/big" - "net" + "net/netip" "github.com/iiiceoo/iprange" ) @@ -26,200 +26,51 @@ func ExampleParse() { // [fd00::1-fd00::a fd00::1-fd00::1:a] } -func ExampleIPRanges_Version() { - v4Ranges, err := iprange.Parse("172.18.0.1", "172.18.0.0/24") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - v6Ranges, err := iprange.Parse("fd00::1-a", "fd00::1-fd00::1:a") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - zero := iprange.IPRanges{} - - fmt.Println(v4Ranges.Version()) - fmt.Println(v6Ranges.Version()) - fmt.Println(zero.Version()) - // Output: - // IPv4 - // IPv6 - // Unknown -} - func ExampleIPRanges_Contains() { ranges, err := iprange.Parse("172.18.0.0/24") if err != nil { log.Fatalf("error parsing IP ranges: %v", err) } - fmt.Println(ranges.Contains(net.ParseIP("172.18.0.1"))) - fmt.Println(ranges.Contains(net.ParseIP("172.19.0.1"))) - fmt.Println(ranges.Contains(net.ParseIP("fd00::1"))) + fmt.Println(ranges.Contains(netip.MustParseAddr("172.18.0.1"))) + fmt.Println(ranges.Contains(netip.MustParseAddr("172.19.0.1"))) + fmt.Println(ranges.Contains(netip.MustParseAddr("fd00::1"))) // Output: // true // false // false } -func ExampleIPRanges_MergeEqual() { - ranges1, err := iprange.Parse("172.18.0.0/24") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - ranges2, err := iprange.Parse("172.18.0.100-255", "172.18.0.0-200") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - - fmt.Println(ranges1.MergeEqual(ranges2)) - // Output: - // true -} - -func ExampleIPRanges_Equal() { - ranges1, err := iprange.Parse("172.18.0.0/24") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - ranges2, err := iprange.Parse("172.18.0.0-255") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - ranges3, err := iprange.Parse("172.18.0.100-255", "172.18.0.0-200") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - - fmt.Println(ranges1.Equal(ranges2)) - fmt.Println(ranges1.Equal(ranges3)) - // Output: - // true - // false -} - -func ExampleIPRanges_Size() { - ranges, err := iprange.Parse("172.18.0.0/24") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - zero := iprange.IPRanges{} - - fmt.Println(ranges.Size()) - fmt.Println(zero.Size()) - // Output: - // 256 - // 0 -} - -func ExampleIPRanges_Merge() { - ranges, err := iprange.Parse("172.18.0.201", "172.18.0.100-200", "172.18.0.1-150") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - - fmt.Println(ranges) - fmt.Println(ranges.Merge()) - // Output: - // [172.18.0.201 172.18.0.100-172.18.0.200 172.18.0.1-172.18.0.150] - // 172.18.0.1-172.18.0.201 -} - func ExampleIPRanges_Union() { - ranges1, err := iprange.Parse("172.18.0.20-30", "172.18.0.1-25") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - ranges2, err := iprange.Parse("172.18.0.5-25") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } + ranges1, _ := iprange.Parse("172.18.0.20-30", "172.18.0.1-25") + ranges2, _ := iprange.Parse("172.18.0.5-25") fmt.Println(ranges1.Union(ranges2)) + fmt.Println(ranges1) // Output: // 172.18.0.1-172.18.0.30 -} - -func ExampleIPRanges_Diff() { - ranges1, err := iprange.Parse("172.18.0.20-30", "172.18.0.0-25") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - ranges2, err := iprange.Parse("172.18.0.4-26") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - - fmt.Println(ranges1.Diff(ranges2)) - // Output: - // [172.18.0.0/30 172.18.0.27-172.18.0.30] -} - -func ExampleIPRanges_Intersect() { - ranges1, err := iprange.Parse("172.18.0.20-30", "172.18.0.1-25") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - ranges2, err := iprange.Parse("172.18.0.5-25") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - - fmt.Println(ranges1.Intersect(ranges2)) - // Output: - // 172.18.0.5-172.18.0.25 -} - -func ExampleIPRanges_Slice() { - ranges, err := iprange.Parse("172.18.0.0-3", "172.18.0.10-14") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - - fmt.Println(ranges.Slice(big.NewInt(2), big.NewInt(-2))) - // Output: - // [172.18.0.2/31 172.18.0.10-172.18.0.13] -} - -func ExampleIPRanges_IsOverlap() { - ranges1, err := iprange.Parse("172.18.0.20-30", "172.18.0.25") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - ranges2, err := iprange.Parse("172.18.0.0/16") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - - fmt.Println(ranges1.IsOverlap()) - fmt.Println(ranges2.IsOverlap()) - // Output: - // true - // false + // [172.18.0.20-172.18.0.30 172.18.0.1-172.18.0.25] } func ExampleIPRanges_IPIterator() { - ranges, err := iprange.Parse("172.18.0.1-3") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } + ranges, _ := iprange.Parse("172.18.0.1-3") iter := ranges.IPIterator() for { - ip := iter.Next() - if ip == nil { + addr := iter.Next() + if !addr.IsValid() { break } - fmt.Println(ip) + fmt.Println(addr) } iter.Reset() - n := big.NewInt(2) for { - ip := iter.NextN(n) - if ip == nil { + addr := iter.NextN(big.NewInt(2)) + if !addr.IsValid() { break } - fmt.Println(ip) + fmt.Println(addr) } // Output: @@ -260,23 +111,3 @@ func ExampleIPRanges_BlockIterator() { // 172.18.0.4 // 172.18.0.4 } - -func ExampleIPRanges_CIDRIterator() { - ranges, err := iprange.Parse("172.18.0.0-255", "172.18.0.1-3") - if err != nil { - log.Fatalf("error parsing IP ranges: %v", err) - } - - iter := ranges.CIDRIterator() - for { - cidr := iter.Next() - if cidr == nil { - break - } - fmt.Println(cidr) - } - // Output: - // 172.18.0.0/24 - // 172.18.0.1/32 - // 172.18.0.2/31 -} diff --git a/go.mod b/go.mod index de14f5f..02922d4 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,3 @@ module github.com/iiiceoo/iprange go 1.22 - -require ( - github.com/brunoga/deep v1.2.5 - github.com/google/go-cmp v0.7.0 -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 76c2187..0000000 --- a/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/brunoga/deep v1.2.5 h1:bigq4eooqbeJXfvTfZBn3AH3B1iW+rtetxVeh0GiLrg= -github.com/brunoga/deep v1.2.5/go.mod h1:GDV6dnXqn80ezsLSZ5Wlv1PdKAWAO4L5PnKYtv2dgaI= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= diff --git a/ip.go b/ip.go index 593c6de..cd0b04e 100644 --- a/ip.go +++ b/ip.go @@ -2,102 +2,80 @@ package iprange import ( "math/big" - "net" + "net/netip" ) -var bigInt = [...]*big.Int{ - big.NewInt(0), - big.NewInt(1), +// ip wraps netip.Addr in order to expand the method of it. +type ip struct { + addr netip.Addr } -// xIP wraps net.IP in order to expand the method of net.IP. -type xIP struct { - net.IP -} - -// version returns the IP version of xIP: -// -// 1: IPv4 -// 2: IPv6 -// 0: not an IP: Unknown -func (ip xIP) version() family { - nIP := normalizeIP(ip.IP) - if nIP == nil { - return Unknown - } - - if len(nIP) == net.IPv4len { +// version returns IP version. +func (ip ip) version() family { + if ip.addr.Is4() { return IPv4 } return IPv6 } -// next returns the next IP address of xIP. -func (ip xIP) next() xIP { - i := ipToInt(ip.IP) - i.Add(i, bigInt[1]) - - return xIP{intToIP(i)} +// next returns the IP following i. If there is none, it returns the zero IP. +func (ip ip) next() ip { + ip.addr = ip.addr.Next() + return ip } -// nextN returns the next nth IP address of xIP. -func (ip xIP) nextN(n *big.Int) xIP { - if n.Sign() == 0 { +// nextN returns the next nth IP following i. If there is none, it returns the +// zero IP. buf is an optional big.Int used to avoid memory allocations. +func (ip ip) nextN(n, buf *big.Int) ip { + if n == nil || n.Sign() <= 0 { return ip } - i := ipToInt(ip.IP) - i.Add(i, n) - - return xIP{intToIP(i)} -} + if n.IsUint64() && n.Uint64() == 1 { + return ip.next() + } -// prev returns the previous IP address of xIP. -func (ip xIP) prev() xIP { - i := ipToInt(ip.IP) - i.Sub(i, bigInt[1]) + if buf == nil { + buf = new(big.Int) + } - return xIP{intToIP(i)} -} + buf.SetBytes(ip.addr.AsSlice()) + buf.Add(buf, n) + ip.addr = intToAddr(ip.version(), buf) -// cmp compares xIP ip and ip2 with the same IP version and returns: -// -// -1: ip < ip2 -// 0: ip == ip2 -// +1: ip > ip2 -func (ip xIP) cmp(ip2 xIP) int { - nIP1 := normalizeIP(ip.IP) - nIP2 := normalizeIP(ip2.IP) - - return ipToInt(nIP1).Cmp(ipToInt(nIP2)) + return ip } -// ipToInt converts net.IP to a big number. -func ipToInt(ip net.IP) *big.Int { - return new(big.Int).SetBytes(ip) +// prev returns the IP before i. If there is none, it returns the zero IP. +func (ip ip) prev() ip { + ip.addr = ip.addr.Prev() + return ip } -// ipToInt converts big number to a net.IP. -func intToIP(i *big.Int) net.IP { - return net.IP(i.Bytes()) +// cmp returns an integer comparing two IPs: +// +// -1: ip < other +// 0: ip == other +// +1: ip > other +func (ip ip) cmp(other ip) int { + return ip.addr.Compare(other.addr) } -// normalizeIP normalizes net.IP by family: -// -// IPv4: 4-byte form -// IPv6: 16-byte form -// not an IP: nil -func normalizeIP(ip net.IP) net.IP { - if v := ip.To4(); v != nil { - return v +// intToAddr converts big.Int to netip.Addr. +func intToAddr(version family, i *big.Int) netip.Addr { + var b [16]byte + if version == IPv4 { + i.FillBytes(b[0:4]) + return netip.AddrFrom4([4]byte(b[0:4])) } - return ip.To16() + i.FillBytes(b[:]) + return netip.AddrFrom16(b) } -// maxXIP returns the larger xIP between x and y. -func maxXIP(x, y xIP) xIP { +// maxIP returns the numerically higher IP. +func maxIP(x, y ip) ip { if x.cmp(y) > 0 { return x } @@ -105,29 +83,11 @@ func maxXIP(x, y xIP) xIP { return y } -// minXIP returns the smaller xIP between x and y. -func minXIP(x, y xIP) xIP { +// minIP returns the numerically lower IP. +func minIP(x, y ip) ip { if x.cmp(y) < 0 { return x } return y } - -// max returns the larger of x and y. -func maxN(x, y int) int { - if x > y { - return x - } - - return y -} - -// min returns the smaller of x and y. -func minN(x, y int) int { - if x < y { - return x - } - - return y -} diff --git a/iterator.go b/iterator.go index 1094ad5..85ad416 100644 --- a/iterator.go +++ b/iterator.go @@ -2,116 +2,120 @@ package iprange import ( "math/big" - "net" + "net/netip" ) -type ipIterator struct { +type IPIterator struct { ranges []ipRange rangeIndex int - current xIP + current ip freeSize *big.Int + + buf *big.Int } // IPIterator generates a new iterator for scanning IP addresses. -func (rr *IPRanges) IPIterator() *ipIterator { - return &ipIterator{ - ranges: rr.ranges, +func (rs *IPRanges) IPIterator() *IPIterator { + return &IPIterator{ + ranges: rs.ranges, + buf: new(big.Int), } } -// Next returns the next IP address. If the ipIterator has been exhausted, -// return nil. -func (ii *ipIterator) Next() net.IP { +// Next returns the next IP address. If the IPIterator has been exhausted, +// return the zero Addr. +func (ii *IPIterator) Next() netip.Addr { n := len(ii.ranges) if n == 0 { - return nil + return netip.Addr{} } - if ii.current.IP == nil { + if !ii.current.addr.IsValid() { ii.freeSize = new(big.Int) - ii.freeSize.Set(ii.ranges[0].size()) + ii.freeSize.Set(ii.ranges[0].size(ii.buf)) ii.freeSize.Sub(ii.freeSize, bigInt[1]) - ii.current.IP = ii.ranges[0].start.IP - return ii.current.IP + ii.current = ii.ranges[0].start + return ii.current.addr } - if !ii.current.Equal(ii.ranges[ii.rangeIndex].end.IP) { + if ii.current.cmp(ii.ranges[ii.rangeIndex].end) != 0 { ii.freeSize.Sub(ii.freeSize, bigInt[1]) ii.current = ii.current.next() - return ii.current.IP + return ii.current.addr } ii.rangeIndex++ if ii.rangeIndex == n { - return nil + return netip.Addr{} } - ii.freeSize.Set(ii.ranges[ii.rangeIndex].size()) + + ii.freeSize.Set(ii.ranges[ii.rangeIndex].size(ii.buf)) ii.freeSize.Sub(ii.freeSize, bigInt[1]) ii.current = ii.ranges[ii.rangeIndex].start - return ii.current.IP + return ii.current.addr } -// NextN returns the next nth IP address. If the ipIterator has been exhausted, -// return nil. If n <= 0, it is equivalent to NextN(1). -func (ii *ipIterator) NextN(n *big.Int) net.IP { +// NextN returns the next nth IP address. If the IPIterator has been exhausted, +// return the zero Addr. If n <= 0, it is equivalent to NextN(1). +func (ii *IPIterator) NextN(n *big.Int) netip.Addr { l := len(ii.ranges) if l == 0 { - return nil + return netip.Addr{} } - if n.Sign() <= 0 { + if n == nil || n.Sign() <= 0 { n = big.NewInt(1) } - if ii.current.IP == nil { + if !ii.current.addr.IsValid() { n = new(big.Int).Set(n) for { - size := ii.ranges[ii.rangeIndex].size() + size := ii.ranges[ii.rangeIndex].size(ii.buf) if n.Cmp(size) <= 0 { ii.freeSize = new(big.Int) ii.freeSize.Set(size) ii.freeSize.Sub(ii.freeSize, n) n.Sub(n, bigInt[1]) - ii.current.IP = ii.ranges[ii.rangeIndex].start.nextN(n).IP - return ii.current.IP + ii.current = ii.ranges[ii.rangeIndex].start.nextN(n, ii.buf) + return ii.current.addr } n.Sub(n, size) ii.rangeIndex++ if ii.rangeIndex == l { - return nil + return netip.Addr{} } } } if n.Cmp(ii.freeSize) <= 0 { ii.freeSize.Sub(ii.freeSize, n) - ii.current.IP = ii.current.nextN(n).IP - return ii.current.IP + ii.current = ii.current.nextN(n, ii.buf) + return ii.current.addr } ii.rangeIndex++ if ii.rangeIndex == l { - return nil + return netip.Addr{} } n = new(big.Int).Set(n) n.Sub(n, ii.freeSize) - ii.freeSize.Set(ii.ranges[ii.rangeIndex].size()) + ii.freeSize.Set(ii.ranges[ii.rangeIndex].size(ii.buf)) ii.freeSize.Sub(ii.freeSize, n) n.Sub(n, bigInt[1]) - ii.current.IP = ii.ranges[ii.rangeIndex].start.nextN(n).IP + ii.current = ii.ranges[ii.rangeIndex].start.nextN(n, ii.buf) - return ii.current.IP + return ii.current.addr } // Reset resets IP iterator. -func (ii *ipIterator) Reset() { +func (ii *IPIterator) Reset() { ii.rangeIndex = 0 - ii.current.IP = nil + ii.current = ip{} } -type blockIterator struct { +type BlockIterator struct { ranges *IPRanges size *big.Int blockSize *big.Int @@ -121,21 +125,21 @@ type blockIterator struct { // BlockIterator generates a new iterator for scanning IP blocks. blockSize // should be at least 1, which is somewhat equivalent to IPIterator. -func (rr *IPRanges) BlockIterator(blockSize *big.Int) *blockIterator { +func (rs *IPRanges) BlockIterator(blockSize *big.Int) *BlockIterator { if blockSize == nil || blockSize.Sign() <= 0 { blockSize = big.NewInt(1) } - return &blockIterator{ - ranges: rr, - size: rr.Size(), + return &BlockIterator{ + ranges: rs, + size: rs.Size(), blockSize: blockSize, } } -// Next returns the next IP block. If the blockIterator has been exhausted, +// Next returns the next IP block. If the BlockIterator has been exhausted, // return nil. -func (bi *blockIterator) Next() *IPRanges { +func (bi *BlockIterator) Next() *IPRanges { if bi.size.Sign() == 0 { return nil } @@ -155,14 +159,14 @@ func (bi *blockIterator) Next() *IPRanges { return bi.ranges.Slice(bi.start, bi.end) } -// NextN returns the next nth IP block. If the blockIterator has been -// exhausted, return nil. If n <= 0, it is equivalent to NextN(1). -func (bi *blockIterator) NextN(n *big.Int) *IPRanges { +// NextN returns the next nth IP block. If the BlockIterator has been +// exhausted, return nil. If n <= 0, it is equivalent to NextN(1). +func (bi *BlockIterator) NextN(n *big.Int) *IPRanges { if bi.size.Sign() == 0 { return nil } - if n.Sign() <= 0 { + if n == nil || n.Sign() <= 0 { n = big.NewInt(1) } @@ -185,91 +189,7 @@ func (bi *blockIterator) NextN(n *big.Int) *IPRanges { } // Reset resets IP block iterator. -func (bi *blockIterator) Reset() { +func (bi *BlockIterator) Reset() { bi.start = nil bi.end = nil } - -type cidrIterator struct { - ranges []ipRange - rangeIndex int - - ipBitLen int - lastInt *big.Int - current *big.Int -} - -// CIDRIterator generates a new iterator for scanning CIDR. -func (rr *IPRanges) CIDRIterator() *cidrIterator { - iter := &cidrIterator{ - ranges: rr.ranges, - } - - if len(iter.ranges) != 0 { - r := iter.ranges[0] - iter.ipBitLen = len(r.start.IP) * 8 - iter.lastInt = ipToInt(r.end.IP) - iter.current = ipToInt(r.start.IP) - } - - return iter -} - -// Next returns the next CIDR. If the cidrIterator has been exhausted, -// return nil. -func (ci *cidrIterator) Next() *net.IPNet { - n := len(ci.ranges) - if n == 0 { - return nil - } - - if ci.current.Cmp(ci.lastInt) <= 0 { - return ci.next() - } - - ci.rangeIndex++ - if ci.rangeIndex == n { - return nil - } - ci.lastInt = ipToInt(ci.ranges[ci.rangeIndex].end.IP) - ci.current = ipToInt(ci.ranges[ci.rangeIndex].start.IP) - - return ci.next() -} - -func (ci *cidrIterator) next() *net.IPNet { - delta := new(big.Int).Sub(ci.lastInt, ci.current) - delta.Add(delta, bigInt[1]) - - curIP := intToIP(ci.current) - nbits := minN(righthandZeroBits(curIP), delta.BitLen()-1) - - incr := new(big.Int).Lsh(bigInt[1], uint(nbits)) - ci.current.Add(ci.current, incr) - - return &net.IPNet{ - IP: curIP, - Mask: net.CIDRMask(ci.ipBitLen-nbits, ci.ipBitLen), - } -} - -// righthandZeroBits counts the number of zero bits on the right hand -// side of bb. -func righthandZeroBits(bb []byte) int { - n := 0 - for i := len(bb) - 1; i >= 0; i-- { - b := bb[i] - if b == 0 { - n += 8 - continue - } - - for b&1 == 0 { - n++ - b >>= 1 - } - break - } - - return n -} diff --git a/iterator_test.go b/iterator_test.go index ae46042..46f58ce 100644 --- a/iterator_test.go +++ b/iterator_test.go @@ -2,64 +2,43 @@ package iprange import ( "math/big" - "net" + "net/netip" + "reflect" "testing" - - "github.com/google/go-cmp/cmp" ) var ipRangesIPIteratorNextTests = []struct { name string ranges *IPRanges - want []net.IP + want []netip.Addr }{ { name: "IPv4", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 10).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 2).To4()}, - }, - }, - }, - want: []net.IP{ - net.IPv4(172, 18, 0, 10).To4(), - net.IPv4(172, 18, 0, 1).To4(), - net.IPv4(172, 18, 0, 2).To4(), + ranges: mustRanges( + IPv4, + [2]string{"172.18.0.10", "172.18.0.10"}, + [2]string{"172.18.0.1", "172.18.0.2"}, + ), + want: []netip.Addr{ + netip.MustParseAddr("172.18.0.10"), + netip.MustParseAddr("172.18.0.1"), + netip.MustParseAddr("172.18.0.2"), }, }, { name: "IPv6", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::a")}, - end: xIP{net.ParseIP("fd00::a")}, - }, - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::2")}, - }, - }, - }, - want: []net.IP{ - net.ParseIP("fd00::a"), - net.ParseIP("fd00::1"), - net.ParseIP("fd00::2"), + ranges: mustRanges( + IPv6, + [2]string{"fd00::a", "fd00::a"}, + [2]string{"fd00::1", "fd00::2"}, + ), + want: []netip.Addr{ + netip.MustParseAddr("fd00::a"), + netip.MustParseAddr("fd00::1"), + netip.MustParseAddr("fd00::2"), }, }, - { - name: "zero", - ranges: &IPRanges{}, - want: nil, - }, + {"zero", &IPRanges{}, nil}, } func TestIPRangesIPIteratorNext(t *testing.T) { @@ -70,17 +49,17 @@ func TestIPRangesIPIteratorNext(t *testing.T) { t.Parallel() iter := test.ranges.IPIterator() - var ips []net.IP + var addrs []netip.Addr for { - ip := iter.Next() - if ip == nil { + addr := iter.Next() + if !addr.IsValid() { break } - ips = append(ips, ip) + addrs = append(addrs, addr) } - if !cmp.Equal(ips, test.want) { - t.Fatalf("IPRanges(%v).IPIterator().Next() = %v, want %v", test.ranges, ips, test.want) + if !reflect.DeepEqual(addrs, test.want) { + t.Fatalf("IPRanges(%v).IPIterator().Next() = %v, want %v", test.ranges, addrs, test.want) } }) } @@ -90,75 +69,52 @@ var ipRangesIPIteratorNextNTests = []struct { name string ranges *IPRanges n *big.Int - want []net.IP + want []netip.Addr }{ { name: "IPv4", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 10).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 2).To4()}, - }, - }, - }, + ranges: mustRanges( + IPv4, + [2]string{"172.18.0.10", "172.18.0.10"}, + [2]string{"172.18.0.1", "172.18.0.2"}, + ), n: big.NewInt(0), - want: []net.IP{ - net.IPv4(172, 18, 0, 10).To4(), - net.IPv4(172, 18, 0, 1).To4(), - net.IPv4(172, 18, 0, 2).To4(), + want: []netip.Addr{ + netip.MustParseAddr("172.18.0.10"), + netip.MustParseAddr("172.18.0.1"), + netip.MustParseAddr("172.18.0.2"), }, }, { name: "IPv6", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::a")}, - end: xIP{net.ParseIP("fd00::a")}, - }, - { - start: xIP{net.ParseIP("fd00::2")}, - end: xIP{net.ParseIP("fd00::3")}, - }, - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::2")}, - }, - }, - }, + ranges: mustRanges( + IPv6, + [2]string{"fd00::a", "fd00::a"}, + [2]string{"fd00::2", "fd00::3"}, + [2]string{"fd00::1", "fd00::2"}, + ), n: big.NewInt(2), - want: []net.IP{ - net.ParseIP("fd00::2"), - net.ParseIP("fd00::1"), + want: []netip.Addr{ + netip.MustParseAddr("fd00::2"), + netip.MustParseAddr("fd00::1"), }, }, { - name: "out of ranges", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 10).To4()}, - }, - }, + name: "nil n", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.2"}), + n: nil, + want: []netip.Addr{ + netip.MustParseAddr("172.18.0.1"), + netip.MustParseAddr("172.18.0.2"), }, - n: big.NewInt(2), - want: nil, }, { - name: "zero", - ranges: &IPRanges{}, - n: big.NewInt(1), + name: "out of ranges", + ranges: mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.10"}), + n: big.NewInt(2), want: nil, }, + {"zero", &IPRanges{}, big.NewInt(1), nil}, } func TestIPRangesIPIteratorNextN(t *testing.T) { @@ -169,17 +125,17 @@ func TestIPRangesIPIteratorNextN(t *testing.T) { t.Parallel() iter := test.ranges.IPIterator() - var ips []net.IP + var addrs []netip.Addr for { - ip := iter.NextN(test.n) - if ip == nil { + addr := iter.NextN(test.n) + if !addr.IsValid() { break } - ips = append(ips, ip) + addrs = append(addrs, addr) } - if !cmp.Equal(ips, test.want) { - t.Fatalf("IPRanges(%v).IPIterator().NextN(%v) = %v, want %v", test.ranges, test.n, ips, test.want) + if !reflect.DeepEqual(addrs, test.want) { + t.Fatalf("IPRanges(%v).IPIterator().NextN(%v) = %v, want %v", test.ranges, test.n, addrs, test.want) } }) } @@ -193,106 +149,33 @@ var ipRangesBlockIteratorNextTests = []struct { }{ { name: "IPv4", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 10).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 2).To4()}, - }, - }, - }, + ranges: mustRanges( + IPv4, + [2]string{"172.18.0.10", "172.18.0.10"}, + [2]string{"172.18.0.1", "172.18.0.2"}, + ), blockSize: big.NewInt(0), want: []*IPRanges{ - { - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 10).To4()}, - }, - }, - }, - { - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 1).To4()}, - }, - }, - }, - { - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 2).To4()}, - end: xIP{net.IPv4(172, 18, 0, 2).To4()}, - }, - }, - }, + mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.10"}), + mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), + mustRanges(IPv4, [2]string{"172.18.0.2", "172.18.0.2"}), }, }, { name: "IPv6", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::a")}, - end: xIP{net.ParseIP("fd00::a")}, - }, - { - start: xIP{net.ParseIP("fd00::6")}, - end: xIP{net.ParseIP("fd00::9")}, - }, - }, - }, + ranges: mustRanges( + IPv6, + [2]string{"fd00::a", "fd00::a"}, + [2]string{"fd00::6", "fd00::9"}, + ), blockSize: big.NewInt(2), want: []*IPRanges{ - { - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::a")}, - end: xIP{net.ParseIP("fd00::a")}, - }, - { - start: xIP{net.ParseIP("fd00::6")}, - end: xIP{net.ParseIP("fd00::6")}, - }, - }, - }, - { - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::7")}, - end: xIP{net.ParseIP("fd00::8")}, - }, - }, - }, - { - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::9")}, - end: xIP{net.ParseIP("fd00::9")}, - }, - }, - }, + mustRanges(IPv6, [2]string{"fd00::a", "fd00::a"}, [2]string{"fd00::6", "fd00::6"}), + mustRanges(IPv6, [2]string{"fd00::7", "fd00::8"}), + mustRanges(IPv6, [2]string{"fd00::9", "fd00::9"}), }, }, - { - name: "zero", - ranges: &IPRanges{}, - blockSize: big.NewInt(1), - want: nil, - }, + {"zero", &IPRanges{}, big.NewInt(1), nil}, } func TestIPRangesBlockIteratorNext(t *testing.T) { @@ -312,8 +195,8 @@ func TestIPRangesBlockIteratorNext(t *testing.T) { ranges = append(ranges, r) } - if !cmp.Equal(ranges, test.want) { - t.Fatalf("IPRanges(%v).BlockIterator(%v).Next() = %v, want %v", test.ranges, test.blockSize, ranges, test.want) + if !reflect.DeepEqual(ranges, test.want) { + t.Fatalf("IPRanges(%v).BlockIterator(%v).Next() = %#v, want %#v", test.ranges, test.blockSize, ranges, test.want) } }) } @@ -328,96 +211,45 @@ var ipRangesBlockIteratorNextNTests = []struct { }{ { name: "IPv4", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 10).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 2).To4()}, - }, - }, - }, + ranges: mustRanges( + IPv4, + [2]string{"172.18.0.10", "172.18.0.10"}, + [2]string{"172.18.0.1", "172.18.0.2"}, + ), blockSize: big.NewInt(1), n: big.NewInt(0), want: []*IPRanges{ - { - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 10).To4()}, - }, - }, - }, - { - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 1).To4()}, - }, - }, - }, - { - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 2).To4()}, - end: xIP{net.IPv4(172, 18, 0, 2).To4()}, - }, - }, - }, + mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.10"}), + mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), + mustRanges(IPv4, [2]string{"172.18.0.2", "172.18.0.2"}), }, }, { name: "IPv6", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::a")}, - end: xIP{net.ParseIP("fd00::a")}, - }, - { - start: xIP{net.ParseIP("fd00::6")}, - end: xIP{net.ParseIP("fd00::b")}, - }, - }, - }, + ranges: mustRanges( + IPv6, + [2]string{"fd00::a", "fd00::a"}, + [2]string{"fd00::6", "fd00::b"}, + ), blockSize: big.NewInt(2), n: big.NewInt(2), want: []*IPRanges{ - { - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::7")}, - end: xIP{net.ParseIP("fd00::8")}, - }, - }, - }, - { - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::b")}, - end: xIP{net.ParseIP("fd00::b")}, - }, - }, - }, + mustRanges(IPv6, [2]string{"fd00::7", "fd00::8"}), + mustRanges(IPv6, [2]string{"fd00::b", "fd00::b"}), }, }, { - name: "zero", - ranges: &IPRanges{}, + name: "nil n", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.3"}), blockSize: big.NewInt(1), - n: big.NewInt(1), - want: nil, + n: nil, + want: []*IPRanges{ + mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), + mustRanges(IPv4, [2]string{"172.18.0.2", "172.18.0.2"}), + mustRanges(IPv4, [2]string{"172.18.0.3", "172.18.0.3"}), + }, }, + {"zero", &IPRanges{}, big.NewInt(1), big.NewInt(1), nil}, } func TestIPRangesBlockIteratorNextN(t *testing.T) { @@ -437,104 +269,8 @@ func TestIPRangesBlockIteratorNextN(t *testing.T) { ranges = append(ranges, r) } - if !cmp.Equal(ranges, test.want) { - t.Fatalf("IPRanges(%v).BlockIterator(%v).NextN(%v) = %v, want %v", test.ranges, test.blockSize, test.n, ranges, test.want) - } - }) - } -} - -var ipRangesCIDRIteratorTests = []struct { - name string - ranges *IPRanges - want []*net.IPNet -}{ - { - name: "IPv4", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 1, 0).To4()}, - end: xIP{net.IPv4(172, 18, 1, 255).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 3).To4()}, - }, - }, - }, - want: []*net.IPNet{ - { - IP: net.IPv4(172, 18, 1, 0).To4(), - Mask: net.CIDRMask(24, 32), - }, - { - IP: net.IPv4(172, 18, 0, 1).To4(), - Mask: net.CIDRMask(32, 32), - }, - { - IP: net.IPv4(172, 18, 0, 2).To4(), - Mask: net.CIDRMask(31, 32), - }, - }, - }, - { - name: "IPv6", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::ffff:ffff:ffff:ffff")}, - }, - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::3")}, - }, - }, - }, - want: []*net.IPNet{ - { - IP: net.ParseIP("fd00::0"), - Mask: net.CIDRMask(64, 128), - }, - { - IP: net.ParseIP("fd00::1"), - Mask: net.CIDRMask(128, 128), - }, - { - IP: net.ParseIP("fd00::2"), - Mask: net.CIDRMask(127, 128), - }, - }, - }, - { - name: "zero", - ranges: &IPRanges{}, - want: nil, - }, -} - -func TestIPRangesCIDRIterator(t *testing.T) { - t.Parallel() - for _, test := range ipRangesCIDRIteratorTests { - test := test - t.Run(test.name, func(t *testing.T) { - t.Parallel() - iter := test.ranges.CIDRIterator() - - var ipNets []*net.IPNet - for { - ipNet := iter.Next() - if ipNet == nil { - break - } - ipNets = append(ipNets, ipNet) - } - - if !cmp.Equal(ipNets, test.want) { - t.Fatalf("IPRanges(%v).CIDRIterator() = %v, want %v", test.ranges, ipNets, test.want) + if !reflect.DeepEqual(ranges, test.want) { + t.Fatalf("IPRanges(%v).BlockIterator(%v).NextN(%v) = %#v, want %#v", test.ranges, test.blockSize, test.n, ranges, test.want) } }) } diff --git a/range.go b/range.go index a76bf4b..de52278 100644 --- a/range.go +++ b/range.go @@ -1,160 +1,160 @@ package iprange import ( + "errors" "fmt" "math/big" "net" + "net/netip" "strings" ) -// The core abstraction of the IP range concept, which uses the starting -// and ending IP addresses to represent any IP range of any size. +var bigInt = [...]*big.Int{ + big.NewInt(0), + big.NewInt(1), +} + +// The core abstraction of the IP range concept, which uses the starting and +// ending IP addresses to represent any IP range of any size. type ipRange struct { - start xIP - end xIP + start ip + end ip } -// parse parses the IP range format string as ipRange that records the -// starting and ending IP addresses. The error errInvalidIPRangeFormat -// wiil be returned when r is invalid. -func parse(r string) (*ipRange, error) { - if r == "" { - return nil, fmt.Errorf(`%w: ""`, errInvalidIPRangeFormat) +// parse parses the IP range format string as ipRange that records the starting +// and ending IP addresses. The error errInvalidIPRangeFormat will be returned +// when format is invalid. +func parse(format string) (ipRange, error) { + fmtErr := fmt.Errorf("%w %s", errInvalidIPRangeFormat, format) + if format == "" { + return ipRange{}, fmtErr } - fmtErr := fmt.Errorf("%w: %s", errInvalidIPRangeFormat, r) - // 172.18.0.0/24 - // fd00::/64 - if strings.Contains(r, "/") { - ip, ipNet, err := net.ParseCIDR(r) + // 172.18.0.0/24 fd00::/64 + if strings.Contains(format, "/") { + prefix, err := netip.ParsePrefix(format) if err != nil { - return nil, fmtErr + return ipRange{}, errors.Join(fmtErr, err) } - n := len(ipNet.IP) - lastIP := make(net.IP, 0, n) - for i := 0; i < n; i++ { - lastIP = append(lastIP, ipNet.IP[i]|^ipNet.Mask[i]) + bytes := prefix.Masked().Addr().AsSlice() + mask := net.CIDRMask(prefix.Bits(), len(bytes)*8) + for i := 0; i < len(bytes); i++ { + bytes[i] |= ^mask[i] } + lastAddr, _ := netip.AddrFromSlice(bytes) - return &ipRange{ - start: xIP{normalizeIP(ip)}, - end: xIP{normalizeIP(lastIP)}, + return ipRange{ + start: ip{addr: prefix.Addr().Unmap()}, + end: ip{addr: lastAddr.Unmap()}, }, nil } - before, after, found := strings.Cut(r, "-") - if found { - startIP := net.ParseIP(before) - if startIP == nil { - return nil, fmtErr + before, after, found := strings.Cut(format, "-") + if !found { + // 172.18.0.1 fd00::1 + addr, err := netip.ParseAddr(format) + if err != nil { + return ipRange{}, errors.Join(fmtErr, err) } - endIP := net.ParseIP(after) - if endIP == nil { - // 172.18.0.1-10 - // fd00::1-a - index := strings.LastIndex(before, ".") - if index == -1 { - index = strings.LastIndex(before, ":") - } - after = before[:index+1] + after - endIP = net.ParseIP(after) - if endIP == nil { - return nil, fmtErr - } - - start := xIP{normalizeIP(startIP)} - end := xIP{normalizeIP(endIP)} - if end.cmp(start) < 0 { - return nil, fmtErr - } - - return &ipRange{ - start: start, - end: end, - }, nil - } + addr = addr.Unmap() + return ipRange{ + start: ip{addr: addr}, + end: ip{addr: addr}, + }, nil + } + + start, err := netip.ParseAddr(before) + if err != nil { + return ipRange{}, errors.Join(fmtErr, err) + } - // 172.18.0.1-172.18.1.10 - // fd00::1-fd00::1:a - start := xIP{normalizeIP(startIP)} - end := xIP{normalizeIP(endIP)} - if end.cmp(start) < 0 { - return nil, fmtErr + _, err = netip.ParseAddr(after) + if err != nil { + // 172.18.0.1-10 fd00::1-a + index := strings.LastIndex(before, ".") + if index == -1 { + index = strings.LastIndex(before, ":") } + after = before[:index+1] + after + } - return &ipRange{ - start: start, - end: end, - }, nil + // 172.18.0.1-172.18.1.10 fd00::1-fd00::1:a + end, err := netip.ParseAddr(after) + if err != nil { + return ipRange{}, errors.Join(fmtErr, err) } - // 172.18.0.1 - // fd00::1 - ip := net.ParseIP(r) - if ip == nil { - return nil, fmtErr + start = start.Unmap() + end = end.Unmap() + if start.Is4() != end.Is4() { + return ipRange{}, fmtErr + } + if end.Compare(start) < 0 { + return ipRange{}, fmtErr } - nIP := normalizeIP(ip) - return &ipRange{ - start: xIP{nIP}, - end: xIP{nIP}, + return ipRange{ + start: ip{addr: start}, + end: ip{addr: end}, }, nil } -// contains reports whether ipRange r contains net.IP ip. -func (r *ipRange) contains(ip net.IP) bool { - w := xIP{ip} - switch r.start.cmp(w) { +// Version returns the IP version of ipRange. +func (r ipRange) version() family { + return r.start.version() +} + +// contains reports whether ipRange r contains netip.Addr addr. +func (r ipRange) contains(addr netip.Addr) bool { + switch r.start.addr.Compare(addr) { case 0: return true case 1: return false default: - return r.end.cmp(w) >= 0 + return r.end.addr.Compare(addr) >= 0 } } -// equal reports whether ipRange r is equal to r2. -func (r *ipRange) equal(r2 *ipRange) bool { - return r.start.Equal(r2.start.IP) && r.end.Equal(r2.end.IP) +// equal reports whether ipRange r is equal to other. +func (r ipRange) equal(other ipRange) bool { + return r.start.cmp(other.start) == 0 && r.end.cmp(other.end) == 0 } // size calculates the total number of IP addresses that pertain to ipRange r. -func (r *ipRange) size() *big.Int { - n := big.NewInt(1) - n.Add(n, ipToInt(r.end.IP)) +// buf is an optional big.Int used to avoid memory allocations. +func (r *ipRange) size(buf *big.Int) *big.Int { + if buf == nil { + buf = new(big.Int) + } + + var start big.Int + start.SetBytes(r.start.addr.AsSlice()) + buf.SetBytes(r.end.addr.AsSlice()) - return n.Sub(n, ipToInt(r.start.IP)) + buf.Sub(buf, &start) + buf.Add(buf, bigInt[1]) + + return buf } // String implements fmt.Stringer. -func (r *ipRange) String() string { - inc := r.size() - dv := new(big.Int).Sub(inc, bigInt[1]) - bl := dv.BitLen() - if bl == 0 { - return r.start.String() +func (r ipRange) String() string { + if r.start.addr == r.end.addr { + return r.start.addr.String() } + inc := r.size(nil) + dv := new(big.Int).Sub(inc, bigInt[1]) if inc.And(inc, dv).Sign() == 0 { - bits := 32 - if r.start.version() == IPv6 { - bits = 128 - } - - ip := r.start.IP - mask := net.CIDRMask(bits-bl, bits) - if ip.Mask(mask).Equal(ip) { - ipNet := net.IPNet{ - IP: ip, - Mask: mask, - } - return ipNet.String() + addr := r.start.addr + prefix, _ := addr.Prefix(addr.BitLen() - dv.BitLen()) + if prefix.Masked().Addr().Compare(addr) == 0 { + return prefix.String() } } - return r.start.String() + "-" + r.end.String() + return r.start.addr.String() + "-" + r.end.addr.String() } diff --git a/ranges.go b/ranges.go index c77d528..5fd06f0 100644 --- a/ranges.go +++ b/ranges.go @@ -3,21 +3,16 @@ package iprange import ( "fmt" "math/big" - "net" - "sort" - - "github.com/brunoga/deep" + "net/netip" + "slices" ) // family defines the version of IP. type family int -// Standard IP version 4 or 6. Unknown represents an invalid IP version, -// which is commonly used in the zero value of an IPRanges struct or to -// distinguish an invalid xIP. +// Standard IP version 4 or 6. const ( - Unknown family = iota - IPv4 + IPv4 family = iota IPv6 ) @@ -26,61 +21,50 @@ func (f family) String() string { if f == IPv4 { return "IPv4" } - if f == IPv6 { - return "IPv6" - } - return "Unknown" + return "IPv6" } -// IPRanges is a set of ipRange that uses the starting and ending IP -// addresses to represent any IP range of any size. The following IP -// range formats are valid: +// IPRanges is a set of ipRange that uses the starting and ending IP addresses +// to represent any IP range of any size. The following IP range formats are +// valid: // // 172.18.0.1 fd00::1 // 172.18.0.0/24 fd00::/64 // 172.18.0.1-10 fd00::1-a // 172.18.0.1-172.18.1.10 fd00::1-fd00::1:a // -// Dual-stack IP ranges are not allowed, The IP version of an IPRanges -// can only be IPv4, IPv6, or unknown (zero value). +// Dual-stack IP ranges are not allowed, The IP version of an IPRanges can only +// be IPv4 or IPv6. type IPRanges struct { version family ranges []ipRange } -// Parse parses a set of IP range format strings as IPRanges, the slice -// of ipRange with the same IP version, which records the starting and -// ending IP addresses. -// -// The error errInvalidIPRangeFormat wiil be returned when one of IP range -// string is invalid. And dual-stack IP ranges are not allowed, the error -// errDualStackIPRanges occurs when parsing a set of IP range strings, where -// there are both IPv4 and IPv6 addresses. -func Parse(rs ...string) (*IPRanges, error) { - if len(rs) == 0 { +// Parse parses a set of IP range format strings as IPRanges, the slice of +// ipRange with the same IP version, which records the starting and ending IP +// addresses. +func Parse(formats ...string) (*IPRanges, error) { + if len(formats) == 0 { return &IPRanges{}, nil } - version := Unknown - ranges := make([]ipRange, 0, len(rs)) - for i, r := range rs { - v, err := parse(r) + version := IPv4 + ranges := make([]ipRange, 0, len(formats)) + for i, f := range formats { + r, err := parse(f) if err != nil { return nil, err } if i == 0 { - version = IPv4 - if v.start.To4() == nil { - version = IPv6 - } + version = r.version() } - if v.start.version() != version { + if i > 0 && r.version() != version { return nil, errDualStackIPRanges } - ranges = append(ranges, *v) + ranges = append(ranges, r) } return &IPRanges{ @@ -91,26 +75,27 @@ func Parse(rs ...string) (*IPRanges, error) { // Version returns the IP version of IPRanges: // -// 1: IPv4 -// 2: IPv6 -// 0: zero value of IPRanges -// -// Do not compare family with a regular int value, which is confusing. -// Use predefined const such as IPv4, IPv6, or Unknown. -func (rr *IPRanges) Version() family { - return rr.version +// 0: IPv4 +// 1: IPv6 +func (rs *IPRanges) Version() family { + return rs.version } -// Contains reports whether IPRanges rr contain net.IP ip. If rr is IPv4 +// Contains reports whether IPRanges rs contain netip.Addr addr. If rs is IPv4 // and ip is IPv6, then it is also considered not contained, and vice versa. -func (rr *IPRanges) Contains(ip net.IP) bool { - w := xIP{ip} - if w.version() != rr.version { +func (rs *IPRanges) Contains(addr netip.Addr) bool { + if len(rs.ranges) == 0 { return false } - for _, r := range rr.ranges { - if r.contains(ip) { + addr = addr.Unmap() + is4 := addr.Is4() + if (rs.version == IPv6 && is4) || (rs.version == IPv4 && !is4) { + return false + } + + for _, r := range rs.ranges { + if r.contains(addr) { return true } } @@ -118,32 +103,29 @@ func (rr *IPRanges) Contains(ip net.IP) bool { return false } -// MergeEqual reports whether IPRanges rr is equal to rr2, but both rr and -// rr2 are pre-merged, which means they are both ordered and deduplicated. -func (rr *IPRanges) MergeEqual(rr2 *IPRanges) bool { - if rr.version != rr2.version { +// MergeEqual reports whether IPRanges rs is equal to other, but both rs and +// other are pre-merged, which means they are both ordered and deduplicated. +func (rs *IPRanges) MergeEqual(other *IPRanges) bool { + if rs.version != other.version { return false } - rr = rr.DeepCopy().Merge() - rr2 = rr2.DeepCopy().Merge() - - return rr.Equal(rr2) + return rs.Merge().Equal(other.Merge()) } -// Equal reports whether IPRanges rr is equal to rr2. -func (rr *IPRanges) Equal(rr2 *IPRanges) bool { - if rr.version != rr2.version { +// Equal reports whether IPRanges rs is equal to other. +func (rs *IPRanges) Equal(other *IPRanges) bool { + if rs.version != other.version { return false } - n := len(rr.ranges) - if len(rr2.ranges) != n { + n := len(rs.ranges) + if len(other.ranges) != n { return false } for i := 0; i < n; i++ { - if !rr.ranges[i].equal(&rr2.ranges[i]) { + if !rs.ranges[i].equal(other.ranges[i]) { return false } } @@ -151,116 +133,105 @@ func (rr *IPRanges) Equal(rr2 *IPRanges) bool { return true } -// Size calculates the total number of IP addresses that pertain to -// IPRanges rr. -func (rr *IPRanges) Size() *big.Int { +// Size calculates the total number of IP addresses that pertain to IPRanges rs. +func (rs *IPRanges) Size() *big.Int { n := big.NewInt(0) - for _, r := range rr.ranges { - n.Add(n, r.size()) + buf := new(big.Int) + for _, r := range rs.ranges { + n.Add(n, r.size(buf)) } return n } -// Merge merges the duplicate parts of multiple ipRanges in rr and sort -// them by their respective starting xIP. -func (rr *IPRanges) Merge() *IPRanges { - if len(rr.ranges) <= 1 { - return rr +// Merge merges the duplicate parts of multiple ipRanges in rs and sort them by +// their respective starting IP. +func (rs *IPRanges) Merge() *IPRanges { + if len(rs.ranges) <= 1 { + return rs.Clone() } - sort.Slice(rr.ranges, func(i, j int) bool { - return rr.ranges[i].start.cmp(rr.ranges[j].start) < 0 + newRS := rs.Clone() + slices.SortFunc(newRS.ranges, func(a, b ipRange) int { + if cmp := a.start.cmp(b.start); cmp != 0 { + return cmp + } + return b.end.cmp(a.end) }) - cur := -1 - merged := make([]ipRange, 0, len(rr.ranges)) - for _, r := range rr.ranges { - if cur == -1 { + merged := newRS.ranges[:0] + for _, r := range newRS.ranges { + if len(merged) == 0 { merged = append(merged, r) - cur++ - continue - } - - if merged[cur].end.next().cmp(r.start) == 0 { - merged[cur].end = r.end continue } - if merged[cur].end.cmp(r.start) < 0 { + last := &merged[len(merged)-1] + if last.end.next().cmp(r.start) < 0 { merged = append(merged, r) - cur++ continue } - if merged[cur].end.cmp(r.end) < 0 { - merged[cur].end = r.end + if last.end.cmp(r.end) < 0 { + last.end = r.end } } - rr.ranges = merged - return rr + newRS.ranges = slices.Clip(merged) + return newRS } -// IsOverlap reports whether IPRanges rr have overlapping parts. -func (rr *IPRanges) IsOverlap() bool { - n := len(rr.ranges) - if n <= 1 { +// IsOverlap reports whether IPRanges rs have overlapping parts. +func (rs *IPRanges) IsOverlap() bool { + if len(rs.ranges) <= 1 { return false } - rs := rr.DeepCopy().ranges - sort.Slice(rs, func(i, j int) bool { - return rs[i].start.cmp(rs[j].start) < 0 - }) + size := rs.Size() + merged := rs.Merge().Size() - for i := 0; i < n-1; i++ { - if rs[i].end.cmp(rs[i+1].start) >= 0 { - return true - } - } - - return false + return size.Cmp(merged) > 0 } -// Union calculates the union of IPRanges rr and rs with the same IP -// version. The result is always merged (ordered and deduplicated). +// Union calculates the union of IPRanges rs and other with the same IP version. +// The result is always merged (ordered and deduplicated). // // Input: [172.18.0.20-30, 172.18.0.1-25] U [172.18.0.5-25] // Output: [172.18.0.1-30] -func (rr *IPRanges) Union(rs *IPRanges) *IPRanges { - if rr.version != rs.version { - return rr.Merge() +func (rs *IPRanges) Union(other *IPRanges) *IPRanges { + if rs.version != other.version { + return rs.Merge() } - rr.ranges = append(rr.ranges, rs.ranges...) - return rr.Merge() + out := rs.Clone() + out.ranges = append(out.ranges, other.ranges...) + + return out.Merge() } -// Diff calculates the difference of IPRanges rr and rs with the same IP +// Diff calculates the difference of IPRanges rs and other with the same IP // version. The result is always merged (ordered and deduplicated). // // Input: [172.18.0.20-30, 172.18.0.1-25] - [172.18.0.5-25] // Output: [172.18.0.1-4, 172.18.0.26-30] -func (rr *IPRanges) Diff(rs *IPRanges) *IPRanges { - if rr.version != rs.version { - return rr.Merge() +func (rs *IPRanges) Diff(other *IPRanges) *IPRanges { + if rs.version != other.version { + return rs.Merge() } - if len(rr.ranges) == 0 || len(rs.ranges) == 0 { - return rr.Merge() + if len(rs.ranges) == 0 || len(other.ranges) == 0 { + return rs.Merge() } - rs = rs.DeepCopy() - omr := rr.Merge().ranges - tmr := rs.Merge().ranges + omr := rs.Merge().ranges + tmr := other.Merge().ranges n1, n2 := len(omr), len(tmr) - ranges := make([]ipRange, 0, n1+n2) i, j := 0, 0 + var ranges []ipRange for i < n1 && j < n2 { - // The following are all distributions of the difference sets between two - // IP range A and B (IP range A - IP range B). + // The following are all distributions of the difference sets between + // two IP range A and B (IP range A - IP range B). // // For convenience, use symbols to distinguish between two IP ranges: // A: *------* @@ -308,49 +279,45 @@ func (rr *IPRanges) Diff(rs *IPRanges) *IPRanges { // *------* // `------` - omr[i].start = (tmr[j].end.next()) + omr[i].start = tmr[j].end.next() j++ } - if j == n2 && tmr[j-1].end.cmp(omr[i].end) < 0 { - ranges = append(ranges, omr[i]) + if j == n2 && i < n1 { + ranges = append(ranges, omr[i:]...) } - if i+1 < n1 { - ranges = append(ranges, omr[i+1:]...) + return &IPRanges{ + version: rs.version, + ranges: ranges, } - rr.ranges = ranges - - return rr } -// Intersect calculates the intersection of IPRanges rr and rs with the -// same IP version. The result is always merged (ordered and deduplicated). +// Intersect calculates the intersection of IPRanges rs and other with the same +// IP version. The result is always merged (ordered and deduplicated). // // Input: [172.18.0.20-30, 172.18.0.1-25] ∩ [172.18.0.5-25] // Output: [172.18.0.5-25] -func (rr *IPRanges) Intersect(rs *IPRanges) *IPRanges { - if rr.version != rs.version { +func (rs *IPRanges) Intersect(other *IPRanges) *IPRanges { + if rs.version != other.version { return &IPRanges{ - version: rr.version, + version: rs.version, } } - if len(rr.ranges) == 0 || len(rs.ranges) == 0 { + if len(rs.ranges) == 0 || len(other.ranges) == 0 { return &IPRanges{ - version: rr.version, + version: rs.version, } } - rs = rs.DeepCopy() - omr := rr.Merge().ranges - tmr := rs.Merge().ranges - n1, n2 := len(omr), len(tmr) - ranges := make([]ipRange, 0, maxN(n1, n2)) + omr := rs.Merge().ranges + tmr := other.Merge().ranges - for i, j := 0, 0; i < n1 && j < n2; { - start := maxXIP(omr[i].start, tmr[j].start) - end := minXIP(omr[i].end, tmr[j].end) + var ranges []ipRange + for i, j := 0, 0; i < len(omr) && j < len(tmr); { + start := maxIP(omr[i].start, tmr[j].start) + end := minIP(omr[i].end, tmr[j].end) if start.cmp(end) <= 0 { ranges = append(ranges, ipRange{ start: start, @@ -364,18 +331,26 @@ func (rr *IPRanges) Intersect(rs *IPRanges) *IPRanges { j++ } } - rr.ranges = ranges - return rr + return &IPRanges{ + version: rs.version, + ranges: ranges, + } } // Slice returns a slice of IPRanges, supporting negative indexes. -func (rr *IPRanges) Slice(start, end *big.Int) *IPRanges { - size := rr.Size() - version := rr.version - rs := &IPRanges{version: version} +func (rs *IPRanges) Slice(start, end *big.Int) *IPRanges { + size := rs.Size() + out := &IPRanges{version: rs.version} if size.Sign() == 0 { - return rs + return out + } + + if start == nil { + start = big.NewInt(0) + } + if end == nil { + end = new(big.Int).Sub(size, bigInt[1]) } if start.Sign() < 0 { @@ -390,70 +365,66 @@ func (rr *IPRanges) Slice(start, end *big.Int) *IPRanges { if end.Sign() < 0 { end = new(big.Int).Add(end, size) if end.Sign() < 0 { - return rs + return out } } else { end = new(big.Int).Set(end) } if start.Cmp(end) > 0 { - return rs + return out } var ranges []ipRange - for i := 0; i < len(rr.ranges); i++ { - size := rr.ranges[i].size() - if start.Cmp(size) >= 0 { - start.Sub(start, size) - end.Sub(end, size) + for i := 0; i < len(rs.ranges); i++ { + rangeSize := rs.ranges[i].size(nil) + if start.Cmp(rangeSize) >= 0 { + start.Sub(start, rangeSize) + end.Sub(end, rangeSize) continue } - if start.Sign() >= 0 { - if end.Cmp(size) < 0 { - ranges = append(ranges, ipRange{ - start: rr.ranges[i].start.nextN(start), - end: rr.ranges[i].start.nextN(end), - }) - break - } - + if end.Cmp(rangeSize) < 0 { ranges = append(ranges, ipRange{ - start: rr.ranges[i].start.nextN(start), - end: rr.ranges[i].end, + start: rs.ranges[i].start.nextN(start, nil), + end: rs.ranges[i].start.nextN(end, nil), }) - start.Sub(start, size) - end.Sub(end, size) - continue - } - - if end.Cmp(size) >= 0 { - ranges = append(ranges, ipRange{ - start: rr.ranges[i].start, - end: rr.ranges[i].end, - }) - end.Sub(end, size) - continue + break } ranges = append(ranges, ipRange{ - start: rr.ranges[i].start, - end: rr.ranges[i].start.nextN(end), + start: rs.ranges[i].start.nextN(start, nil), + end: rs.ranges[i].end, }) - break + start.Sub(start, rangeSize) + end.Sub(end, rangeSize) } - rs.ranges = ranges - return rs + out.ranges = ranges + return out } -func (rr *IPRanges) DeepCopy() *IPRanges { - return deep.MustCopy(rr) +// Clone creates a deep copy of IPRanges rs. +func (rs *IPRanges) Clone() *IPRanges { + n := len(rs.ranges) + if n == 0 { + return &IPRanges{ + version: rs.version, + } + } + + ranges := make([]ipRange, n) + copy(ranges, rs.ranges) + + return &IPRanges{ + version: rs.version, + ranges: ranges, + } } // String implements fmt.Stringer. -func (rr *IPRanges) String() string { - ss := rr.Strings() +func (rs *IPRanges) String() string { + ss := rs.Strings() if len(ss) == 1 { return ss[0] } @@ -461,10 +432,10 @@ func (rr *IPRanges) String() string { return fmt.Sprint(ss) } -// Strings returns a slice of the string representations of the IPRanges rr. -func (rr *IPRanges) Strings() []string { - ss := make([]string, 0, len(rr.ranges)) - for _, r := range rr.ranges { +// Strings returns a slice of the string representations of the IPRanges rs. +func (rs *IPRanges) Strings() []string { + ss := make([]string, 0, len(rs.ranges)) + for _, r := range rs.ranges { ss = append(ss, r.String()) } diff --git a/ranges_test.go b/ranges_test.go index 1913325..6a8c63b 100644 --- a/ranges_test.go +++ b/ranges_test.go @@ -3,12 +3,26 @@ package iprange import ( "errors" "math/big" - "net" + "net/netip" + "reflect" "testing" - - "github.com/google/go-cmp/cmp" ) +func mustRanges(version family, pairs ...[2]string) *IPRanges { + ranges := make([]ipRange, 0, len(pairs)) + for _, pair := range pairs { + ranges = append(ranges, ipRange{ + start: ip{addr: netip.MustParseAddr(pair[0])}, + end: ip{addr: netip.MustParseAddr(pair[1])}, + }) + } + + return &IPRanges{ + version: version, + ranges: ranges, + } +} + var parseTests = []struct { name string rs []string @@ -17,76 +31,32 @@ var parseTests = []struct { }{ { name: "IPv4", - rs: []string{ - "172.18.0.1", - "172.18.0.0/24", - "172.18.0.1-10", - "172.18.0.1-172.18.1.10", - }, - want: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 1).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 255).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 10).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 1, 10).To4()}, - }, - }, - }, - err: nil, + rs: []string{"172.18.0.1", "172.18.0.0/24", "172.18.0.1-10", "172.18.0.1-172.18.1.10"}, + want: mustRanges(IPv4, + [2]string{"172.18.0.1", "172.18.0.1"}, + [2]string{"172.18.0.0", "172.18.0.255"}, + [2]string{"172.18.0.1", "172.18.0.10"}, + [2]string{"172.18.0.1", "172.18.1.10"}, + ), }, { name: "IPv6", - rs: []string{ - "fd00::1", - "fd00::/64", - "fd00::1-a", - "fd00::1-fd00::1:a", - }, - want: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::1")}, - }, - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::ffff:ffff:ffff:ffff")}, - }, - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::a")}, - }, - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::1:a")}, - }, - }, - }, - err: nil, + rs: []string{"fd00::1", "fd00::/64", "fd00::1-a", "fd00::1-fd00::1:a"}, + want: mustRanges(IPv6, + [2]string{"fd00::1", "fd00::1"}, + [2]string{"fd00::", "fd00::ffff:ffff:ffff:ffff"}, + [2]string{"fd00::1", "fd00::a"}, + [2]string{"fd00::1", "fd00::1:a"}, + ), }, {"empty", []string{}, &IPRanges{}, nil}, - {"empty", []string{""}, nil, errInvalidIPRangeFormat}, + {"empty string", []string{""}, nil, errInvalidIPRangeFormat}, {"invalid CIDR", []string{"172.18.0.0/33"}, nil, errInvalidIPRangeFormat}, {"invalid start", []string{"172.18.0.a"}, nil, errInvalidIPRangeFormat}, - {"invalid start", []string{"172.18.0.a-10"}, nil, errInvalidIPRangeFormat}, - {"invalid start", []string{"172.18.0.a-172.18.0.10"}, nil, errInvalidIPRangeFormat}, + {"invalid short end", []string{"172.18.0.a-10"}, nil, errInvalidIPRangeFormat}, {"invalid end", []string{"172.18.0.1-a"}, nil, errInvalidIPRangeFormat}, - {"invalid end", []string{"172.18.0.1-172.18.0.a"}, nil, errInvalidIPRangeFormat}, {"start exceeds end", []string{"172.18.0.10-1"}, nil, errInvalidIPRangeFormat}, - {"start exceeds end", []string{"172.18.0.10-172.18.0.1"}, nil, errInvalidIPRangeFormat}, + {"mixed range version", []string{"172.18.0.1-fd00::1"}, nil, errInvalidIPRangeFormat}, {"dual-stack", []string{"172.18.0.1", "fd00::/64"}, nil, errDualStackIPRanges}, } @@ -103,8 +73,47 @@ func TestParse(t *testing.T) { } return } - if !cmp.Equal(ranges, test.want) { - t.Fatalf("Parse(%q) = %v, want %v", test.rs, ranges, test.want) + if !reflect.DeepEqual(ranges, test.want) { + t.Fatalf("Parse(%q) = %#v, want %#v", test.rs, ranges, test.want) + } + }) + } +} + +func TestErrorHelpers(t *testing.T) { + t.Parallel() + + if !IsInvalidIPRangeFormat(errInvalidIPRangeFormat) { + t.Fatal("IsInvalidIPRangeFormat() = false, want true") + } + if IsInvalidIPRangeFormat(errDualStackIPRanges) { + t.Fatal("IsInvalidIPRangeFormat() = true, want false") + } + if !IsDualStackIPRanges(errDualStackIPRanges) { + t.Fatal("IsDualStackIPRanges() = false, want true") + } + if IsDualStackIPRanges(errInvalidIPRangeFormat) { + t.Fatal("IsDualStackIPRanges() = true, want false") + } +} + +var familyStringTests = []struct { + name string + family family + want string +}{ + {"IPv4", IPv4, "IPv4"}, + {"IPv6", IPv6, "IPv6"}, +} + +func TestFamilyString(t *testing.T) { + t.Parallel() + for _, test := range familyStringTests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + if got := test.family.String(); got != test.want { + t.Fatalf("family(%v).String() = %q, want %q", test.family, got, test.want) } }) } @@ -116,36 +125,16 @@ var ipRangesVersionTests = []struct { want family }{ { - name: "IPv4", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 1).To4()}, - }, - }, - }, - want: IPv4, - }, - { - name: "IPv6", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::1")}, - }, - }, - }, - want: IPv6, + name: "IPv4", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), + want: IPv4, }, { - name: "unknown", - ranges: &IPRanges{}, - want: Unknown, + name: "IPv6", + ranges: mustRanges(IPv6, [2]string{"fd00::1", "fd00::1"}), + want: IPv6, }, + {"zero", &IPRanges{}, IPv4}, } func TestIPRangesVersion(t *testing.T) { @@ -154,9 +143,8 @@ func TestIPRangesVersion(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - version := test.ranges.Version() - if version != test.want { - t.Fatalf("IPRanges(%v).Version() = %v, want %v", test.ranges, version, test.want) + if got := test.ranges.Version(); got != test.want { + t.Fatalf("IPRanges(%v).Version() = %v, want %v", test.ranges, got, test.want) } }) } @@ -165,113 +153,46 @@ func TestIPRangesVersion(t *testing.T) { var ipRangesContainsTests = []struct { name string ranges *IPRanges - ip net.IP + addr netip.Addr want bool }{ { - name: "IPv4 contain", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 3).To4()}, - }, - }, - }, - ip: net.IPv4(172, 18, 0, 1), - want: true, - }, - { - name: "IPv6 contain", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::3")}, - }, - }, - }, - ip: net.ParseIP("fd00::2"), - want: true, + name: "IPv4 contain", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.3"}), + addr: netip.MustParseAddr("172.18.0.1"), + want: true, }, { - name: "IPv4 not contain", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 3).To4()}, - }, - }, - }, - ip: net.IPv4(172, 18, 0, 0), - want: false, + name: "IPv6 contain", + ranges: mustRanges(IPv6, [2]string{"fd00::1", "fd00::3"}), + addr: netip.MustParseAddr("fd00::2"), + want: true, }, { - name: "IPv6 not contain", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::3")}, - }, - }, - }, - ip: net.ParseIP("fd00::0"), - want: false, - }, - { - name: "diff version", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 3).To4()}, - }, - }, - }, - ip: net.ParseIP("fd00::2"), - want: false, + name: "IPv4 not contain", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.3"}), + addr: netip.MustParseAddr("172.18.0.4"), + want: false, }, { - name: "diff version", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::3")}, - }, - }, - }, - ip: net.IPv4(172, 18, 0, 1), - want: false, + name: "IPv6 not contain", + ranges: mustRanges(IPv6, [2]string{"fd00::1", "fd00::3"}), + addr: netip.MustParseAddr("fd00::0"), + want: false, }, { - name: "diff version", - ranges: &IPRanges{}, - ip: net.IPv4(172, 18, 0, 1), + name: "different version", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.3"}), + addr: netip.MustParseAddr("fd00::2"), want: false, }, { - name: "invalid IP", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 3).To4()}, - }, - }, - }, - ip: nil, - want: false, + name: "unmapped IPv4", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.3"}), + addr: netip.MustParseAddr("::ffff:172.18.0.2"), + want: true, }, + {"zero", &IPRanges{}, netip.Addr{}, false}, } func TestIPRangesContains(t *testing.T) { @@ -280,9 +201,8 @@ func TestIPRangesContains(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - contains := test.ranges.Contains(test.ip) - if contains != test.want { - t.Fatalf("IPRanges(%v).Contains(%v) = %v, want %v", test.ranges, test.ip, contains, test.want) + if got := test.ranges.Contains(test.addr); got != test.want { + t.Fatalf("IPRanges(%v).Contains(%v) = %v, want %v", test.ranges, test.addr, got, test.want) } }) } @@ -295,84 +215,23 @@ var ipRangesMergeEqualTests = []struct { want bool }{ { - name: "IPv4", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 100).To4()}, - end: xIP{net.IPv4(172, 18, 0, 255).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 200).To4()}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 255).To4()}, - }, - }, - }, - want: true, - }, - { - name: "IPv6", - rangesX: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::aa")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::dd")}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - }, - }, - want: true, + name: "IPv4", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.100", "172.18.0.255"}, [2]string{"172.18.0.0", "172.18.0.200"}), + rangesY: mustRanges(IPv4, [2]string{"172.18.0.0", "172.18.0.255"}), + want: true, }, { - name: "zero", - rangesX: &IPRanges{}, - rangesY: &IPRanges{}, + name: "IPv6", + rangesX: mustRanges(IPv6, [2]string{"fd00::aa", "fd00::ff"}, [2]string{"fd00::", "fd00::dd"}), + rangesY: mustRanges(IPv6, [2]string{"fd00::", "fd00::ff"}), want: true, }, + {"zero", &IPRanges{}, &IPRanges{}, true}, { - name: "diff version", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 255).To4()}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - }, - }, - want: false, + name: "diff version", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.0", "172.18.0.255"}), + rangesY: mustRanges(IPv6, [2]string{"fd00::", "fd00::ff"}), + want: false, }, } @@ -382,9 +241,8 @@ func TestIPRangesMergeEqual(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - equal := test.rangesX.MergeEqual(test.rangesY) - if equal != test.want { - t.Fatalf("IPRanges(%v).MergeEqual(%v) = %v, want %v", test.rangesX, test.rangesY, equal, test.want) + if got := test.rangesX.MergeEqual(test.rangesY); got != test.want { + t.Fatalf("IPRanges(%v).MergeEqual(%v) = %v, want %v", test.rangesX, test.rangesY, got, test.want) } }) } @@ -397,148 +255,28 @@ var ipRangesEqualTests = []struct { want bool }{ { - name: "IPv4 equal", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 100).To4()}, - end: xIP{net.IPv4(172, 18, 0, 255).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 200).To4()}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 100).To4()}, - end: xIP{net.IPv4(172, 18, 0, 255).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 200).To4()}, - }, - }, - }, - want: true, - }, - { - name: "IPv6 equal", - rangesX: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::aa")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::dd")}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::aa")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::dd")}, - }, - }, - }, - want: true, - }, - { - name: "zero", - rangesX: &IPRanges{}, - rangesY: &IPRanges{}, + name: "equal", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), + rangesY: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), want: true, }, { - name: "IPv4 not equal", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 100).To4()}, - end: xIP{net.IPv4(172, 18, 0, 255).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 200).To4()}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 255).To4()}, - }, - }, - }, - want: false, + name: "different len", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), + rangesY: mustRanges(IPv4), + want: false, }, { - name: "IPv6 not equal", - rangesX: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::aa")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::dd")}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::aa")}, - }, - { - start: xIP{net.ParseIP("fd00::ab")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - }, - }, - want: false, + name: "different range", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), + rangesY: mustRanges(IPv4, [2]string{"172.18.0.2", "172.18.0.2"}), + want: false, }, { - name: "diff version", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 255).To4()}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - }, - }, - want: false, + name: "different version", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), + rangesY: mustRanges(IPv6, [2]string{"fd00::1", "fd00::1"}), + want: false, }, } @@ -548,56 +286,34 @@ func TestIPRangesEqual(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - equal := test.rangesX.Equal(test.rangesY) - if equal != test.want { - t.Fatalf("IPRanges(%v).Equal(%v) = %v, want %v", test.rangesX, test.rangesY, equal, test.want) + if got := test.rangesX.Equal(test.rangesY); got != test.want { + t.Fatalf("IPRanges(%v).Equal(%v) = %v, want %v", test.rangesX, test.rangesY, got, test.want) } }) } } -var size, _ = big.NewInt(0).SetString("18446744073709551615", 10) - var ipRangesSizeTests = []struct { name string ranges *IPRanges want *big.Int }{ { - name: "IPv4", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 100).To4()}, - end: xIP{net.IPv4(172, 18, 0, 255).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 200).To4()}, - }, - }, - }, - want: big.NewInt(357), + name: "IPv4", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.3"}), + want: big.NewInt(3), }, { - name: "IPv6", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::ffff:ffff:ffff:ffff")}, - }, - }, - }, - want: size, + name: "IPv6", + ranges: mustRanges(IPv6, [2]string{"fd00::1", "fd00::3"}), + want: big.NewInt(3), }, { - name: "zero", - ranges: &IPRanges{}, - want: big.NewInt(0), + name: "multiple", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.3"}, [2]string{"172.18.0.10", "172.18.0.12"}), + want: big.NewInt(6), }, + {"zero", &IPRanges{}, big.NewInt(0)}, } func TestIPRangesSize(t *testing.T) { @@ -606,9 +322,8 @@ func TestIPRangesSize(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - size := test.ranges.Size() - if size.Cmp(test.want) != 0 { - t.Fatalf("IPRanges(%v).Size() = %v, want %v", test.ranges, size, test.want) + if got := test.ranges.Size(); got.Cmp(test.want) != 0 { + t.Fatalf("IPRanges(%v).Size() = %v, want %v", test.ranges, got, test.want) } }) } @@ -620,62 +335,44 @@ var ipRangesMergeTests = []struct { want *IPRanges }{ { - name: "multiple", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 100).To4()}, - end: xIP{net.IPv4(172, 18, 0, 210).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 200).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 211).To4()}, - end: xIP{net.IPv4(172, 18, 0, 211).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 220).To4()}, - end: xIP{net.IPv4(172, 18, 0, 230).To4()}, - }, - }, - }, - want: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 0).To4()}, - end: xIP{net.IPv4(172, 18, 0, 211).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 220).To4()}, - end: xIP{net.IPv4(172, 18, 0, 230).To4()}, - }, - }, - }, + name: "single", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), + }, + { + name: "merge overlap", + ranges: mustRanges(IPv4, [2]string{"172.18.0.100", "172.18.0.255"}, [2]string{"172.18.0.0", "172.18.0.200"}), + want: mustRanges(IPv4, [2]string{"172.18.0.0", "172.18.0.255"}), }, { - name: "one", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - }, - }, - want: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - }, - }, + name: "merge adjacent", + ranges: mustRanges(IPv6, [2]string{"fd00::1", "fd00::2"}, [2]string{"fd00::3", "fd00::4"}), + want: mustRanges(IPv6, [2]string{"fd00::1", "fd00::4"}), + }, + { + name: "contained", + ranges: mustRanges(IPv4, [2]string{"172.18.0.0", "172.18.0.10"}, [2]string{"172.18.0.2", "172.18.0.4"}), + want: mustRanges(IPv4, [2]string{"172.18.0.0", "172.18.0.10"}), + }, + { + name: "same start different end", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.3"}, [2]string{"172.18.0.1", "172.18.0.5"}), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), + }, + { + name: "multiple overlaps", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.10"}, [2]string{"172.18.0.5", "172.18.0.15"}, [2]string{"172.18.0.20", "172.18.0.25"}), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.15"}, [2]string{"172.18.0.20", "172.18.0.25"}), + }, + { + name: "unordered input", + ranges: mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.20"}, [2]string{"172.18.0.1", "172.18.0.5"}), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}, [2]string{"172.18.0.10", "172.18.0.20"}), + }, + { + name: "duplicate ranges", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}, [2]string{"172.18.0.1", "172.18.0.5"}), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), }, } @@ -685,9 +382,45 @@ func TestIPRangesMerge(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() + original := test.ranges.Clone() merged := test.ranges.Merge() - if !cmp.Equal(merged, test.want) { - t.Fatalf("IPRanges(%v).Merge() = %v, want %v", test.ranges, merged, test.want) + if !reflect.DeepEqual(merged, test.want) { + t.Fatalf("IPRanges(%v).Merge() = %#v, want %#v", test.ranges, merged, test.want) + } + if !reflect.DeepEqual(test.ranges, original) { + t.Fatalf("IPRanges(%v).Merge() mutated receiver", original) + } + }) + } +} + +var ipRangesIsOverlapTests = []struct { + name string + ranges *IPRanges + want bool +}{ + { + name: "overlap", + ranges: mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.20"}, [2]string{"172.18.0.15", "172.18.0.25"}), + want: true, + }, + { + name: "adjacent only", + ranges: mustRanges(IPv6, [2]string{"fd00::", "fd00::aa"}, [2]string{"fd00::ab", "fd00::ff"}), + want: false, + }, + {"single", mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.1"}), false}, + {"zero", &IPRanges{}, false}, +} + +func TestIPRangesIsOverlap(t *testing.T) { + t.Parallel() + for _, test := range ipRangesIsOverlapTests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + if got := test.ranges.IsOverlap(); got != test.want { + t.Fatalf("IPRanges(%v).IsOverlap() = %v, want %v", test.ranges, got, test.want) } }) } @@ -700,110 +433,16 @@ var ipRangesUnionTests = []struct { want *IPRanges }{ { - name: "IPv4", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 20).To4()}, - end: xIP{net.IPv4(172, 18, 0, 25).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 10).To4()}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 5).To4()}, - end: xIP{net.IPv4(172, 18, 0, 15).To4()}, - }, - }, - }, - want: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 15).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 20).To4()}, - end: xIP{net.IPv4(172, 18, 0, 25).To4()}, - }, - }, - }, + name: "same version", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.20", "172.18.0.30"}, [2]string{"172.18.0.1", "172.18.0.25"}), + rangesY: mustRanges(IPv4, [2]string{"172.18.0.5", "172.18.0.25"}), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.30"}), }, { - name: "IPv6", - rangesX: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::14")}, - end: xIP{net.ParseIP("fd00::19")}, - }, - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::a")}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::5")}, - end: xIP{net.ParseIP("fd00::f")}, - }, - }, - }, - want: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::f")}, - }, - { - start: xIP{net.ParseIP("fd00::14")}, - end: xIP{net.ParseIP("fd00::19")}, - }, - }, - }, - }, - { - name: "diff version", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 20).To4()}, - end: xIP{net.IPv4(172, 18, 0, 25).To4()}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::5")}, - }, - }, - }, - want: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 20).To4()}, - end: xIP{net.IPv4(172, 18, 0, 25).To4()}, - }, - }, - }, + name: "different version", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.20", "172.18.0.30"}, [2]string{"172.18.0.1", "172.18.0.25"}), + rangesY: mustRanges(IPv6, [2]string{"fd00::1", "fd00::5"}), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.30"}), }, } @@ -814,8 +453,8 @@ func TestIPRangesUnion(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() union := test.rangesX.Union(test.rangesY) - if !cmp.Equal(union, test.want) { - t.Fatalf("IPRanges(%v).Union(%v) = %v, want %v", test.rangesX, test.rangesY, union, test.want) + if !reflect.DeepEqual(union, test.want) { + t.Fatalf("IPRanges(%v).Union(%v) = %#v, want %#v", test.rangesX, test.rangesY, union, test.want) } }) } @@ -828,203 +467,43 @@ var ipRangesDiffTests = []struct { want *IPRanges }{ { - name: "IPv4", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 20).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 5).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 25).To4()}, - end: xIP{net.IPv4(172, 18, 0, 30).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 40).To4()}, - end: xIP{net.IPv4(172, 18, 0, 50).To4()}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 15).To4()}, - end: xIP{net.IPv4(172, 18, 0, 15).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 8).To4()}, - end: xIP{net.IPv4(172, 18, 0, 12).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 18).To4()}, - end: xIP{net.IPv4(172, 18, 0, 22).To4()}, - }, - }, - }, - want: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 5).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 13).To4()}, - end: xIP{net.IPv4(172, 18, 0, 14).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 16).To4()}, - end: xIP{net.IPv4(172, 18, 0, 17).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 25).To4()}, - end: xIP{net.IPv4(172, 18, 0, 30).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 40).To4()}, - end: xIP{net.IPv4(172, 18, 0, 50).To4()}, - }, - }, - }, + name: "subset middle", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.20", "172.18.0.30"}, [2]string{"172.18.0.1", "172.18.0.25"}), + rangesY: mustRanges(IPv4, [2]string{"172.18.0.5", "172.18.0.25"}), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.4"}, [2]string{"172.18.0.26", "172.18.0.30"}), }, { - name: "IPv6", - rangesX: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::a")}, - end: xIP{net.ParseIP("fd00::14")}, - }, - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::5")}, - }, - { - start: xIP{net.ParseIP("fd00::19")}, - end: xIP{net.ParseIP("fd00::1e")}, - }, - { - start: xIP{net.ParseIP("fd00::28")}, - end: xIP{net.ParseIP("fd00::32")}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::f")}, - end: xIP{net.ParseIP("fd00::f")}, - }, - { - start: xIP{net.ParseIP("fd00::8")}, - end: xIP{net.ParseIP("fd00::c")}, - }, - { - start: xIP{net.ParseIP("fd00::12")}, - end: xIP{net.ParseIP("fd00::16")}, - }, - }, - }, - want: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::5")}, - }, - { - start: xIP{net.ParseIP("fd00::d")}, - end: xIP{net.ParseIP("fd00::e")}, - }, - { - start: xIP{net.ParseIP("fd00::10")}, - end: xIP{net.ParseIP("fd00::11")}, - }, - { - start: xIP{net.ParseIP("fd00::19")}, - end: xIP{net.ParseIP("fd00::1e")}, - }, - { - start: xIP{net.ParseIP("fd00::28")}, - end: xIP{net.ParseIP("fd00::32")}, - }, - }, - }, + name: "disjoint", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), + rangesY: mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.12"}), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), }, { - name: "diff version", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 20).To4()}, - end: xIP{net.IPv4(172, 18, 0, 25).To4()}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::5")}, - }, - }, - }, - want: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 20).To4()}, - end: xIP{net.IPv4(172, 18, 0, 25).To4()}, - }, - }, - }, + name: "full cover", + rangesX: mustRanges(IPv6, [2]string{"fd00::1", "fd00::5"}), + rangesY: mustRanges(IPv6, [2]string{"fd00::", "fd00::10"}), + want: &IPRanges{version: IPv6}, }, { - name: "zero-", - rangesX: &IPRanges{version: IPv6}, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::5")}, - }, - }, - }, - want: &IPRanges{version: IPv6}, + name: "left after right", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.12"}), + rangesY: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), + want: mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.12"}), }, { - name: "-zero", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 20).To4()}, - end: xIP{net.IPv4(172, 18, 0, 25).To4()}, - }, - }, - }, - rangesY: &IPRanges{version: IPv6}, - want: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 20).To4()}, - end: xIP{net.IPv4(172, 18, 0, 25).To4()}, - }, - }, - }, + name: "trim tail only", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.10"}), + rangesY: mustRanges(IPv4, [2]string{"172.18.0.5", "172.18.0.20"}), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.4"}), }, + { + name: "different version", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), + rangesY: mustRanges(IPv6, [2]string{"fd00::1", "fd00::5"}), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), + }, + {"zero left", &IPRanges{version: IPv4}, mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), &IPRanges{version: IPv4}}, + {"zero right", mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), &IPRanges{version: IPv4}, mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"})}, } func TestIPRangesDiff(t *testing.T) { @@ -1033,9 +512,9 @@ func TestIPRangesDiff(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - difference := test.rangesX.Diff(test.rangesY) - if !cmp.Equal(difference, test.want) { - t.Fatalf("IPRanges(%v).Diff(%v) = %v, want %v", test.rangesX, test.rangesY, difference, test.want) + diff := test.rangesX.Diff(test.rangesY) + if !reflect.DeepEqual(diff, test.want) { + t.Fatalf("IPRanges(%v).Diff(%v) = %#v, want %#v", test.rangesX, test.rangesY, diff, test.want) } }) } @@ -1048,171 +527,24 @@ var ipRangesIntersectTests = []struct { want *IPRanges }{ { - name: "IPv4", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 20).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 5).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 25).To4()}, - end: xIP{net.IPv4(172, 18, 0, 30).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 40).To4()}, - end: xIP{net.IPv4(172, 18, 0, 50).To4()}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 15).To4()}, - end: xIP{net.IPv4(172, 18, 0, 15).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 8).To4()}, - end: xIP{net.IPv4(172, 18, 0, 12).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 18).To4()}, - end: xIP{net.IPv4(172, 18, 0, 22).To4()}, - }, - }, - }, - want: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 12).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 15).To4()}, - end: xIP{net.IPv4(172, 18, 0, 15).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 18).To4()}, - end: xIP{net.IPv4(172, 18, 0, 20).To4()}, - }, - }, - }, + name: "same version", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.20", "172.18.0.30"}, [2]string{"172.18.0.1", "172.18.0.25"}), + rangesY: mustRanges(IPv4, [2]string{"172.18.0.5", "172.18.0.25"}), + want: mustRanges(IPv4, [2]string{"172.18.0.5", "172.18.0.25"}), }, { - name: "IPv6", - rangesX: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::f")}, - end: xIP{net.ParseIP("fd00::f")}, - }, - { - start: xIP{net.ParseIP("fd00::8")}, - end: xIP{net.ParseIP("fd00::c")}, - }, - { - start: xIP{net.ParseIP("fd00::12")}, - end: xIP{net.ParseIP("fd00::16")}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::a")}, - end: xIP{net.ParseIP("fd00::14")}, - }, - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::5")}, - }, - { - start: xIP{net.ParseIP("fd00::19")}, - end: xIP{net.ParseIP("fd00::1e")}, - }, - { - start: xIP{net.ParseIP("fd00::28")}, - end: xIP{net.ParseIP("fd00::32")}, - }, - }, - }, - want: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::a")}, - end: xIP{net.ParseIP("fd00::c")}, - }, - { - start: xIP{net.ParseIP("fd00::f")}, - end: xIP{net.ParseIP("fd00::f")}, - }, - { - start: xIP{net.ParseIP("fd00::12")}, - end: xIP{net.ParseIP("fd00::14")}, - }, - }, - }, - }, - { - name: "diff version", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 20).To4()}, - end: xIP{net.IPv4(172, 18, 0, 25).To4()}, - }, - }, - }, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::5")}, - }, - }, - }, - want: &IPRanges{version: IPv4}, - }, - { - name: "zero-", - rangesX: &IPRanges{version: IPv6}, - rangesY: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::5")}, - }, - }, - }, - want: &IPRanges{version: IPv6}, + name: "disjoint", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), + rangesY: mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.12"}), + want: &IPRanges{version: IPv4}, }, { - name: "-zero", - rangesX: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 20).To4()}, - end: xIP{net.IPv4(172, 18, 0, 25).To4()}, - }, - }, - }, - rangesY: &IPRanges{version: IPv4}, + name: "different version", + rangesX: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), + rangesY: mustRanges(IPv6, [2]string{"fd00::1", "fd00::5"}), want: &IPRanges{version: IPv4}, }, + {"zero", &IPRanges{version: IPv6}, mustRanges(IPv6, [2]string{"fd00::1", "fd00::5"}), &IPRanges{version: IPv6}}, } func TestIPRangesIntersect(t *testing.T) { @@ -1222,8 +554,8 @@ func TestIPRangesIntersect(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() intersection := test.rangesX.Intersect(test.rangesY) - if !cmp.Equal(intersection, test.want) { - t.Fatalf("IPRanges(%v).Intersect(%v) = %v, want %v", test.rangesX, test.rangesY, intersection, test.want) + if !reflect.DeepEqual(intersection, test.want) { + t.Fatalf("IPRanges(%v).Intersect(%v) = %#v, want %#v", test.rangesX, test.rangesY, intersection, test.want) } }) } @@ -1237,175 +569,62 @@ var ipRangesSliceTests = []struct { want *IPRanges }{ { - name: "IPv4", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 20).To4()}, - }, - }, - }, - start: big.NewInt(0), - end: big.NewInt(2), - want: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 12).To4()}, - }, - }, - }, + name: "IPv4", + ranges: mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.20"}), + start: big.NewInt(0), + end: big.NewInt(2), + want: mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.12"}), }, { - name: "IPv6", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::f")}, - end: xIP{net.ParseIP("fd00::f")}, - }, - { - start: xIP{net.ParseIP("fd00::8")}, - end: xIP{net.ParseIP("fd00::9")}, - }, - { - start: xIP{net.ParseIP("fd00::12")}, - end: xIP{net.ParseIP("fd00::16")}, - }, - }, - }, - start: big.NewInt(1), - end: big.NewInt(3), - want: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::8")}, - end: xIP{net.ParseIP("fd00::9")}, - }, - { - start: xIP{net.ParseIP("fd00::12")}, - end: xIP{net.ParseIP("fd00::12")}, - }, - }, - }, + name: "IPv6", + ranges: mustRanges(IPv6, [2]string{"fd00::f", "fd00::f"}, [2]string{"fd00::8", "fd00::9"}, [2]string{"fd00::12", "fd00::16"}), + start: big.NewInt(1), + end: big.NewInt(3), + want: mustRanges(IPv6, [2]string{"fd00::8", "fd00::9"}, [2]string{"fd00::12", "fd00::12"}), }, { - name: "negative index", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 20).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 5).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 25).To4()}, - end: xIP{net.IPv4(172, 18, 0, 30).To4()}, - }, - }, - }, - start: big.NewInt(1), - end: big.NewInt(-2), - want: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 11).To4()}, - end: xIP{net.IPv4(172, 18, 0, 20).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 5).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 25).To4()}, - end: xIP{net.IPv4(172, 18, 0, 29).To4()}, - }, - }, - }, + name: "negative index", + ranges: mustRanges(IPv4, [2]string{"172.18.0.10", "172.18.0.20"}, [2]string{"172.18.0.1", "172.18.0.5"}, [2]string{"172.18.0.25", "172.18.0.30"}), + start: big.NewInt(1), + end: big.NewInt(-2), + want: mustRanges(IPv4, [2]string{"172.18.0.11", "172.18.0.20"}, [2]string{"172.18.0.1", "172.18.0.5"}, [2]string{"172.18.0.25", "172.18.0.29"}), }, { - name: "start < 0 && end > size", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 5).To4()}, - }, - }, - }, - start: big.NewInt(-100), - end: big.NewInt(100), - want: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 5).To4()}, - }, - }, - }, + name: "start < 0 && end > size", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), + start: big.NewInt(-100), + end: big.NewInt(100), + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), }, { - name: "end out of ranges", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 5).To4()}, - }, - }, - }, - start: big.NewInt(-100), - end: big.NewInt(-100), - want: &IPRanges{version: IPv4}, + name: "end out of ranges", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), + start: big.NewInt(-100), + end: big.NewInt(-100), + want: &IPRanges{version: IPv4}, }, { - name: "start out of ranges", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 5).To4()}, - }, - }, - }, - start: big.NewInt(6), - end: big.NewInt(6), - want: &IPRanges{version: IPv4}, + name: "start out of ranges", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), + start: big.NewInt(6), + end: big.NewInt(6), + want: &IPRanges{version: IPv4}, }, { - name: "start > end", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 1).To4()}, - end: xIP{net.IPv4(172, 18, 0, 5).To4()}, - }, - }, - }, - start: big.NewInt(-1), - end: big.NewInt(0), - want: &IPRanges{version: IPv4}, + name: "start > end", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.5"}), + start: big.NewInt(-1), + end: big.NewInt(0), + want: &IPRanges{version: IPv4}, }, { - name: "zero", - ranges: &IPRanges{}, - want: &IPRanges{}, + name: "nil indexes", + ranges: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.3"}), + start: nil, + end: nil, + want: mustRanges(IPv4, [2]string{"172.18.0.1", "172.18.0.3"}), }, + {"zero", &IPRanges{}, nil, nil, &IPRanges{}}, } func TestIPRangesSlice(t *testing.T) { @@ -1415,71 +634,8 @@ func TestIPRangesSlice(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() s := test.ranges.Slice(test.start, test.end) - if !cmp.Equal(s, test.want) { - t.Fatalf("IPRanges(%v).Slice(%v, %v) = %v, want %v", test.ranges, test.start, test.end, s, test.want) - } - }) - } -} - -var ipRangesIsOverlapTests = []struct { - name string - ranges *IPRanges - want bool -}{ - { - name: "IPv4", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 10).To4()}, - end: xIP{net.IPv4(172, 18, 0, 20).To4()}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 15).To4()}, - end: xIP{net.IPv4(172, 18, 0, 25).To4()}, - }, - }, - }, - want: true, - }, - { - name: "IPv6", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::ab")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - { - start: xIP{net.ParseIP("fd00::0")}, - end: xIP{net.ParseIP("fd00::aa")}, - }, - }, - }, - want: false, - }, - { - name: "zero", - ranges: &IPRanges{}, - want: false, - }, -} - -func TestIPRangesIsOverlap(t *testing.T) { - t.Parallel() - for _, test := range ipRangesIsOverlapTests { - test := test - t.Run(test.name, func(t *testing.T) { - t.Parallel() - overlap := test.ranges.IsOverlap() - if !cmp.Equal(overlap, test.want) { - t.Fatalf("IPRanges(%v).IsOverlap() = %v, want %v", test.ranges, overlap, test.want) + if !reflect.DeepEqual(s, test.want) { + t.Fatalf("IPRanges(%v).Slice(%v, %v) = %#v, want %#v", test.ranges, test.start, test.end, s, test.want) } }) } @@ -1491,66 +647,22 @@ var ipRangesStringTests = []struct { want string }{ { - name: "ranges", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 100)}, - end: xIP{net.IPv4(172, 18, 0, 255)}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 0)}, - end: xIP{net.IPv4(172, 18, 0, 200)}, - }, - }, - }, - want: "[172.18.0.100-172.18.0.255 172.18.0.0-172.18.0.200]", + name: "ranges", + ranges: mustRanges(IPv4, [2]string{"172.18.0.100", "172.18.0.255"}, [2]string{"172.18.0.0", "172.18.0.200"}), + want: "[172.18.0.100-172.18.0.255 172.18.0.0-172.18.0.200]", }, { - name: "range", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 100)}, - end: xIP{net.IPv4(172, 18, 0, 255)}, - }, - }, - }, - want: "172.18.0.100-172.18.0.255", + name: "range", + ranges: mustRanges(IPv4, [2]string{"172.18.0.100", "172.18.0.255"}), + want: "172.18.0.100-172.18.0.255", }, { - name: "CIDR", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - }, - }, - want: "fd00::/120", - }, - { - name: "single", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::1")}, - }, - }, - }, - want: "fd00::1", - }, - { - name: "zero", - ranges: &IPRanges{}, - want: "[]", + name: "CIDR", + ranges: mustRanges(IPv6, [2]string{"fd00::", "fd00::ff"}), + want: "fd00::/120", }, + {"single", mustRanges(IPv6, [2]string{"fd00::1", "fd00::1"}), "fd00::1"}, + {"zero", &IPRanges{}, "[]"}, } func TestIPRangesString(t *testing.T) { @@ -1559,9 +671,8 @@ func TestIPRangesString(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - s := test.ranges.String() - if s != test.want { - t.Fatalf("IPRanges(%v).String() = %v, want %v", test.ranges, s, test.want) + if got := test.ranges.String(); got != test.want { + t.Fatalf("IPRanges(%v).String() = %v, want %v", test.ranges, got, test.want) } }) } @@ -1573,56 +684,17 @@ var ipRangesStringsTests = []struct { want []string }{ { - name: "range", - ranges: &IPRanges{ - version: IPv4, - ranges: []ipRange{ - { - start: xIP{net.IPv4(172, 18, 0, 100)}, - end: xIP{net.IPv4(172, 18, 0, 255)}, - }, - { - start: xIP{net.IPv4(172, 18, 0, 0)}, - end: xIP{net.IPv4(172, 18, 0, 200)}, - }, - }, - }, - want: []string{ - "172.18.0.100-172.18.0.255", - "172.18.0.0-172.18.0.200", - }, - }, - { - name: "CIDR", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::")}, - end: xIP{net.ParseIP("fd00::ff")}, - }, - }, - }, - want: []string{"fd00::/120"}, - }, - { - name: "single", - ranges: &IPRanges{ - version: IPv6, - ranges: []ipRange{ - { - start: xIP{net.ParseIP("fd00::1")}, - end: xIP{net.ParseIP("fd00::1")}, - }, - }, - }, - want: []string{"fd00::1"}, + name: "range", + ranges: mustRanges(IPv4, [2]string{"172.18.0.100", "172.18.0.255"}, [2]string{"172.18.0.0", "172.18.0.200"}), + want: []string{"172.18.0.100-172.18.0.255", "172.18.0.0-172.18.0.200"}, }, { - name: "zero", - ranges: &IPRanges{}, - want: []string{}, + name: "CIDR", + ranges: mustRanges(IPv6, [2]string{"fd00::", "fd00::ff"}), + want: []string{"fd00::/120"}, }, + {"single", mustRanges(IPv6, [2]string{"fd00::1", "fd00::1"}), []string{"fd00::1"}}, + {"zero", &IPRanges{}, []string{}}, } func TestIPRangesStrings(t *testing.T) { @@ -1631,9 +703,8 @@ func TestIPRangesStrings(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - ss := test.ranges.Strings() - if !cmp.Equal(ss, test.want) { - t.Fatalf("IPRanges(%v).Strings() = %v, want %v", test.ranges, ss, test.want) + if got := test.ranges.Strings(); !reflect.DeepEqual(got, test.want) { + t.Fatalf("IPRanges(%v).Strings() = %v, want %v", test.ranges, got, test.want) } }) }