diff --git a/benchmark_test.go b/benchmark_test.go index f3cefa3..eb8bb2c 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -97,6 +97,26 @@ func BenchmarkCursorNextIPv6(b *testing.B) { } } +func BenchmarkCursorPrevIPv4(b *testing.B) { + ps := toPrefixes([]string{"192.168.0.255/24"}) + c := ipaddr.NewCursor(ps) + + for i := 0; i < b.N; i++ { + for c.Prev() != nil { + } + } +} + +func BenchmarkCursorPrevIPv6(b *testing.B) { + ps := toPrefixes([]string{"2001:db8::ff/120"}) + c := ipaddr.NewCursor(ps) + + for i := 0; i < b.N; i++ { + for c.Prev() != nil { + } + } +} + func BenchmarkPrefixEqualIPv4(b *testing.B) { ps := toPrefixes([]string{"192.168.1.0/24", "192.168.2.0/24"}) diff --git a/cursor.go b/cursor.go index 76a0613..822fef6 100644 --- a/cursor.go +++ b/cursor.go @@ -9,44 +9,28 @@ import ( "net" ) -// A Position represents a position on a cursor. -type Position struct { - IP net.IP // IP address - Prefix Prefix // IP address prefix -} - -// A Cursor repesents a movable indicator on multiple prefixes. +// A Cursor repesents a movable indicator on a single or multiple +// prefixes. type Cursor struct { - curr, last ipv6Int + curr, start, end ipv6Int pi int - ps []Prefix // IP address prefixes -} - -func (c *Cursor) next() { - c.pi++ - ip := c.ps[c.pi].IP.To16() - c.curr = ipToIPv6Int(ip) - if ip.To4() != nil { - c.last = c.ps[c.pi].lastIPv4MappedInt() - } - if ip.To16() != nil && ip.To4() == nil { - c.last = c.ps[c.pi].lastIPv6Int() - } + ps []Prefix } func (c *Cursor) set(pi int, ip net.IP) { c.pi = pi c.curr = ipToIPv6Int(ip.To16()) + c.start = ipToIPv6Int(c.ps[c.pi].IP.To16()) if ip.To4() != nil { - c.last = c.ps[c.pi].lastIPv4MappedInt() + c.end = c.ps[c.pi].lastIPv4MappedIPv6Int() } if ip.To16() != nil && ip.To4() == nil { - c.last = c.ps[c.pi].lastIPv6Int() + c.end = c.ps[c.pi].lastIPv6Int() } } -// First returns the first position on the cursor c. +// First returns the start position on the cursor c. func (c *Cursor) First() *Position { return &Position{IP: c.ps[0].IP, Prefix: c.ps[0]} } @@ -61,15 +45,23 @@ func (c *Cursor) List() []Prefix { return c.ps } -// Next returns the next position on the cursor c. +// Next turns to the next postion on the cursor c. // It returns nil at the end on the cursor c. func (c *Cursor) Next() *Position { - n := c.curr.cmp(&c.last) + n := c.curr.cmp(&c.end) if n == 0 { if c.pi == len(c.ps)-1 { return nil } - c.next() + c.pi++ + c.curr = ipToIPv6Int(c.ps[c.pi].IP.To16()) + c.start = c.curr + if c.ps[c.pi].IP.To4() != nil { + c.end = c.ps[c.pi].lastIPv4MappedIPv6Int() + } + if c.ps[c.pi].IP.To16() != nil && c.ps[c.pi].IP.To4() == nil { + c.end = c.ps[c.pi].lastIPv6Int() + } } else { c.curr.incr() } @@ -81,6 +73,40 @@ func (c *Cursor) Pos() *Position { return &Position{IP: c.curr.ip(), Prefix: c.ps[c.pi]} } +// Prev turns to the previous position on the cursor c. +// It returns nil at the start on the cursor c. +func (c *Cursor) Prev() *Position { + n := c.curr.cmp(&c.start) + if n == 0 { + if c.pi == 0 { + return nil + } + c.pi-- + if c.ps[c.pi].IP.To4() != nil { + c.curr = c.ps[c.pi].lastIPv4MappedIPv6Int() + c.end = c.curr + } + if c.ps[c.pi].IP.To16() != nil && c.ps[c.pi].IP.To4() == nil { + c.curr = c.ps[c.pi].lastIPv6Int() + c.end = c.curr + } + c.start = ipToIPv6Int(c.ps[c.pi].IP.To16()) + } else { + c.curr.decr() + } + return c.Pos() +} + +// Reset resets all state and switches the prefixes to ps. +// It uses the existing prefixes when ps is nil. +func (c *Cursor) Reset(ps []Prefix) { + ps = sortAndDedup(ps, false) + if len(ps) > 0 { + c.ps = ps + } + c.set(0, c.ps[0].IP.To16()) +} + // Set sets the current postion on the cursor c to pos. func (c *Cursor) Set(pos *Position) error { if pos == nil { @@ -102,9 +128,6 @@ func (c *Cursor) Set(pos *Position) error { // NewCursor returns a new cursor. func NewCursor(ps []Prefix) *Cursor { - if len(ps) == 0 { - return nil - } ps = sortAndDedup(ps, false) if len(ps) == 0 { return nil diff --git a/cursor_test.go b/cursor_test.go index 7545b16..6a33483 100644 --- a/cursor_test.go +++ b/cursor_test.go @@ -13,56 +13,48 @@ import ( "github.com/mikioh/ipaddr" ) -func toPosition(s1, s2 string) *ipaddr.Position { - if s1 == "" || s2 == "" { - return nil - } - return &ipaddr.Position{IP: net.ParseIP(s1), Prefix: *toPrefix(s2)} -} - var cursorFirstLastIPTests = []struct { - in []string + in []ipaddr.Prefix first, last net.IP }{ // IPv4 prefixes { - []string{"0.0.0.0/0", "255.255.255.255/32"}, + toPrefixes([]string{"0.0.0.0/0", "255.255.255.255/32"}), net.ParseIP("0.0.0.0"), net.ParseIP("255.255.255.255"), }, { - []string{"192.168.0.0/32", "192.168.0.1/32", "192.168.0.2/32", "192.168.0.3/32", "192.168.4.0/24", "192.168.0.0/32", "192.168.0.1/32"}, + toPrefixes([]string{"192.168.0.0/32", "192.168.0.1/32", "192.168.0.2/32", "192.168.0.3/32", "192.168.4.0/24", "192.168.0.0/32", "192.168.0.1/32"}), net.ParseIP("192.168.0.0"), net.ParseIP("192.168.4.255"), }, { - []string{"192.168.0.1/32"}, + toPrefixes([]string{"192.168.0.1/32"}), net.ParseIP("192.168.0.1"), net.ParseIP("192.168.0.1"), }, // IPv6 prefixes { - []string{"::/0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"}, + toPrefixes([]string{"::/0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"}), net.ParseIP("::"), net.ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), }, { - []string{"2001:db8::/64", "2001:db8:0:1::/64", "2001:db8:0:2::/64", "2001:db8:0:3::/64", "2001:db8:0:4::/64", "2001:db8::/64", "2001:db8::1/64"}, + toPrefixes([]string{"2001:db8::/64", "2001:db8:0:1::/64", "2001:db8:0:2::/64", "2001:db8:0:3::/64", "2001:db8:0:4::/64", "2001:db8::/64", "2001:db8::1/64"}), net.ParseIP("2001:db8::"), net.ParseIP("2001:db8:0:4:ffff:ffff:ffff:ffff"), }, { - []string{"2001:db8::1/128"}, + toPrefixes([]string{"2001:db8::1/128"}), net.ParseIP("2001:db8::1"), net.ParseIP("2001:db8::1"), }, // Mixed prefixes { - []string{"192.168.0.1/32", "2001:db8::1/64", "192.168.255.0/24"}, + toPrefixes([]string{"192.168.0.1/32", "2001:db8::1/64", "192.168.255.0/24"}), net.ParseIP("192.168.0.1"), net.ParseIP("2001:db8::ffff:ffff:ffff:ffff"), }, } func TestCursorFirstLastIP(t *testing.T) { for i, tt := range cursorFirstLastIPTests { - in := toPrefixes(tt.in) - c := ipaddr.NewCursor(in) + c := ipaddr.NewCursor(tt.in) fpos, lpos := c.First(), c.Last() if !tt.first.Equal(fpos.IP) || !tt.last.Equal(lpos.IP) { t.Errorf("#%d: got %v, %v; want %v, %v", i, fpos.IP, lpos.IP, tt.first, tt.last) @@ -71,40 +63,39 @@ func TestCursorFirstLastIP(t *testing.T) { } var cursorFirstLastPrefixTests = []struct { - in []string + in []ipaddr.Prefix first, last *ipaddr.Prefix }{ // IPv4 prefixes { - []string{"0.0.0.0/0", "255.255.255.255/32"}, + toPrefixes([]string{"0.0.0.0/0", "255.255.255.255/32"}), toPrefix("0.0.0.0/0"), toPrefix("255.255.255.255/32"), }, { - []string{"192.168.0.0/32", "192.168.0.1/32", "192.168.0.2/32", "192.168.0.3/32", "192.168.4.0/24", "192.168.0.0/32", "192.168.0.1/32"}, + toPrefixes([]string{"192.168.0.0/32", "192.168.0.1/32", "192.168.0.2/32", "192.168.0.3/32", "192.168.4.0/24", "192.168.0.0/32", "192.168.0.1/32"}), toPrefix("192.168.0.0/32"), toPrefix("192.168.4.0/24"), }, // IPv6 prefixes { - []string{"::/0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"}, + toPrefixes([]string{"::/0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"}), toPrefix("::/0"), toPrefix("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"), }, { - []string{"2001:db8::/64", "2001:db8:0:1::/64", "2001:db8:0:2::/64", "2001:db8:0:3::/64", "2001:db8:0:4::/64", "2001:db8::/64", "2001:db8::1/64"}, + toPrefixes([]string{"2001:db8::/64", "2001:db8:0:1::/64", "2001:db8:0:2::/64", "2001:db8:0:3::/64", "2001:db8:0:4::/64", "2001:db8::/64", "2001:db8::1/64"}), toPrefix("2001:db8::/64"), toPrefix("2001:db8:0:4::/64"), }, // Mixed prefixes { - []string{"192.168.0.1/32", "2001:db8::1/64", "192.168.255.0/24"}, + toPrefixes([]string{"192.168.0.1/32", "2001:db8::1/64", "192.168.255.0/24"}), toPrefix("192.168.0.1/32"), toPrefix("2001:db8::/64"), }, } func TestCursorFirstLastPrefix(t *testing.T) { for i, tt := range cursorFirstLastPrefixTests { - in := toPrefixes(tt.in) - c := ipaddr.NewCursor(in) + c := ipaddr.NewCursor(tt.in) fpos, lpos := c.First(), c.Last() if !tt.first.Equal(&fpos.Prefix) || !tt.last.Equal(&lpos.Prefix) { t.Errorf("#%d: got %v, %v; want %v, %v", i, fpos.Prefix, lpos.Prefix, tt.first, tt.last) @@ -112,120 +103,181 @@ func TestCursorFirstLastPrefix(t *testing.T) { } } -var cursorNextTests = []struct { - ps []string - in, want *ipaddr.Position +var cursorPrevNextTests = []struct { + ps []ipaddr.Prefix + in, pwant, nwant *ipaddr.Position }{ // IPv4 prefixes { - []string{"192.168.0.0/24"}, - toPosition("192.168.0.255", "192.168.0.0/24"), nil, + toPrefixes([]string{"192.168.0.0/24"}), + toPosition("192.168.0.0", "192.168.0.0/24"), nil, toPosition("192.168.0.1", "192.168.0.0/24"), + }, + { + toPrefixes([]string{"192.168.0.0/24"}), + toPosition("192.168.0.255", "192.168.0.0/24"), toPosition("192.168.0.254", "192.168.0.0/24"), nil, }, { - []string{"192.168.0.0/24", "192.168.1.0/24"}, - toPosition("192.168.0.255", "192.168.0.0/24"), toPosition("192.168.1.0", "192.168.1.0/24"), + toPrefixes([]string{"192.168.0.0/24", "192.168.1.0/24"}), + toPosition("192.168.0.255", "192.168.0.0/24"), toPosition("192.168.0.254", "192.168.0.0/24"), toPosition("192.168.1.0", "192.168.1.0/24"), + }, + { + toPrefixes([]string{"192.168.0.0/24", "192.168.1.0/24"}), + toPosition("192.168.1.0", "192.168.1.0/24"), toPosition("192.168.0.255", "192.168.0.0/24"), toPosition("192.168.1.1", "192.168.1.0/24"), }, // IPv6 prefixes { - []string{"2001:db8::/64"}, - toPosition("2001:db8::ffff:ffff:ffff:ffff", "2001:db8::/64"), nil, + toPrefixes([]string{"2001:db8::/64"}), + toPosition("2001:db8::", "2001:db8::/64"), nil, toPosition("2001:db8::1", "2001:db8::/64"), + }, + { + toPrefixes([]string{"2001:db8::/64"}), + toPosition("2001:db8::ffff:ffff:ffff:ffff", "2001:db8::/64"), toPosition("2001:db8::ffff:ffff:ffff:fffe", "2001:db8::/64"), nil, + }, + { + toPrefixes([]string{"2001:db8::/64", "2001:db8:1::/64"}), + toPosition("2001:db8::ffff:ffff:ffff:ffff", "2001:db8::/64"), toPosition("2001:db8::ffff:ffff:ffff:fffe", "2001:db8::/64"), toPosition("2001:db8:1::", "2001:db8:1::/64"), }, { - []string{"2001:db8::/64", "2001:db8:1::/64"}, - toPosition("2001:db8::ffff:ffff:ffff:ffff", "2001:db8::/64"), toPosition("2001:db8:1::", "2001:db8:1::/64"), + toPrefixes([]string{"2001:db8::/64", "2001:db8:1::/64"}), + toPosition("2001:db8:1::", "2001:db8:1::/64"), toPosition("2001:db8::ffff:ffff:ffff:ffff", "2001:db8::/64"), toPosition("2001:db8:1::1", "2001:db8:1::/64"), }, // Mixed prefixes { - []string{"192.168.0.0/24", "2001:db8::/64"}, - toPosition("2001:db8::ffff:ffff:ffff:ffff", "2001:db8::/64"), nil, + toPrefixes([]string{"192.168.0.0/24", "2001:db8::/64"}), + toPosition("2001:db8::ffff:ffff:ffff:ffff", "2001:db8::/64"), toPosition("2001:db8::ffff:ffff:ffff:fffe", "2001:db8::/64"), nil, }, { - []string{"192.168.0.0/24", "::/64"}, - toPosition("192.168.0.255", "192.168.0.0/24"), nil, + toPrefixes([]string{"192.168.0.0/24", "2001:db8::/64"}), + toPosition("192.168.0.255", "192.168.0.0/24"), toPosition("192.168.0.254", "192.168.0.0/24"), toPosition("2001:db8::", "2001:db8::/64"), }, { - []string{"192.168.0.0/24", "2001:db8::/64"}, - toPosition("192.168.0.255", "192.168.0.0/24"), toPosition("2001:db8::", "2001:db8::/64"), + toPrefixes([]string{"192.168.0.0/24", "::/64"}), + toPosition("192.168.0.255", "192.168.0.0/24"), toPosition("192.168.0.254", "192.168.0.0/24"), nil, }, { - []string{"192.168.0.0/24", "::/64"}, - toPosition("::ffff:ffff:ffff:ffff", "::/64"), toPosition("192.168.0.0", "192.168.0.0/24"), + toPrefixes([]string{"192.168.0.0/24", "::/64"}), + toPosition("::ffff:ffff:ffff:ffff", "::/64"), toPosition("::ffff:ffff:ffff:fffe", "::/64"), toPosition("192.168.0.0", "192.168.0.0/24"), }, } -func TestCursorNext(t *testing.T) { - for i, tt := range cursorNextTests { - ps := toPrefixes(tt.ps) - c := ipaddr.NewCursor(ps) +func TestCursorPrevNext(t *testing.T) { + for i, tt := range cursorPrevNextTests { + c := ipaddr.NewCursor(tt.ps) if err := c.Set(tt.in); err != nil { t.Fatal(err) } - out := c.Next() - if !reflect.DeepEqual(out, tt.want) { - t.Errorf("#%d: got %v; want %v", i, out, tt.want) + out := c.Prev() + if !reflect.DeepEqual(out, tt.pwant) { + t.Errorf("#%d: got %v; want %v", i, out, tt.pwant) + } + if err := c.Set(tt.in); err != nil { + t.Fatal(err) + } + out = c.Next() + if !reflect.DeepEqual(out, tt.nwant) { + t.Errorf("#%d: got %v; want %v", i, out, tt.nwant) + } + } +} + +var cursorResetTests = []struct { + in []ipaddr.Prefix + out *ipaddr.Position +}{ + // IPv4 prefixes + { + toPrefixes([]string{"192.168.0.0/24"}), + toPosition("192.168.0.0", "192.168.0.0/24"), + }, + + // IPv6 prefixes + { + toPrefixes([]string{"2001:db8::/64"}), + toPosition("2001:db8::", "2001:db8::/64"), + }, + + // Mixed prefixes + { + toPrefixes([]string{"192.168.0.0/24", "2001:db8::/64"}), + toPosition("192.168.0.0", "192.168.0.0/24"), + }, + + { + nil, + toPosition("0.0.0.0", "0.0.0.0/0"), + }, +} + +func TestCursorReset(t *testing.T) { + for i, tt := range cursorResetTests { + in := toPrefixes([]string{"0.0.0.0/0"}) + c := ipaddr.NewCursor(in) + c.Reset(tt.in) + if !reflect.DeepEqual(c.Pos(), tt.out) { + t.Errorf("#%d: got %v; want %v", i, c.Pos(), tt.out) } } } var cursorPosTests = []struct { - ps []string + ps []ipaddr.Prefix in *ipaddr.Position error }{ // IPv4 prefixes { - []string{"192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24"}, + toPrefixes([]string{"192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24"}), toPosition("192.168.1.1", "192.168.1.0/24"), nil, }, { - []string{"192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24"}, + toPrefixes([]string{"192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24"}), toPosition("192.168.3.1", "192.168.1.0/24"), errors.New("should fail"), }, { - []string{"192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24"}, + toPrefixes([]string{"192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24"}), toPosition("192.168.1.1", "192.168.3.0/24"), errors.New("should fail"), }, // IPv6 prefixes { - []string{"2001:db8::/64", "2001:db8:1::/64", "2001:db8:2::/64"}, + toPrefixes([]string{"2001:db8::/64", "2001:db8:1::/64", "2001:db8:2::/64"}), toPosition("2001:db8:1::1", "2001:db8:1::/64"), nil, }, { - []string{"2001:db8::/64", "2001:db8:1::/64", "2001:db8:2::/64"}, + toPrefixes([]string{"2001:db8::/64", "2001:db8:1::/64", "2001:db8:2::/64"}), toPosition("2001:db8:3::1", "2001:db8:1::/64"), errors.New("should fail"), }, { - []string{"2001:db8::/64", "2001:db8:1::/64", "2001:db8:2::/64"}, + toPrefixes([]string{"2001:db8::/64", "2001:db8:1::/64", "2001:db8:2::/64"}), toPosition("2001:db8:1::1", "2001:db8:3::/64"), errors.New("should fail"), }, // Mixed prefixes { - []string{"192.168.0.0/24", "2001:db8::/64"}, + toPrefixes([]string{"192.168.0.0/24", "2001:db8::/64"}), toPosition("192.168.0.1", "192.168.0.0/24"), nil, }, { - []string{"192.168.0.0/24", "2001:db8::/64"}, + toPrefixes([]string{"192.168.0.0/24", "2001:db8::/64"}), toPosition("2001:db8::1", "2001:db8::/64"), nil, }, { - []string{"192.168.0.0/24", "2001:db8::/64"}, + toPrefixes([]string{"192.168.0.0/24", "2001:db8::/64"}), toPosition("2001:db8::1", "192.168.0.0/24"), errors.New("should fail"), }, { - []string{"192.168.0.0/24", "2001:db8::/64"}, + toPrefixes([]string{"192.168.0.0/24", "2001:db8::/64"}), toPosition("192.168.0.1", "2001:db8::/64"), errors.New("should fail"), }, @@ -233,8 +285,7 @@ var cursorPosTests = []struct { func TestCursorPos(t *testing.T) { for i, tt := range cursorPosTests { - ps := toPrefixes(tt.ps) - c := ipaddr.NewCursor(ps) + c := ipaddr.NewCursor(tt.ps) err := c.Set(tt.in) if err != nil && tt.error == nil { t.Errorf("#%d: got %v; want %v", i, err, tt.error) diff --git a/example_test.go b/example_test.go index 8eef821..9bd8884 100644 --- a/example_test.go +++ b/example_test.go @@ -27,6 +27,10 @@ func ExampleCursor_traversal() { fmt.Println(pos) } fmt.Println(c.Pos(), c.First(), c.Last(), c.List()) + for pos := c.Prev(); pos != nil; pos = c.Prev() { + fmt.Println(pos) + } + fmt.Println(c.Pos(), c.First(), c.Last(), c.List()) // Output: // &{192.168.0.0 192.168.0.0/29} &{192.168.0.0 192.168.0.0/29} &{2001:db8::3 2001:db8::/126} [192.168.0.0/29 192.168.1.128/30 2001:db8::/126] // &{192.168.0.1 192.168.0.0/29} @@ -45,6 +49,22 @@ func ExampleCursor_traversal() { // &{2001:db8::2 2001:db8::/126} // &{2001:db8::3 2001:db8::/126} // &{2001:db8::3 2001:db8::/126} &{192.168.0.0 192.168.0.0/29} &{2001:db8::3 2001:db8::/126} [192.168.0.0/29 192.168.1.128/30 2001:db8::/126] + // &{2001:db8::2 2001:db8::/126} + // &{2001:db8::1 2001:db8::/126} + // &{2001:db8:: 2001:db8::/126} + // &{192.168.1.131 192.168.1.128/30} + // &{192.168.1.130 192.168.1.128/30} + // &{192.168.1.129 192.168.1.128/30} + // &{192.168.1.128 192.168.1.128/30} + // &{192.168.0.7 192.168.0.0/29} + // &{192.168.0.6 192.168.0.0/29} + // &{192.168.0.5 192.168.0.0/29} + // &{192.168.0.4 192.168.0.0/29} + // &{192.168.0.3 192.168.0.0/29} + // &{192.168.0.2 192.168.0.0/29} + // &{192.168.0.1 192.168.0.0/29} + // &{192.168.0.0 192.168.0.0/29} + // &{192.168.0.0 192.168.0.0/29} &{192.168.0.0 192.168.0.0/29} &{2001:db8::3 2001:db8::/126} [192.168.0.0/29 192.168.1.128/30 2001:db8::/126] } func ExamplePrefix_subnettingAndSupernetting() { diff --git a/position.go b/position.go new file mode 100644 index 0000000..a9721af --- /dev/null +++ b/position.go @@ -0,0 +1,25 @@ +// Copyright 2015 Mikio Hara. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE. + +package ipaddr + +import "net" + +// A Position represents a position on IP address space. +type Position struct { + IP net.IP // IP address + Prefix Prefix // IP address prefix +} + +// IsBroadcast reports whether p is an IPv4 directed or limited +// broadcast address. +func (p *Position) IsBroadcast() bool { + return !p.IP.IsUnspecified() && !p.IP.IsMulticast() && p.IP.To4() != nil && (p.IP.Equal(net.IPv4bcast) || p.IP.Equal(p.Prefix.Last())) +} + +// IsSubnetRouterAnycast reports whether p is an IPv6 subnet router +// anycast address. +func (p *Position) IsSubnetRouterAnycast() bool { + return !p.IP.IsUnspecified() && !p.IP.IsLoopback() && !p.IP.IsMulticast() && p.IP.To16() != nil && p.IP.To4() == nil && p.IP.Equal(p.Prefix.IP) +} diff --git a/position_test.go b/position_test.go new file mode 100644 index 0000000..121f871 --- /dev/null +++ b/position_test.go @@ -0,0 +1,75 @@ +// Copyright 2015 Mikio Hara. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE. + +package ipaddr_test + +import ( + "net" + "testing" + + "github.com/mikioh/ipaddr" +) + +func toPosition(s1, s2 string) *ipaddr.Position { + if s1 == "" || s2 == "" { + return nil + } + return &ipaddr.Position{IP: net.ParseIP(s1), Prefix: *toPrefix(s2)} +} + +var isBroadcastTests = []struct { + in *ipaddr.Position + ok bool +}{ + {toPosition("192.168.0.255", "192.168.0.0/24"), true}, + {toPosition("255.255.255.255", "0.0.0.0/0"), true}, + + {toPosition("192.168.0.0", "192.168.0.0/24"), false}, + {toPosition("224.0.0.0", "224.0.0.0/4"), false}, + {toPosition("239.255.255.255", "224.0.0.0/4"), false}, + {toPosition("0.0.0.0", "0.0.0.0/0"), false}, + + {toPosition("2001:db8::", "2001:db8::/64"), false}, + {toPosition("2001:db8::ffff:ffff:ffff:ffff", "2001:db8::/64"), false}, + {toPosition("ff00::", "ff00::/8"), false}, + {toPosition("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ff00::/8"), false}, + {toPosition("::1", "::1/128"), false}, + {toPosition("::", "::/0"), false}, +} + +func TestIsBroadcast(t *testing.T) { + for i, tt := range isBroadcastTests { + if ok := tt.in.IsBroadcast(); ok != tt.ok { + t.Errorf("#%d: got %v for %v; want %v", i, ok, tt.in, tt.ok) + } + } +} + +var isSubnetRouterAnycastTests = []struct { + in *ipaddr.Position + ok bool +}{ + {toPosition("2001:db8::", "2001:db8::/64"), true}, + + {toPosition("2001:db8::ffff:ffff:ffff:ffff", "2001:db8::/64"), false}, + {toPosition("ff00::", "ff00::/8"), false}, + {toPosition("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ff00::/8"), false}, + {toPosition("::1", "::1/128"), false}, + {toPosition("::", "::/0"), false}, + + {toPosition("192.168.0.0", "192.168.0.0/24"), false}, + {toPosition("192.168.0.255", "192.168.0.0/24"), false}, + {toPosition("255.255.255.255", "0.0.0.0/0"), false}, + {toPosition("224.0.0.0", "224.0.0.0/4"), false}, + {toPosition("239.255.255.255", "224.0.0.0/4"), false}, + {toPosition("0.0.0.0", "0.0.0.0/0"), false}, +} + +func TestIsSubnetRouterAnycast(t *testing.T) { + for i, tt := range isSubnetRouterAnycastTests { + if ok := tt.in.IsSubnetRouterAnycast(); ok != tt.ok { + t.Errorf("#%d: got %v for %v; want %v", i, ok, tt.in, tt.ok) + } + } +} diff --git a/prefix.go b/prefix.go index c4a3fc2..0ed8193 100644 --- a/prefix.go +++ b/prefix.go @@ -38,7 +38,7 @@ func (p *Prefix) lastIPv4Int() ipv4Int { return ipToIPv4Int(p.IP) | ipv4Int(^mask32(p.Len())) } -func (p *Prefix) lastIPv4MappedInt() ipv6Int { +func (p *Prefix) lastIPv4MappedIPv6Int() ipv6Int { i4 := ipToIPv4Int(p.IP) | ipv4Int(^mask32(p.Len())) var i6 ipv6Int i6[1] = 0x0000ffff00000000 | uint64(i4) @@ -215,9 +215,6 @@ func (p *Prefix) UnmarshalText(txt []byte) error { // Aggregate aggregates the prefixes ps and returns a list of // aggregated prefixes. func Aggregate(ps []Prefix) []Prefix { - if len(ps) == 0 { - return nil - } ps = sortAndDedup(ps, true) if len(ps) == 0 { return nil @@ -357,7 +354,7 @@ func Summarize(first, last net.IP) []Prefix { return nil } -const ipv4IntEOR = ipv4Int(0xffffffff) +const ipv4IntEOR = ipv4Int(math.MaxUint32) func summarizeIPv4(fip, lip net.IP) []Prefix { var ps []Prefix @@ -383,7 +380,7 @@ func summarizeIPv4(fip, lip net.IP) []Prefix { return ps } -var ipv6IntEOR = ipv6Int{0xffffffffffffffff, 0xffffffffffffffff} +var ipv6IntEOR = ipv6Int{math.MaxUint64, math.MaxUint64} func summarizeIPv6(fip, lip net.IP) []Prefix { var ps []Prefix @@ -516,6 +513,9 @@ func (ps byAddrLen) Less(i, j int) bool { func (ps byAddrLen) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] } func sortAndDedup(ps []Prefix, strict bool) []Prefix { + if len(ps) == 0 { + return nil + } if strict { if ps[0].IP.To4() != nil { ps = byAddrFamily(ps).ipv4Only() @@ -589,6 +589,18 @@ func (a *ipv6Int) cmp(b *ipv6Int) int { return 0 } +func (i *ipv6Int) decr() { + if i[0] == 0 && i[1] == 0 { + return + } + if i[1] > 0 { + i[1]-- + } else { + i[0]-- + i[1] = math.MaxUint64 + } +} + func (i *ipv6Int) incr() { if i[1] == math.MaxUint64 { i[0]++ diff --git a/prefix_test.go b/prefix_test.go index 93dde19..0838c75 100644 --- a/prefix_test.go +++ b/prefix_test.go @@ -138,33 +138,32 @@ func TestAggregate(t *testing.T) { } var compareTests = []struct { - in []string + in []ipaddr.Prefix n int }{ - {[]string{"192.168.1.0/23", "192.168.1.0/24"}, -1}, - {[]string{"192.168.1.0/24", "192.168.1.0/24"}, 0}, - {[]string{"192.168.1.0/25", "192.168.1.0/24"}, +1}, + {toPrefixes([]string{"192.168.1.0/23", "192.168.1.0/24"}), -1}, + {toPrefixes([]string{"192.168.1.0/24", "192.168.1.0/24"}), 0}, + {toPrefixes([]string{"192.168.1.0/25", "192.168.1.0/24"}), +1}, - {[]string{"192.168.0.0/24", "192.168.1.0/24"}, -1}, - {[]string{"192.168.1.0/24", "192.168.1.0/24"}, 0}, - {[]string{"192.168.2.0/24", "192.168.1.0/24"}, +1}, + {toPrefixes([]string{"192.168.0.0/24", "192.168.1.0/24"}), -1}, + {toPrefixes([]string{"192.168.1.0/24", "192.168.1.0/24"}), 0}, + {toPrefixes([]string{"192.168.2.0/24", "192.168.1.0/24"}), +1}, - {[]string{"2001:db8:1::/47", "2001:db8:1::/48"}, -1}, - {[]string{"2001:db8:1::/48", "2001:db8:1::/48"}, 0}, - {[]string{"2001:db8:1::/49", "2001:db8:1::/48"}, +1}, + {toPrefixes([]string{"2001:db8:1::/47", "2001:db8:1::/48"}), -1}, + {toPrefixes([]string{"2001:db8:1::/48", "2001:db8:1::/48"}), 0}, + {toPrefixes([]string{"2001:db8:1::/49", "2001:db8:1::/48"}), +1}, - {[]string{"2001:db8:1::/128", "2001:db8:1::1/128"}, -1}, - {[]string{"2001:db8:1::1/128", "2001:db8:1::1/128"}, 0}, - {[]string{"2001:db8:1::2/128", "2001:db8:1::1/128"}, +1}, + {toPrefixes([]string{"2001:db8:1::/128", "2001:db8:1::1/128"}), -1}, + {toPrefixes([]string{"2001:db8:1::1/128", "2001:db8:1::1/128"}), 0}, + {toPrefixes([]string{"2001:db8:1::2/128", "2001:db8:1::1/128"}), +1}, - {[]string{"192.168.0.1/24", "2001:db8:1::1/64"}, -1}, - {[]string{"2001:db8:1::1/64", "192.168.0.1/24"}, +1}, + {toPrefixes([]string{"192.168.0.1/24", "2001:db8:1::1/64"}), -1}, + {toPrefixes([]string{"2001:db8:1::1/64", "192.168.0.1/24"}), +1}, } func TestCompare(t *testing.T) { for i, tt := range compareTests { - in := toPrefixes(tt.in) - if n := ipaddr.Compare(&in[0], &in[1]); n != tt.n { + if n := ipaddr.Compare(&tt.in[0], &tt.in[1]); n != tt.n { t.Errorf("#%d: got %v for %v; want %v", i, n, tt.in, tt.n) } }