|  | // Copyright 2020 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package netip | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "encoding" | 
|  | "encoding/json" | 
|  | "strings" | 
|  | "testing" | 
|  | ) | 
|  |  | 
|  | var ( | 
|  | mustPrefix = MustParsePrefix | 
|  | mustIP     = MustParseAddr | 
|  | ) | 
|  |  | 
|  | func TestPrefixValid(t *testing.T) { | 
|  | v4 := MustParseAddr("1.2.3.4") | 
|  | v6 := MustParseAddr("::1") | 
|  | tests := []struct { | 
|  | ipp  Prefix | 
|  | want bool | 
|  | }{ | 
|  | {Prefix{v4, -2}, false}, | 
|  | {Prefix{v4, -1}, false}, | 
|  | {Prefix{v4, 0}, true}, | 
|  | {Prefix{v4, 32}, true}, | 
|  | {Prefix{v4, 33}, false}, | 
|  |  | 
|  | {Prefix{v6, -2}, false}, | 
|  | {Prefix{v6, -1}, false}, | 
|  | {Prefix{v6, 0}, true}, | 
|  | {Prefix{v6, 32}, true}, | 
|  | {Prefix{v6, 128}, true}, | 
|  | {Prefix{v6, 129}, false}, | 
|  |  | 
|  | {Prefix{Addr{}, -2}, false}, | 
|  | {Prefix{Addr{}, -1}, false}, | 
|  | {Prefix{Addr{}, 0}, false}, | 
|  | {Prefix{Addr{}, 32}, false}, | 
|  | {Prefix{Addr{}, 128}, false}, | 
|  | } | 
|  | for _, tt := range tests { | 
|  | got := tt.ipp.IsValid() | 
|  | if got != tt.want { | 
|  | t.Errorf("(%v).IsValid() = %v want %v", tt.ipp, got, tt.want) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | var nextPrevTests = []struct { | 
|  | ip   Addr | 
|  | next Addr | 
|  | prev Addr | 
|  | }{ | 
|  | {mustIP("10.0.0.1"), mustIP("10.0.0.2"), mustIP("10.0.0.0")}, | 
|  | {mustIP("10.0.0.255"), mustIP("10.0.1.0"), mustIP("10.0.0.254")}, | 
|  | {mustIP("127.0.0.1"), mustIP("127.0.0.2"), mustIP("127.0.0.0")}, | 
|  | {mustIP("254.255.255.255"), mustIP("255.0.0.0"), mustIP("254.255.255.254")}, | 
|  | {mustIP("255.255.255.255"), Addr{}, mustIP("255.255.255.254")}, | 
|  | {mustIP("0.0.0.0"), mustIP("0.0.0.1"), Addr{}}, | 
|  | {mustIP("::"), mustIP("::1"), Addr{}}, | 
|  | {mustIP("::%x"), mustIP("::1%x"), Addr{}}, | 
|  | {mustIP("::1"), mustIP("::2"), mustIP("::")}, | 
|  | {mustIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), Addr{}, mustIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe")}, | 
|  | } | 
|  |  | 
|  | func TestIPNextPrev(t *testing.T) { | 
|  | doNextPrev(t) | 
|  |  | 
|  | for _, ip := range []Addr{ | 
|  | mustIP("0.0.0.0"), | 
|  | mustIP("::"), | 
|  | } { | 
|  | got := ip.Prev() | 
|  | if !got.isZero() { | 
|  | t.Errorf("IP(%v).Prev = %v; want zero", ip, got) | 
|  | } | 
|  | } | 
|  |  | 
|  | var allFF [16]byte | 
|  | for i := range allFF { | 
|  | allFF[i] = 0xff | 
|  | } | 
|  |  | 
|  | for _, ip := range []Addr{ | 
|  | mustIP("255.255.255.255"), | 
|  | AddrFrom16(allFF), | 
|  | } { | 
|  | got := ip.Next() | 
|  | if !got.isZero() { | 
|  | t.Errorf("IP(%v).Next = %v; want zero", ip, got) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkIPNextPrev(b *testing.B) { | 
|  | for i := 0; i < b.N; i++ { | 
|  | doNextPrev(b) | 
|  | } | 
|  | } | 
|  |  | 
|  | func doNextPrev(t testing.TB) { | 
|  | for _, tt := range nextPrevTests { | 
|  | gnext, gprev := tt.ip.Next(), tt.ip.Prev() | 
|  | if gnext != tt.next { | 
|  | t.Errorf("IP(%v).Next = %v; want %v", tt.ip, gnext, tt.next) | 
|  | } | 
|  | if gprev != tt.prev { | 
|  | t.Errorf("IP(%v).Prev = %v; want %v", tt.ip, gprev, tt.prev) | 
|  | } | 
|  | if !tt.ip.Next().isZero() && tt.ip.Next().Prev() != tt.ip { | 
|  | t.Errorf("IP(%v).Next.Prev = %v; want %v", tt.ip, tt.ip.Next().Prev(), tt.ip) | 
|  | } | 
|  | if !tt.ip.Prev().isZero() && tt.ip.Prev().Next() != tt.ip { | 
|  | t.Errorf("IP(%v).Prev.Next = %v; want %v", tt.ip, tt.ip.Prev().Next(), tt.ip) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestIPBitLen(t *testing.T) { | 
|  | tests := []struct { | 
|  | ip   Addr | 
|  | want int | 
|  | }{ | 
|  | {Addr{}, 0}, | 
|  | {mustIP("0.0.0.0"), 32}, | 
|  | {mustIP("10.0.0.1"), 32}, | 
|  | {mustIP("::"), 128}, | 
|  | {mustIP("fed0::1"), 128}, | 
|  | {mustIP("::ffff:10.0.0.1"), 128}, | 
|  | } | 
|  | for _, tt := range tests { | 
|  | got := tt.ip.BitLen() | 
|  | if got != tt.want { | 
|  | t.Errorf("BitLen(%v) = %d; want %d", tt.ip, got, tt.want) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestPrefixContains(t *testing.T) { | 
|  | tests := []struct { | 
|  | ipp  Prefix | 
|  | ip   Addr | 
|  | want bool | 
|  | }{ | 
|  | {mustPrefix("9.8.7.6/0"), mustIP("9.8.7.6"), true}, | 
|  | {mustPrefix("9.8.7.6/16"), mustIP("9.8.7.6"), true}, | 
|  | {mustPrefix("9.8.7.6/16"), mustIP("9.8.6.4"), true}, | 
|  | {mustPrefix("9.8.7.6/16"), mustIP("9.9.7.6"), false}, | 
|  | {mustPrefix("9.8.7.6/32"), mustIP("9.8.7.6"), true}, | 
|  | {mustPrefix("9.8.7.6/32"), mustIP("9.8.7.7"), false}, | 
|  | {mustPrefix("9.8.7.6/32"), mustIP("9.8.7.7"), false}, | 
|  | {mustPrefix("::1/0"), mustIP("::1"), true}, | 
|  | {mustPrefix("::1/0"), mustIP("::2"), true}, | 
|  | {mustPrefix("::1/127"), mustIP("::1"), true}, | 
|  | {mustPrefix("::1/127"), mustIP("::2"), false}, | 
|  | {mustPrefix("::1/128"), mustIP("::1"), true}, | 
|  | {mustPrefix("::1/127"), mustIP("::2"), false}, | 
|  | // Zones ignored: https://go.dev/issue/51899 | 
|  | {Prefix{mustIP("1.2.3.4").WithZone("a"), 32}, mustIP("1.2.3.4"), true}, | 
|  | {Prefix{mustIP("::1").WithZone("a"), 128}, mustIP("::1"), true}, | 
|  | // invalid IP | 
|  | {mustPrefix("::1/0"), Addr{}, false}, | 
|  | {mustPrefix("1.2.3.4/0"), Addr{}, false}, | 
|  | // invalid Prefix | 
|  | {Prefix{mustIP("::1"), 129}, mustIP("::1"), false}, | 
|  | {Prefix{mustIP("1.2.3.4"), 33}, mustIP("1.2.3.4"), false}, | 
|  | {Prefix{Addr{}, 0}, mustIP("1.2.3.4"), false}, | 
|  | {Prefix{Addr{}, 32}, mustIP("1.2.3.4"), false}, | 
|  | {Prefix{Addr{}, 128}, mustIP("::1"), false}, | 
|  | // wrong IP family | 
|  | {mustPrefix("::1/0"), mustIP("1.2.3.4"), false}, | 
|  | {mustPrefix("1.2.3.4/0"), mustIP("::1"), false}, | 
|  | } | 
|  | for _, tt := range tests { | 
|  | got := tt.ipp.Contains(tt.ip) | 
|  | if got != tt.want { | 
|  | t.Errorf("(%v).Contains(%v) = %v want %v", tt.ipp, tt.ip, got, tt.want) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestParseIPError(t *testing.T) { | 
|  | tests := []struct { | 
|  | ip     string | 
|  | errstr string | 
|  | }{ | 
|  | { | 
|  | ip: "localhost", | 
|  | }, | 
|  | { | 
|  | ip:     "500.0.0.1", | 
|  | errstr: "field has value >255", | 
|  | }, | 
|  | { | 
|  | ip:     "::gggg%eth0", | 
|  | errstr: "must have at least one digit", | 
|  | }, | 
|  | { | 
|  | ip:     "fe80::1cc0:3e8c:119f:c2e1%", | 
|  | errstr: "zone must be a non-empty string", | 
|  | }, | 
|  | { | 
|  | ip:     "%eth0", | 
|  | errstr: "missing IPv6 address", | 
|  | }, | 
|  | } | 
|  | for _, test := range tests { | 
|  | t.Run(test.ip, func(t *testing.T) { | 
|  | _, err := ParseAddr(test.ip) | 
|  | if err == nil { | 
|  | t.Fatal("no error") | 
|  | } | 
|  | if _, ok := err.(parseAddrError); !ok { | 
|  | t.Errorf("error type is %T, want parseIPError", err) | 
|  | } | 
|  | if test.errstr == "" { | 
|  | test.errstr = "unable to parse IP" | 
|  | } | 
|  | if got := err.Error(); !strings.Contains(got, test.errstr) { | 
|  | t.Errorf("error is missing substring %q: %s", test.errstr, got) | 
|  | } | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestParseAddrPort(t *testing.T) { | 
|  | tests := []struct { | 
|  | in      string | 
|  | want    AddrPort | 
|  | wantErr bool | 
|  | }{ | 
|  | {in: "1.2.3.4:1234", want: AddrPort{mustIP("1.2.3.4"), 1234}}, | 
|  | {in: "1.1.1.1:123456", wantErr: true}, | 
|  | {in: "1.1.1.1:-123", wantErr: true}, | 
|  | {in: "[::1]:1234", want: AddrPort{mustIP("::1"), 1234}}, | 
|  | {in: "[1.2.3.4]:1234", wantErr: true}, | 
|  | {in: "fe80::1:1234", wantErr: true}, | 
|  | {in: ":0", wantErr: true}, // if we need to parse this form, there should be a separate function that explicitly allows it | 
|  | } | 
|  | for _, test := range tests { | 
|  | t.Run(test.in, func(t *testing.T) { | 
|  | got, err := ParseAddrPort(test.in) | 
|  | if err != nil { | 
|  | if test.wantErr { | 
|  | return | 
|  | } | 
|  | t.Fatal(err) | 
|  | } | 
|  | if got != test.want { | 
|  | t.Errorf("got %v; want %v", got, test.want) | 
|  | } | 
|  | if got.String() != test.in { | 
|  | t.Errorf("String = %q; want %q", got.String(), test.in) | 
|  | } | 
|  | }) | 
|  |  | 
|  | t.Run(test.in+"/AppendTo", func(t *testing.T) { | 
|  | got, err := ParseAddrPort(test.in) | 
|  | if err == nil { | 
|  | testAppendToMarshal(t, got) | 
|  | } | 
|  | }) | 
|  |  | 
|  | // TextMarshal and TextUnmarshal mostly behave like | 
|  | // ParseAddrPort and String. Divergent behavior are handled in | 
|  | // TestAddrPortMarshalUnmarshal. | 
|  | t.Run(test.in+"/Marshal", func(t *testing.T) { | 
|  | var got AddrPort | 
|  | jsin := `"` + test.in + `"` | 
|  | err := json.Unmarshal([]byte(jsin), &got) | 
|  | if err != nil { | 
|  | if test.wantErr { | 
|  | return | 
|  | } | 
|  | t.Fatal(err) | 
|  | } | 
|  | if got != test.want { | 
|  | t.Errorf("got %v; want %v", got, test.want) | 
|  | } | 
|  | gotb, err := json.Marshal(got) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | if string(gotb) != jsin { | 
|  | t.Errorf("Marshal = %q; want %q", string(gotb), jsin) | 
|  | } | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestAddrPortMarshalUnmarshal(t *testing.T) { | 
|  | tests := []struct { | 
|  | in   string | 
|  | want AddrPort | 
|  | }{ | 
|  | {"", AddrPort{}}, | 
|  | } | 
|  |  | 
|  | for _, test := range tests { | 
|  | t.Run(test.in, func(t *testing.T) { | 
|  | orig := `"` + test.in + `"` | 
|  |  | 
|  | var ipp AddrPort | 
|  | if err := json.Unmarshal([]byte(orig), &ipp); err != nil { | 
|  | t.Fatalf("failed to unmarshal: %v", err) | 
|  | } | 
|  |  | 
|  | ippb, err := json.Marshal(ipp) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to marshal: %v", err) | 
|  | } | 
|  |  | 
|  | back := string(ippb) | 
|  | if orig != back { | 
|  | t.Errorf("Marshal = %q; want %q", back, orig) | 
|  | } | 
|  |  | 
|  | testAppendToMarshal(t, ipp) | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | type appendMarshaler interface { | 
|  | encoding.TextMarshaler | 
|  | AppendTo([]byte) []byte | 
|  | } | 
|  |  | 
|  | // testAppendToMarshal tests that x's AppendTo and MarshalText methods yield the same results. | 
|  | // x's MarshalText method must not return an error. | 
|  | func testAppendToMarshal(t *testing.T, x appendMarshaler) { | 
|  | t.Helper() | 
|  | m, err := x.MarshalText() | 
|  | if err != nil { | 
|  | t.Fatalf("(%v).MarshalText: %v", x, err) | 
|  | } | 
|  | a := make([]byte, 0, len(m)) | 
|  | a = x.AppendTo(a) | 
|  | if !bytes.Equal(m, a) { | 
|  | t.Errorf("(%v).MarshalText = %q, (%v).AppendTo = %q", x, m, x, a) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestIPv6Accessor(t *testing.T) { | 
|  | var a [16]byte | 
|  | for i := range a { | 
|  | a[i] = uint8(i) + 1 | 
|  | } | 
|  | ip := AddrFrom16(a) | 
|  | for i := range a { | 
|  | if got, want := ip.v6(uint8(i)), uint8(i)+1; got != want { | 
|  | t.Errorf("v6(%v) = %v; want %v", i, got, want) | 
|  | } | 
|  | } | 
|  | } |