http2: use an array instead of a map in typeFrameParser

FrameType is a dense integer range, so we can store the frameParsers
in an array instead of a map. This should be a very small performance
win on all Go http2 servers. For high QPS gRPC services, this function
is visible in the Go profiler. For example, it shows up as 0.16% of
all CPU time on one production service at Datadog.

Change FrameType.String() to use the same pattern.

Add a test for testFrameType with unknown FrameTypes.

Fixes golang/go#73613

Change-Id: I5f5b523e011a99d6b428cbdbfd97415e488169d1
Reviewed-on: https://go-review.googlesource.com/c/net/+/670415
Reviewed-by: Sean Liao <sean@liao.dev>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/http2/frame.go b/http2/frame.go
index 97bd8b0..e6f728c 100644
--- a/http2/frame.go
+++ b/http2/frame.go
@@ -39,7 +39,7 @@
 	FrameContinuation FrameType = 0x9
 )
 
-var frameName = map[FrameType]string{
+var frameNames = [...]string{
 	FrameData:         "DATA",
 	FrameHeaders:      "HEADERS",
 	FramePriority:     "PRIORITY",
@@ -53,10 +53,10 @@
 }
 
 func (t FrameType) String() string {
-	if s, ok := frameName[t]; ok {
-		return s
+	if int(t) < len(frameNames) {
+		return frameNames[t]
 	}
-	return fmt.Sprintf("UNKNOWN_FRAME_TYPE_%d", uint8(t))
+	return fmt.Sprintf("UNKNOWN_FRAME_TYPE_%d", t)
 }
 
 // Flags is a bitmask of HTTP/2 flags.
@@ -124,7 +124,7 @@
 // might be 0).
 type frameParser func(fc *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error)
 
-var frameParsers = map[FrameType]frameParser{
+var frameParsers = [...]frameParser{
 	FrameData:         parseDataFrame,
 	FrameHeaders:      parseHeadersFrame,
 	FramePriority:     parsePriorityFrame,
@@ -138,8 +138,8 @@
 }
 
 func typeFrameParser(t FrameType) frameParser {
-	if f := frameParsers[t]; f != nil {
-		return f
+	if int(t) < len(frameParsers) {
+		return frameParsers[t]
 	}
 	return parseUnknownFrame
 }
diff --git a/http2/frame_test.go b/http2/frame_test.go
index 86e5d4f..6850531 100644
--- a/http2/frame_test.go
+++ b/http2/frame_test.go
@@ -1258,3 +1258,21 @@
 	}
 
 }
+
+func TestTypeFrameParser(t *testing.T) {
+	if len(frameNames) != len(frameParsers) {
+		t.Errorf("expected len(frameNames)=%d to equal len(frameParsers)=%d",
+			len(frameNames), len(frameParsers))
+	}
+
+	// typeFrameParser() for an unknown type returns a function that returns UnknownFrame
+	unknownFrameType := FrameType(FrameContinuation + 1)
+	unknownParser := typeFrameParser(unknownFrameType)
+	frame, err := unknownParser(nil, FrameHeader{}, nil, nil)
+	if err != nil {
+		t.Errorf("unknownParser() must not return an error: %v", err)
+	}
+	if _, isUnknown := frame.(*UnknownFrame); !isUnknown {
+		t.Errorf("expected UnknownFrame, got %T", frame)
+	}
+}