diff --git a/ip.go b/ip.go index f6d0e14..d542598 100644 --- a/ip.go +++ b/ip.go @@ -249,13 +249,145 @@ func IsIP(ip string) bool { } // IsIPv4 判断是否为合法 IPv4 -func IsIPv4(ip string) bool { - return ParseIPv4(ip) != nil +// IsIPv4 works the same way as net.ParseIP, +// but without check for IPv6 case and without returning net.IP slice, whereby IsIPv4 makes no allocations. +// Ref: gofiber/utils +func IsIPv4(s string) bool { + for i := 0; i < net.IPv4len; i++ { + if len(s) == 0 { + return false + } + + if i > 0 { + if s[0] != '.' { + return false + } + s = s[1:] + } + + n, ci := 0, 0 + + for ci = 0; ci < len(s) && '0' <= s[ci] && s[ci] <= '9'; ci++ { + n = n*10 + int(s[ci]-'0') + if n > 0xFF { + return false + } + } + + if ci == 0 || (ci > 1 && s[0] == '0') { + return false + } + + s = s[ci:] + } + + return len(s) == 0 } // IsIPv6 判断是否为合法 IPv6 -func IsIPv6(ip string) bool { - return ParseIPv6(ip) != nil +// IsIPv6 works the same way as net.ParseIP, +// but without check for IPv4 case and without returning net.IP slice, whereby IsIPv6 makes no allocations. +// Ref: gofiber/utils +func IsIPv6(s string) bool { + ellipsis := -1 // position of ellipsis in ip + + // Might have leading ellipsis + if len(s) >= 2 && s[0] == ':' && s[1] == ':' { + ellipsis = 0 + s = s[2:] + // Might be only ellipsis + if len(s) == 0 { + return true + } + } + + // Loop, parsing hex numbers followed by colon. + i := 0 + for i < net.IPv6len { + // Hex number. + n, ci := 0, 0 + + for ci = 0; ci < len(s); ci++ { + if '0' <= s[ci] && s[ci] <= '9' { + n *= 16 + n += int(s[ci] - '0') + } else if 'a' <= s[ci] && s[ci] <= 'f' { + n *= 16 + n += int(s[ci]-'a') + 10 + } else if 'A' <= s[ci] && s[ci] <= 'F' { + n *= 16 + n += int(s[ci]-'A') + 10 + } else { + break + } + if n > 0xFFFF { + return false + } + } + if ci == 0 || n > 0xFFFF { + return false + } + + if ci < len(s) && s[ci] == '.' { + if ellipsis < 0 && i != net.IPv6len-net.IPv4len { + return false + } + if i+net.IPv4len > net.IPv6len { + return false + } + + if !IsIPv4(s) { + return false + } + + s = "" + i += net.IPv4len + break + } + + // Save this 16-bit chunk. + i += 2 + + // Stop at end of string. + s = s[ci:] + if len(s) == 0 { + break + } + + // Otherwise must be followed by colon and more. + if s[0] != ':' || len(s) == 1 { + return false + } + s = s[1:] + + // Look for ellipsis. + if s[0] == ':' { + if ellipsis >= 0 { // already have one + return false + } + ellipsis = i + s = s[1:] + if len(s) == 0 { // can be at end + break + } + } + } + + // Must have used entire string. + if len(s) != 0 { + return false + } + + // If didn't parse enough, expand ellipsis. + if i < net.IPv6len { + if ellipsis < 0 { + return false + } + } else if ellipsis >= 0 { + // Ellipsis must represent at least one 0 group. + return false + } + return true } // ParseIPv4 判断是否为合法 IPv4 并解析 diff --git a/ip_test.go b/ip_test.go index 9d7a5e6..c71f279 100644 --- a/ip_test.go +++ b/ip_test.go @@ -318,3 +318,104 @@ func TestParseIPx(t *testing.T) { assert.Equal(t, v.isIPv6, isIPv6) } } + +// Ref: gofiber/utils +func Test_IsIPv4(t *testing.T) { + t.Parallel() + + assert.Equal(t, true, IsIPv4("255.255.255.255")) + assert.Equal(t, true, IsIPv4("174.23.33.100")) + assert.Equal(t, true, IsIPv4("127.0.0.1")) + assert.Equal(t, true, IsIPv4("0.0.0.0")) + + assert.Equal(t, false, IsIPv4("255.255.255.256")) + assert.Equal(t, false, IsIPv4(".0.0.0")) + assert.Equal(t, false, IsIPv4("0.0.0.")) + assert.Equal(t, false, IsIPv4("0.0.0")) + assert.Equal(t, false, IsIPv4(".0.0.0.")) + assert.Equal(t, false, IsIPv4("0.0.0.0.0")) + assert.Equal(t, false, IsIPv4("0")) + assert.Equal(t, false, IsIPv4("")) + assert.Equal(t, false, IsIPv4("2345:0425:2CA1::0567:5673:23b5")) + assert.Equal(t, false, IsIPv4("invalid")) + assert.Equal(t, false, IsIPv4("189.12.34.260")) + assert.Equal(t, false, IsIPv4("189.12.260.260")) + assert.Equal(t, false, IsIPv4("189.260.260.260")) + assert.Equal(t, false, IsIPv4("999.999.999.999")) + assert.Equal(t, false, IsIPv4("9999.9999.9999.9999")) +} + +// Ref: gofiber/utils +func Benchmark_IsIPv4(b *testing.B) { + ip := "174.23.33.100" + var res bool + + b.Run("fiber", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IsIPv4(ip) + } + assert.Equal(b, true, res) + }) + + b.Run("default", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = net.ParseIP(ip) != nil + } + assert.Equal(b, true, res) + }) +} + +// Ref: gofiber/utils +func Test_IsIPv6(t *testing.T) { + t.Parallel() + + assert.Equal(t, true, IsIPv6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")) + assert.Equal(t, true, IsIPv6("9396:9549:b4f7:8ed0:4791:1330:8c06:e62d")) + assert.Equal(t, true, IsIPv6("2345:0425:2CA1::0567:5673:23b5")) + assert.Equal(t, true, IsIPv6("2001:1:2:3:4:5:6:7")) + assert.Equal(t, true, IsIPv6("2001::")) + assert.Equal(t, true, IsIPv6("::")) + + assert.Equal(t, false, IsIPv6("2001::1::1")) + assert.Equal(t, false, IsIPv6("2001::fffg")) + assert.Equal(t, false, IsIPv6("1.1.1.1")) + assert.Equal(t, false, IsIPv6("2001:1:2:3:4:5:6:")) + assert.Equal(t, false, IsIPv6(":1:2:3:4:5:6:")) + assert.Equal(t, false, IsIPv6("1:2:3:4:5:6:")) + assert.Equal(t, false, IsIPv6("")) + assert.Equal(t, false, IsIPv6("invalid")) +} + +// Ref: gofiber/utils +func Benchmark_IsIPv6(b *testing.B) { + ip := "9396:9549:b4f7:8ed0:4791:1330:8c06:e62d" + var res bool + + b.Run("fiber", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IsIPv6(ip) + } + assert.Equal(b, true, res) + }) + + b.Run("default", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = net.ParseIP(ip) != nil + } + assert.Equal(b, true, res) + }) +} + +// go test -run=^$ -benchmem -benchtime=1s -count=2 -bench=Benchmark_IsIP +// goos: linux +// goarch: amd64 +// pkg: github.com/fufuok/utils +// cpu: AMD Ryzen 7 5700G with Radeon Graphics +// Benchmark_IsIPv4/fiber-16 88282953 13.25 ns/op 0 B/op 0 allocs/op +// Benchmark_IsIPv4/fiber-16 90268933 13.23 ns/op 0 B/op 0 allocs/op +// Benchmark_IsIPv4/default-16 45390993 25.53 ns/op 0 B/op 0 allocs/op +// Benchmark_IsIPv4/default-16 46329868 25.49 ns/op 0 B/op 0 allocs/op +// Benchmark_IsIPv6/fiber-16 27761413 44.99 ns/op 0 B/op 0 allocs/op +// Benchmark_IsIPv6/fiber-16 27327100 43.97 ns/op 0 B/op 0 allocs/op +// Benchmark_IsIPv6/default-16 21168326 57.92 ns/op 0 B/op 0 allocs/op +// Benchmark_IsIPv6/default-16 20407336 57.98 ns/op 0 B/op 0 allocs/op