func matchHTTP2Field(r io.Reader, name, value string) (matched bool) { if !hasHTTP2Preface(r) { return false } framer := http2.NewFramer(ioutil.Discard, r) hdec := hpack.NewDecoder(uint32(4<<10), func(hf hpack.HeaderField) { if hf.Name == name && hf.Value == value { matched = true } }) for { f, err := framer.ReadFrame() if err != nil { return false } switch f := f.(type) { case *http2.HeadersFrame: if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil { return false } if matched { return true } if f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0 { return false } } } }
func newFramer(conn net.Conn) *framer { f := &framer{ reader: bufio.NewReaderSize(conn, http2IOBufSize), writer: bufio.NewWriterSize(conn, http2IOBufSize), } f.fr = http2.NewFramer(f.writer, f.reader) f.fr.ReadMetaHeaders = hpack.NewDecoder(http2InitHeaderTableSize, nil) return f }
func newHPACKDecoder() *hpackDecoder { d := &hpackDecoder{} d.h = hpack.NewDecoder(http2InitHeaderTableSize, func(f hpack.HeaderField) { switch f.Name { case "content-type": // TODO(zhaoq): Tentatively disable the check until a bug is fixed. /* if !strings.Contains(f.Value, "application/grpc") { d.err = StreamErrorf(codes.FailedPrecondition, "transport: received the unexpected header") return } */ case "grpc-status": code, err := strconv.Atoi(f.Value) if err != nil { d.err = StreamErrorf(codes.Internal, "transport: malformed grpc-status: %v", err) return } d.state.statusCode = codes.Code(code) case "grpc-message": d.state.statusDesc = f.Value case "grpc-timeout": d.state.timeoutSet = true var err error d.state.timeout, err = timeoutDecode(f.Value) if err != nil { d.err = StreamErrorf(codes.Internal, "transport: malformed time-out: %v", err) return } case ":path": d.state.method = f.Value default: if !isReservedHeader(f.Name) { if f.Name == "user-agent" { i := strings.LastIndex(f.Value, " ") if i == -1 { // There is no application user agent string being set. return } // Extract the application user agent string. f.Value = f.Value[:i] } if d.state.mdata == nil { d.state.mdata = make(map[string][]string) } k, v, err := metadata.DecodeKeyValue(f.Name, f.Value) if err != nil { grpclog.Printf("Failed to decode (%q, %q): %v", f.Name, f.Value, err) return } d.state.mdata[k] = append(d.state.mdata[k], v) } } }) return d }
func newHPACKDecoder() *hpackDecoder { hpackDec := &hpackDecoder{} hpackDec.reset() hpackDec.decoder = hpack.NewDecoder( http2InitHeaderTableSize, func(field hpack.HeaderField) { key := strings.ToLower(field.Name) hpackDec.headers[key] = append( hpackDec.headers[key], field.Value) }) return hpackDec }
func newServerTesterFromConn(t testing.TB, cc io.ReadWriteCloser) *serverTester { st := &serverTester{ t: t, cc: cc, frc: make(chan http2.Frame, 1), frErrc: make(chan error, 1), } st.hpackEnc = hpack.NewEncoder(&st.headerBuf) st.fr = http2.NewFramer(cc, cc) st.fr.ReadMetaHeaders = hpack.NewDecoder(4096 /*initialHeaderTableSize*/, nil) return st }
func newFramer(conn net.Conn) *framer { // 1. 首先: reader/writer 是对Conn做了一层buffer f := &framer{ reader: bufio.NewReaderSize(conn, http2IOBufSize), writer: bufio.NewWriterSize(conn, http2IOBufSize), } // 2. fr: 在buffer的基础上在做了http2的处理 f.fr = http2.NewFramer(f.writer, f.reader) // 3. f.fr.ReadMetaHeaders = hpack.NewDecoder(http2InitHeaderTableSize, nil) return f }
// readLoop runs in its own goroutine and reads and dispatches frames. func (cc *ClientConn) readLoop() { rl := &clientConnReadLoop{ cc: cc, activeRes: make(map[uint32]*clientStream), } rl.hdec = hpack.NewDecoder(initialHeaderTableSize, rl.onNewHeaderField) defer rl.cleanup() cc.readerErr = rl.run() if ce, ok := cc.readerErr.(ConnectionError); ok { cc.wmu.Lock() cc.fr.WriteGoAway(0, ErrCode(ce), nil) cc.wmu.Unlock() } }
func matchHTTP2Field(w io.Writer, r io.Reader, name, value string) (matched bool) { if !hasHTTP2Preface(r) { return false } done := false framer := http2.NewFramer(w, r) hdec := hpack.NewDecoder(uint32(4<<10), func(hf hpack.HeaderField) { if hf.Name == name { done = true if hf.Value == value { matched = true } } }) for { f, err := framer.ReadFrame() if err != nil { return false } switch f := f.(type) { case *http2.SettingsFrame: if err := framer.WriteSettings(); err != nil { return false } case *http2.ContinuationFrame: if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil { return false } done = done || f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0 case *http2.HeadersFrame: if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil { return false } done = done || f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0 } if done { return matched } } }
func (app *h2i) readFrames() error { for { f, err := app.framer.ReadFrame() if err != nil { return fmt.Errorf("ReadFrame: %v", err) } app.logf("%v", f) switch f := f.(type) { case *http2.PingFrame: app.logf(" Data = %q", f.Data) case *http2.SettingsFrame: f.ForeachSetting(func(s http2.Setting) error { app.logf(" %v", s) app.peerSetting[s.ID] = s.Val return nil }) case *http2.WindowUpdateFrame: app.logf(" Window-Increment = %v\n", f.Increment) case *http2.GoAwayFrame: app.logf(" Last-Stream-ID = %d; Error-Code = %v (%d)\n", f.LastStreamID, f.ErrCode, f.ErrCode) case *http2.DataFrame: app.logf(" %q", f.Data()) case *http2.HeadersFrame: if f.HasPriority() { app.logf(" PRIORITY = %v", f.Priority) } if app.hdec == nil { // TODO: if the user uses h2i to send a SETTINGS frame advertising // something larger, we'll need to respect SETTINGS_HEADER_TABLE_SIZE // and stuff here instead of using the 4k default. But for now: tableSize := uint32(4 << 10) app.hdec = hpack.NewDecoder(tableSize, app.onNewHeaderField) } app.hdec.Write(f.HeaderBlockFragment()) } } }
// newServerTesterInternal creates test context. If frontendTLS is // true, set up TLS frontend connection. connectPort is the server // side port where client connection is made. func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handler, frontendTLS bool, connectPort int, clientConfig *tls.Config) *serverTester { ts := httptest.NewUnstartedServer(handler) args := []string{} backendTLS := false dns := false externalDNS := false acceptProxyProtocol := false for _, k := range src_args { switch k { case "--http2-bridge": backendTLS = true case "--dns": dns = true case "--external-dns": dns = true externalDNS = true case "--accept-proxy-protocol": acceptProxyProtocol = true default: args = append(args, k) } } if backendTLS { nghttp2.ConfigureServer(ts.Config, &nghttp2.Server{}) // According to httptest/server.go, we have to set // NextProtos separately for ts.TLS. NextProtos set // in nghttp2.ConfigureServer is effectively ignored. ts.TLS = new(tls.Config) ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2") ts.StartTLS() args = append(args, "-k") } else { ts.Start() } scheme := "http" if frontendTLS { scheme = "https" args = append(args, testDir+"/server.key", testDir+"/server.crt") } backendURL, err := url.Parse(ts.URL) if err != nil { t.Fatalf("Error parsing URL from httptest.Server: %v", err) } // URL.Host looks like "127.0.0.1:8080", but we want // "127.0.0.1,8080" b := "-b" if !externalDNS { b += fmt.Sprintf("%v;", strings.Replace(backendURL.Host, ":", ",", -1)) } else { sep := strings.LastIndex(backendURL.Host, ":") if sep == -1 { t.Fatalf("backendURL.Host %v does not contain separator ':'", backendURL.Host) } // We use awesome service xip.io. b += fmt.Sprintf("%v.xip.io,%v;", backendURL.Host[:sep], backendURL.Host[sep+1:]) } if backendTLS { b += ";proto=h2;tls" } if dns { b += ";dns" } noTLS := ";no-tls" if frontendTLS { noTLS = "" } var proxyProto string if acceptProxyProtocol { proxyProto = ";proxyproto" } args = append(args, fmt.Sprintf("-f127.0.0.1,%v%v%v", serverPort, noTLS, proxyProto), b, "--errorlog-file="+logDir+"/log.txt", "-LINFO") authority := fmt.Sprintf("127.0.0.1:%v", connectPort) st := &serverTester{ cmd: exec.Command(serverBin, args...), t: t, ts: ts, url: fmt.Sprintf("%v://%v", scheme, authority), frontendHost: fmt.Sprintf("127.0.0.1:%v", serverPort), backendHost: backendURL.Host, nextStreamID: 1, authority: authority, frCh: make(chan http2.Frame), spdyFrCh: make(chan spdy.Frame), errCh: make(chan error), } if err := st.cmd.Start(); err != nil { st.t.Fatalf("Error starting %v: %v", serverBin, err) } retry := 0 for { time.Sleep(50 * time.Millisecond) var conn net.Conn var err error if frontendTLS { var tlsConfig *tls.Config if clientConfig == nil { tlsConfig = new(tls.Config) } else { tlsConfig = clientConfig } tlsConfig.InsecureSkipVerify = true tlsConfig.NextProtos = []string{"h2", "spdy/3.1"} conn, err = tls.Dial("tcp", authority, tlsConfig) } else { conn, err = net.Dial("tcp", authority) } if err != nil { retry += 1 if retry >= 100 { st.Close() st.t.Fatalf("Error server is not responding too long; server command-line arguments may be invalid") } continue } if frontendTLS { tlsConn := conn.(*tls.Conn) cs := tlsConn.ConnectionState() if !cs.NegotiatedProtocolIsMutual { st.Close() st.t.Fatalf("Error negotiated next protocol is not mutual") } } st.conn = conn break } st.fr = http2.NewFramer(st.conn, st.conn) spdyFr, err := spdy.NewFramer(st.conn, st.conn) if err != nil { st.Close() st.t.Fatalf("Error spdy.NewFramer: %v", err) } st.spdyFr = spdyFr st.enc = hpack.NewEncoder(&st.headerBlkBuf) st.dec = hpack.NewDecoder(4096, func(f hpack.HeaderField) { st.header.Add(f.Name, f.Value) }) return st }
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) { if VerboseLogs { t.vlogf("http2: Transport creating client conn to %v", c.RemoteAddr()) } if _, err := c.Write(clientPreface); err != nil { t.vlogf("client preface write error: %v", err) return nil, err } cc := &ClientConn{ t: t, tconn: c, readerDone: make(chan struct{}), nextStreamID: 1, maxFrameSize: 16 << 10, // spec default initialWindowSize: 65535, // spec default maxConcurrentStreams: 1000, // "infinite", per spec. 1000 seems good enough. streams: make(map[uint32]*clientStream), } cc.cond = sync.NewCond(&cc.mu) cc.flow.add(int32(initialWindowSize)) // TODO: adjust this writer size to account for frame size + // MTU + crypto/tls record padding. cc.bw = bufio.NewWriter(stickyErrWriter{c, &cc.werr}) cc.br = bufio.NewReader(c) cc.fr = NewFramer(cc.bw, cc.br) cc.fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil) cc.fr.MaxHeaderListSize = t.maxHeaderListSize() // TODO: SetMaxDynamicTableSize, SetMaxDynamicTableSizeLimit on // henc in response to SETTINGS frames? cc.henc = hpack.NewEncoder(&cc.hbuf) if cs, ok := c.(connectionStater); ok { state := cs.ConnectionState() cc.tlsState = &state } initialSettings := []Setting{ {ID: SettingEnablePush, Val: 0}, {ID: SettingInitialWindowSize, Val: transportDefaultStreamFlow}, } if max := t.maxHeaderListSize(); max != 0 { initialSettings = append(initialSettings, Setting{ID: SettingMaxHeaderListSize, Val: max}) } cc.fr.WriteSettings(initialSettings...) cc.fr.WriteWindowUpdate(0, transportDefaultConnFlow) cc.inflow.add(transportDefaultConnFlow + initialWindowSize) cc.bw.Flush() if cc.werr != nil { return nil, cc.werr } // Read the obligatory SETTINGS frame f, err := cc.fr.ReadFrame() if err != nil { return nil, err } sf, ok := f.(*SettingsFrame) if !ok { return nil, fmt.Errorf("expected settings frame, got: %T", f) } cc.fr.WriteSettingsAck() cc.bw.Flush() sf.ForeachSetting(func(s Setting) error { switch s.ID { case SettingMaxFrameSize: cc.maxFrameSize = s.Val case SettingMaxConcurrentStreams: cc.maxConcurrentStreams = s.Val case SettingInitialWindowSize: cc.initialWindowSize = s.Val default: // TODO(bradfitz): handle more; at least SETTINGS_HEADER_TABLE_SIZE? t.vlogf("Unhandled Setting: %v", s) } return nil }) go cc.readLoop() return cc, nil }
func (t *Transport) newClientConn(host, port, key string) (*clientConn, error) { cfg := &tls.Config{ ServerName: host, NextProtos: []string{NextProtoTLS}, InsecureSkipVerify: t.InsecureTLSDial, } tconn, err := tls.Dial("tcp", net.JoinHostPort(host, port), cfg) if err != nil { return nil, err } if err := tconn.Handshake(); err != nil { return nil, err } if !t.InsecureTLSDial { if err := tconn.VerifyHostname(cfg.ServerName); err != nil { return nil, err } } state := tconn.ConnectionState() if p := state.NegotiatedProtocol; p != NextProtoTLS { // TODO(bradfitz): fall back to Fallback return nil, fmt.Errorf("bad protocol: %v", p) } if !state.NegotiatedProtocolIsMutual { return nil, errors.New("could not negotiate protocol mutually") } if _, err := tconn.Write(clientPreface); err != nil { return nil, err } cc := &clientConn{ t: t, tconn: tconn, connKey: []string{key}, // TODO: cert's validated hostnames too tlsState: &state, readerDone: make(chan struct{}), nextStreamID: 1, maxFrameSize: 16 << 10, // spec default initialWindowSize: 65535, // spec default maxConcurrentStreams: 1000, // "infinite", per spec. 1000 seems good enough. streams: make(map[uint32]*clientStream), } cc.bw = bufio.NewWriter(stickyErrWriter{tconn, &cc.werr}) cc.br = bufio.NewReader(tconn) cc.fr = NewFramer(cc.bw, cc.br) cc.henc = hpack.NewEncoder(&cc.hbuf) cc.fr.WriteSettings() // TODO: re-send more conn-level flow control tokens when server uses all these. cc.fr.WriteWindowUpdate(0, 1<<30) // um, 0x7fffffff doesn't work to Google? it hangs? cc.bw.Flush() if cc.werr != nil { return nil, cc.werr } // Read the obligatory SETTINGS frame f, err := cc.fr.ReadFrame() if err != nil { return nil, err } sf, ok := f.(*SettingsFrame) if !ok { return nil, fmt.Errorf("expected settings frame, got: %T", f) } cc.fr.WriteSettingsAck() cc.bw.Flush() sf.ForeachSetting(func(s Setting) error { switch s.ID { case SettingMaxFrameSize: cc.maxFrameSize = s.Val case SettingMaxConcurrentStreams: cc.maxConcurrentStreams = s.Val case SettingInitialWindowSize: cc.initialWindowSize = s.Val default: // TODO(bradfitz): handle more t.vlogf("Unhandled Setting: %v", s) } return nil }) // TODO: figure out henc size cc.hdec = hpack.NewDecoder(initialHeaderTableSize, cc.onNewHeaderField) go cc.readLoop() return cc, nil }
// newServerTesterInternal creates test context. If frontendTLS is // true, set up TLS frontend connection. func newServerTesterInternal(args []string, t *testing.T, handler http.Handler, frontendTLS bool, clientConfig *tls.Config) *serverTester { ts := httptest.NewUnstartedServer(handler) backendTLS := false for _, k := range args { switch k { case "--http2-bridge": backendTLS = true } } if backendTLS { nghttp2.ConfigureServer(ts.Config, &nghttp2.Server{}) // According to httptest/server.go, we have to set // NextProtos separately for ts.TLS. NextProtos set // in nghttp2.ConfigureServer is effectively ignored. ts.TLS = new(tls.Config) ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2-14") ts.StartTLS() args = append(args, "-k") } else { ts.Start() } scheme := "http" if frontendTLS { scheme = "https" args = append(args, testDir+"/server.key", testDir+"/server.crt") } else { args = append(args, "--frontend-no-tls") } backendURL, err := url.Parse(ts.URL) if err != nil { t.Fatalf("Error parsing URL from httptest.Server: %v", err) } // URL.Host looks like "127.0.0.1:8080", but we want // "127.0.0.1,8080" b := "-b" + strings.Replace(backendURL.Host, ":", ",", -1) args = append(args, fmt.Sprintf("-f127.0.0.1,%v", serverPort), b, "--errorlog-file="+testDir+"/log.txt", "-LINFO") authority := fmt.Sprintf("127.0.0.1:%v", serverPort) st := &serverTester{ cmd: exec.Command(serverBin, args...), t: t, ts: ts, url: fmt.Sprintf("%v://%v", scheme, authority), frontendHost: fmt.Sprintf("127.0.0.1:%v", serverPort), backendHost: backendURL.Host, nextStreamID: 1, authority: authority, frCh: make(chan http2.Frame), spdyFrCh: make(chan spdy.Frame), errCh: make(chan error), } if err := st.cmd.Start(); err != nil { st.t.Fatalf("Error starting %v: %v", serverBin, err) } retry := 0 for { var conn net.Conn var err error if frontendTLS { var tlsConfig *tls.Config if clientConfig == nil { tlsConfig = new(tls.Config) } else { tlsConfig = clientConfig } tlsConfig.InsecureSkipVerify = true tlsConfig.NextProtos = []string{"h2-14", "spdy/3.1"} conn, err = tls.Dial("tcp", authority, tlsConfig) } else { conn, err = net.Dial("tcp", authority) } if err != nil { retry += 1 if retry >= 100 { st.Close() st.t.Fatalf("Error server is not responding too long; server command-line arguments may be invalid") } time.Sleep(150 * time.Millisecond) continue } if frontendTLS { tlsConn := conn.(*tls.Conn) cs := tlsConn.ConnectionState() if !cs.NegotiatedProtocolIsMutual { st.Close() st.t.Fatalf("Error negotiated next protocol is not mutual") } } st.conn = conn break } st.fr = http2.NewFramer(st.conn, st.conn) spdyFr, err := spdy.NewFramer(st.conn, st.conn) if err != nil { st.Close() st.t.Fatalf("Error spdy.NewFramer: %v", err) } st.spdyFr = spdyFr st.enc = hpack.NewEncoder(&st.headerBlkBuf) st.dec = hpack.NewDecoder(4096, func(f hpack.HeaderField) { st.header.Add(f.Name, f.Value) }) return st }
func TestMetaFrameHeader(t *testing.T) { write := func(f *Framer, frags ...[]byte) { for i, frag := range frags { end := (i == len(frags)-1) if i == 0 { f.WriteHeaders(HeadersFrameParam{ StreamID: 1, BlockFragment: frag, EndHeaders: end, }) } else { f.WriteContinuation(1, end, frag) } } } want := func(flags Flags, length uint32, pairs ...string) *MetaHeadersFrame { mh := &MetaHeadersFrame{ HeadersFrame: &HeadersFrame{ FrameHeader: FrameHeader{ Type: FrameHeaders, Flags: flags, Length: length, StreamID: 1, }, }, Fields: []hpack.HeaderField(nil), } for len(pairs) > 0 { mh.Fields = append(mh.Fields, hpack.HeaderField{ Name: pairs[0], Value: pairs[1], }) pairs = pairs[2:] } return mh } truncated := func(mh *MetaHeadersFrame) *MetaHeadersFrame { mh.Truncated = true return mh } const noFlags Flags = 0 oneKBString := strings.Repeat("a", 1<<10) tests := [...]struct { name string w func(*Framer) want interface{} // *MetaHeaderFrame or error wantErrReason string maxHeaderListSize uint32 }{ 0: { name: "single_headers", w: func(f *Framer) { var he hpackEncoder all := he.encodeHeaderRaw(t, ":method", "GET", ":path", "/") write(f, all) }, want: want(FlagHeadersEndHeaders, 2, ":method", "GET", ":path", "/"), }, 1: { name: "with_continuation", w: func(f *Framer) { var he hpackEncoder all := he.encodeHeaderRaw(t, ":method", "GET", ":path", "/", "foo", "bar") write(f, all[:1], all[1:]) }, want: want(noFlags, 1, ":method", "GET", ":path", "/", "foo", "bar"), }, 2: { name: "with_two_continuation", w: func(f *Framer) { var he hpackEncoder all := he.encodeHeaderRaw(t, ":method", "GET", ":path", "/", "foo", "bar") write(f, all[:2], all[2:4], all[4:]) }, want: want(noFlags, 2, ":method", "GET", ":path", "/", "foo", "bar"), }, 3: { name: "big_string_okay", w: func(f *Framer) { var he hpackEncoder all := he.encodeHeaderRaw(t, ":method", "GET", ":path", "/", "foo", oneKBString) write(f, all[:2], all[2:]) }, want: want(noFlags, 2, ":method", "GET", ":path", "/", "foo", oneKBString), }, 4: { name: "big_string_error", w: func(f *Framer) { var he hpackEncoder all := he.encodeHeaderRaw(t, ":method", "GET", ":path", "/", "foo", oneKBString) write(f, all[:2], all[2:]) }, maxHeaderListSize: (1 << 10) / 2, want: ConnectionError(ErrCodeCompression), }, 5: { name: "max_header_list_truncated", w: func(f *Framer) { var he hpackEncoder var pairs = []string{":method", "GET", ":path", "/"} for i := 0; i < 100; i++ { pairs = append(pairs, "foo", "bar") } all := he.encodeHeaderRaw(t, pairs...) write(f, all[:2], all[2:]) }, maxHeaderListSize: (1 << 10) / 2, want: truncated(want(noFlags, 2, ":method", "GET", ":path", "/", "foo", "bar", "foo", "bar", "foo", "bar", "foo", "bar", "foo", "bar", "foo", "bar", "foo", "bar", "foo", "bar", "foo", "bar", "foo", "bar", "foo", "bar", // 11 )), }, 6: { name: "pseudo_order", w: func(f *Framer) { write(f, encodeHeaderRaw(t, ":method", "GET", "foo", "bar", ":path", "/", // bogus )) }, want: StreamError{1, ErrCodeProtocol}, wantErrReason: "pseudo header field after regular", }, 7: { name: "pseudo_unknown", w: func(f *Framer) { write(f, encodeHeaderRaw(t, ":unknown", "foo", // bogus "foo", "bar", )) }, want: StreamError{1, ErrCodeProtocol}, wantErrReason: "invalid pseudo-header \":unknown\"", }, 8: { name: "pseudo_mix_request_response", w: func(f *Framer) { write(f, encodeHeaderRaw(t, ":method", "GET", ":status", "100", )) }, want: StreamError{1, ErrCodeProtocol}, wantErrReason: "mix of request and response pseudo headers", }, 9: { name: "pseudo_dup", w: func(f *Framer) { write(f, encodeHeaderRaw(t, ":method", "GET", ":method", "POST", )) }, want: StreamError{1, ErrCodeProtocol}, wantErrReason: "duplicate pseudo-header \":method\"", }, 10: { name: "trailer_okay_no_pseudo", w: func(f *Framer) { write(f, encodeHeaderRaw(t, "foo", "bar")) }, want: want(FlagHeadersEndHeaders, 8, "foo", "bar"), }, 11: { name: "invalid_field_name", w: func(f *Framer) { write(f, encodeHeaderRaw(t, "CapitalBad", "x")) }, want: StreamError{1, ErrCodeProtocol}, wantErrReason: "invalid header field name \"CapitalBad\"", }, 12: { name: "invalid_field_value", w: func(f *Framer) { write(f, encodeHeaderRaw(t, "key", "bad_null\x00")) }, want: StreamError{1, ErrCodeProtocol}, wantErrReason: "invalid header field value \"bad_null\\x00\"", }, } for i, tt := range tests { buf := new(bytes.Buffer) f := NewFramer(buf, buf) f.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil) f.MaxHeaderListSize = tt.maxHeaderListSize tt.w(f) name := tt.name if name == "" { name = fmt.Sprintf("test index %d", i) } var got interface{} var err error got, err = f.ReadFrame() if err != nil { got = err } if !reflect.DeepEqual(got, tt.want) { if mhg, ok := got.(*MetaHeadersFrame); ok { if mhw, ok := tt.want.(*MetaHeadersFrame); ok { hg := mhg.HeadersFrame hw := mhw.HeadersFrame if hg != nil && hw != nil && !reflect.DeepEqual(*hg, *hw) { t.Errorf("%s: headers differ:\n got: %+v\nwant: %+v\n", name, *hg, *hw) } } } str := func(v interface{}) string { if _, ok := v.(error); ok { return fmt.Sprintf("error %v", v) } else { return fmt.Sprintf("value %#v", v) } } t.Errorf("%s:\n got: %v\nwant: %s", name, str(got), str(tt.want)) } if tt.wantErrReason != "" && tt.wantErrReason != fmt.Sprint(f.errDetail) { t.Errorf("%s: got error reason %q; want %q", name, f.errDetail, tt.wantErrReason) } } }