| // 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 |
| }{ |
| {PrefixFrom(v4, -2), false}, |
| {PrefixFrom(v4, -1), false}, |
| {PrefixFrom(v4, 0), true}, |
| {PrefixFrom(v4, 32), true}, |
| {PrefixFrom(v4, 33), false}, |
| |
| {PrefixFrom(v6, -2), false}, |
| {PrefixFrom(v6, -1), false}, |
| {PrefixFrom(v6, 0), true}, |
| {PrefixFrom(v6, 32), true}, |
| {PrefixFrom(v6, 128), true}, |
| {PrefixFrom(v6, 129), false}, |
| |
| {PrefixFrom(Addr{}, -2), false}, |
| {PrefixFrom(Addr{}, -1), false}, |
| {PrefixFrom(Addr{}, 0), false}, |
| {PrefixFrom(Addr{}, 32), false}, |
| {PrefixFrom(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) |
| } |
| |
| // Test that there is only one invalid Prefix representation per Addr. |
| invalid := PrefixFrom(tt.ipp.Addr(), -1) |
| if !got && tt.ipp != invalid { |
| t.Errorf("(%v == %v) = false, want true", tt.ipp, invalid) |
| } |
| } |
| } |
| |
| 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 |
| {PrefixFrom(mustIP("::1"), 129), mustIP("::1"), false}, |
| {PrefixFrom(mustIP("1.2.3.4"), 33), mustIP("1.2.3.4"), false}, |
| {PrefixFrom(Addr{}, 0), mustIP("1.2.3.4"), false}, |
| {PrefixFrom(Addr{}, 32), mustIP("1.2.3.4"), false}, |
| {PrefixFrom(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) |
| } |
| } |
| } |