Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Num prefixes #18

Merged
merged 6 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion ipv4/prefix.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ipv4

import (
"fmt"
"math"
"net"
)

Expand Down Expand Up @@ -173,7 +174,22 @@ func (me Prefix) Contains(other SetI) bool {
// NumAddresses returns the number of addresses in the prefix, including network and
// broadcast addresses. It ignores any bits set in the host part of the address.
func (me Prefix) NumAddresses() int64 {
return 1 << (addressSize - me.Length())
count, _ := me.NumPrefixes(32)
return int64(count)
}

// NumPrefixes returns the number of prefixes of the given length contained in
// this prefix.
func (me Prefix) NumPrefixes(length uint32) (count uint64, err error) {
switch {
case 32 < length:
err = fmt.Errorf("length is greater than 32")
case length < me.length:
count = 0
default:
count = uint64(math.Pow(2, float64(length-me.length)))
}
return
}

// String returns the string representation of this prefix in dotted-quad cidr
Expand Down
43 changes: 43 additions & 0 deletions ipv4/prefix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -534,3 +534,46 @@ func TestPrefixAsMapKey(t *testing.T) {

assert.True(t, m[_p("203.0.113.1/32")])
}

func TestPrefixNumPrefixes(t *testing.T) {
tests := []struct {
description string
prefix Prefix
length uint32
count uint64
error bool
}{
{
description: "bad length",
length: 33,
error: true,
}, {
description: "same size",
prefix: _p("203.0.113.0/24"),
length: 24,
count: 1,
}, {
description: "too big",
prefix: _p("203.0.113.0/24"),
length: 23,
}, {
description: "28",
prefix: _p("203.0.113.0/24"),
length: 30,
count: 0x40,
}, {
description: "26",
prefix: _p("203.0.113.0/24"),
length: 26,
count: 4,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
count, err := tt.prefix.NumPrefixes(tt.length)
assert.Equal(t, tt.error, err != nil)
assert.Equal(t, tt.count, count)
})
}
}
9 changes: 8 additions & 1 deletion ipv4/range.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,16 @@ func RangeFromAddresses(first, last Address) (r Range, empty bool) {
}, false
}

// NumPrefixes returns the number of prefixes of the given prefix length in
// this range.
func (me Range) NumPrefixes(length uint32) (count uint64, err error) {
return me.Set().NumPrefixes(length)
}

// NumAddresses returns the number of addresses in the range
func (me Range) NumAddresses() int64 {
return 1 + int64(me.last.ui-me.first.ui)
c, _ := me.NumPrefixes(32)
return int64(c)
}

// First returns the first address in the range
Expand Down
30 changes: 30 additions & 0 deletions ipv4/range_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,33 @@ func TestRangeAsMapKey(t *testing.T) {

assert.True(t, m[_r(_a("203.0.113.0"), _a("203.0.113.127"))])
}

func TestRangeNumPrefixes(t *testing.T) {
tests := []struct {
description string
r Range
length uint32
count uint64
error bool
}{
{
description: "simple",
r: _r(_a("203.0.113.0"), _a("203.0.114.0")),
length: 31,
count: 128,
}, {
description: "bonus",
r: _r(_a("203.0.113.0"), _a("203.0.114.0")),
length: 32,
count: 257,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
count, err := tt.r.NumPrefixes(tt.length)
assert.Equal(t, tt.error, err != nil)
assert.Equal(t, tt.count, count)
})
}
}
13 changes: 13 additions & 0 deletions ipv4/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,19 @@ func (me Set) WalkAddresses(callback func(Address) bool) bool {
})
}

// NumPrefixes returns the number of prefixes of the given prefix length in
// this set.
func (me Set) NumPrefixes(length uint32) (count uint64, err error) {
// NOTE This could be done more efficiently with its own recursive
// implementation that stops descending when the prefixes are too small.
me.WalkPrefixes(func(p Prefix) bool {
c, _ := p.NumPrefixes(length)
count += c
return true
})
return
}

// WalkRanges calls `callback` for each IP range in lexographical order. It
// stops iteration immediately if callback returns false. It always uses the
// largest ranges possible so if two ranges are adjacent and can be combined,
Expand Down
93 changes: 93 additions & 0 deletions ipv4/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ipv4

import (
"math/rand"
"strconv"
"sync"
"testing"

Expand Down Expand Up @@ -860,3 +861,95 @@ func TestOldEqualAllIPv4(t *testing.T) {
assert.True(t, c.Equal(a))
assert.True(t, c.Equal(b))
}

func TestSetNumPrefixes(t *testing.T) {
tests := []struct {
description string
prefixes []SetI
length uint32
count uint64
error bool
}{
{
description: "empty set",
prefixes: []SetI{},
length: 32,
count: 0,
}, {
description: "single prefix",
prefixes: []SetI{_p("203.0.113.0/8")},
length: 16,
count: 256,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
s := Set{}.Build(func(s Set_) bool {
for _, prefix := range tt.prefixes {
s.Insert(prefix)
}
return true
})

count, err := s.NumPrefixes(tt.length)
assert.Equal(t, tt.error, err != nil)
if !tt.error {
assert.Equal(t, tt.count, count)
}
})
}
}

func TestSetNumPrefixesStairs(t *testing.T) {
tests := []struct {
description string
length uint32
count uint64
}{
{length: 8, count: 0x00001},
{length: 9, count: 0x00003},
{length: 10, count: 0x00007},
{length: 11, count: 0x0000f},
{length: 12, count: 0x0001f},
{length: 13, count: 0x0003f},
{length: 14, count: 0x0007f},
{length: 15, count: 0x000ff},
{length: 16, count: 0x001ff},
{length: 17, count: 0x003ff},
{length: 18, count: 0x007ff},
{length: 19, count: 0x00fff},
{length: 20, count: 0x01fff},
{length: 21, count: 0x03fff},
{length: 22, count: 0x07fff},
{length: 23, count: 0x0fffe},
{length: 24, count: 0x1fffc},
}

s := Set{}.Build(func(s Set_) bool {
s.Insert(_p("10.0.0.0/8"))
s.Insert(_p("11.0.0.0/9"))
s.Insert(_p("11.128.0.0/10"))
s.Insert(_p("11.192.0.0/11"))
s.Insert(_p("11.224.0.0/12"))
s.Insert(_p("11.240.0.0/13"))
s.Insert(_p("11.248.0.0/14"))
s.Insert(_p("11.252.0.0/15"))
s.Insert(_p("11.254.0.0/16"))
s.Insert(_p("11.255.0.0/17"))
s.Insert(_p("11.255.128.0/18"))
s.Insert(_p("11.255.192.0/19"))
s.Insert(_p("11.255.224.0/20"))
s.Insert(_p("11.255.240.0/21"))
s.Insert(_p("11.255.248.0/22"))
return true
})

for _, tt := range tests {
t.Run(strconv.FormatUint(uint64(tt.length), 10), func(t *testing.T) {
count, err := s.NumPrefixes(tt.length)
assert.Nil(t, err)
assert.Equal(t, tt.count, count)
})
}
}
17 changes: 17 additions & 0 deletions ipv6/prefix.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ipv6

import (
"fmt"
"math"
"net"
)

Expand Down Expand Up @@ -116,6 +117,22 @@ func (me Prefix) Length() int {
return int(me.length)
}

// NumPrefixes returns the number of prefixes of the given length contained in
// this prefix.
func (me Prefix) NumPrefixes(length uint32) (count uint64, err error) {
switch {
case 128 < length:
err = fmt.Errorf("length is greater than 128")
case length < me.length:
count = 0
case length-me.length > 63:
err = fmt.Errorf("overflow")
default:
count = uint64(math.Pow(2, float64(length-me.length)))
}
return
}

// Mask returns a new Address with 1s in the first `length` bits and then 0s
// representing the network mask for this prefix.
func (me Prefix) Mask() Mask {
Expand Down
47 changes: 47 additions & 0 deletions ipv6/prefix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,3 +488,50 @@ func TestPrefixAsMapKey(t *testing.T) {

assert.True(t, m[_p("2001::/56")])
}

func TestPrefixNumPrefixes(t *testing.T) {
tests := []struct {
description string
prefix Prefix
length uint32
count uint64
error bool
}{
{
description: "overflow",
length: 64,
error: true,
}, {
description: "bad length",
length: 129,
error: true,
}, {
description: "same size",
prefix: _p("2001:db8::/64"),
length: 64,
count: 1,
}, {
description: "too big",
prefix: _p("2001:db8::/64"),
length: 32,
}, {
description: "32",
prefix: _p("2001:db8::/32"),
length: 64,
count: 0x100000000,
}, {
description: "56",
prefix: _p("2001:db8::/56"),
length: 64,
count: 256,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
count, err := tt.prefix.NumPrefixes(tt.length)
assert.Equal(t, tt.error, err != nil)
assert.Equal(t, tt.count, count)
})
}
}
6 changes: 6 additions & 0 deletions ipv6/range.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ func (me Range) Contains(other SetI) bool {
return me.Set().Contains(other)
}

// NumPrefixes returns the number of prefixes of the given prefix length in
// this range.
func (me Range) NumPrefixes(length uint32) (count uint64, err error) {
return me.Set().NumPrefixes(length)
}

// Minus returns a slice of ranges resulting from subtracting the given range
// The slice will contain from 0 to 2 new ranges depending on how they overlap
func (me Range) Minus(other Range) []Range {
Expand Down
30 changes: 30 additions & 0 deletions ipv6/range_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,33 @@ func TestRangeAsMapKey(t *testing.T) {

assert.True(t, m[_r(_a("2001::"), _a("2001::1000:0"))])
}

func TestRangeNumPrefixes(t *testing.T) {
tests := []struct {
description string
r Range
length uint32
count uint64
error bool
}{
{
description: "simple",
r: _r(_a("2001::"), _a("2002::")),
length: 24,
count: 256,
}, {
description: "bonus",
r: _r(_a("2001::"), _a("2001::1:0")),
length: 128,
count: 0x10001,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
count, err := tt.r.NumPrefixes(tt.length)
assert.Equal(t, tt.error, err != nil)
assert.Equal(t, tt.count, count)
})
}
}
Loading
Loading