diff --git a/net/address/address.go b/net/address/address.go index 42aa3cc2..6252adcd 100644 --- a/net/address/address.go +++ b/net/address/address.go @@ -73,6 +73,40 @@ type CIDR struct { PrefixLen int } +// CIDRs returns a list of CIDR-aligned ranges which cover this range. +func (r Range) CIDRs() []CIDR { + const ( + fullMask = ^Address(0) + cidrMaxPrefixLen = 32 + ) + var cidrs []CIDR + + for start, end := r.Start, r.End-1; end >= start; { + mask, prefixLen := fullMask, cidrMaxPrefixLen + // Find the smallest mask which would cover some part of [start;end]. + // Once we found such, apply it by OR'ing + for mask > 0 { + tmpMask := mask << 1 + // Check whether mask neither too short nor too long + if (start&tmpMask) != start || (start|^tmpMask) > end { + break + } + mask = tmpMask + prefixLen-- + } + cidrs = append(cidrs, CIDR{start, prefixLen}) + // Apply mask + start |= ^mask + // Check for overflow + if start+1 < start { + break + } + start++ + } + + return cidrs +} + func ParseIP(s string) (Address, error) { if ip := net.ParseIP(s); ip != nil { return FromIP4(ip), nil diff --git a/net/address/address_test.go b/net/address/address_test.go index 46f699b4..326a5b2e 100644 --- a/net/address/address_test.go +++ b/net/address/address_test.go @@ -39,3 +39,25 @@ func TestBiggestPow2AlignedRange(t *testing.T) { } require.NoError(t, quick.Check(prop, &quick.Config{MaxCount: 1000000})) } + +func ip(s string) Address { + addr, _ := ParseIP(s) + return addr +} + +func cidr(s string) CIDR { + c, err := ParseCIDR(s) + if err != nil { + panic(err) + } + return c +} + +func TestCIDRs(t *testing.T) { + start := ip("10.0.0.1") + end := ip("10.0.0.9") + r := NewRange(start, Subtract(end, start)) + require.Equal(t, + []CIDR{cidr("10.0.0.1/32"), cidr("10.0.0.2/31"), cidr("10.0.0.4/30"), cidr("10.0.0.8/32")}, + r.CIDRs()) +}