Browse Source

Merge branch 'master' of github.com:bradfitz/http2

Brad Fitzpatrick 11 years ago
parent
commit
2440b99f4f
13 changed files with 825 additions and 258 deletions
  1. 1 0
      AUTHORS
  2. 1 0
      CONTRIBUTORS
  3. 1 1
      errors_test.go
  4. 21 6
      frame.go
  5. 10 2
      frame_test.go
  6. 2 3
      hpack/encode_test.go
  7. 5 5
      hpack/hpack_test.go
  8. 0 45
      http2_test.go
  9. 121 0
      priority_test.go
  10. 300 110
      server.go
  11. 350 73
      server_test.go
  12. 2 9
      write.go
  13. 11 4
      z_spec_test.go

+ 1 - 0
AUTHORS

@@ -15,4 +15,5 @@ Gabriel Aszalos <gabriel.aszalos@gmail.com> github=gbbr
 Google, Inc.
 Keith Rarick <kr@xph.us> github=kr
 Matthew Keenan <tank.en.mate@gmail.com> <github@mattkeenan.net> github=mattkeenan
+Matt Layher <mdlayher@gmail.com> github=mdlayher
 Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> github=tatsuhiro-t

+ 1 - 0
CONTRIBUTORS

@@ -15,4 +15,5 @@ Daniel Morsing <daniel.morsing@gmail.com> github=DanielMorsing
 Gabriel Aszalos <gabriel.aszalos@gmail.com> github=gbbr
 Keith Rarick <kr@xph.us> github=kr
 Matthew Keenan <tank.en.mate@gmail.com> <github@mattkeenan.net> github=mattkeenan
+Matt Layher <mdlayher@gmail.com> github=mdlayher
 Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> github=tatsuhiro-t

+ 1 - 1
errors_test.go

@@ -21,7 +21,7 @@ func TestErrCodeString(t *testing.T) {
 	for i, tt := range tests {
 		got := tt.err.String()
 		if got != tt.want {
-			t.Errorf("%i. Error = %q; want %q", i, got, tt.want)
+			t.Errorf("%d. Error = %q; want %q", i, got, tt.want)
 		}
 	}
 }

+ 21 - 6
frame.go

@@ -146,9 +146,22 @@ func typeFrameParser(t FrameType) frameParser {
 type FrameHeader struct {
 	valid bool // caller can access []byte fields in the Frame
 
-	Type     FrameType
-	Flags    Flags
-	Length   uint32 // actually a uint24 max; default is uint16 max
+	// Type is the 1 byte frame type. There are ten standard frame
+	// types, but extension frame types may be written by WriteRawFrame
+	// and will be returned by ReadFrame (as UnknownFrame).
+	Type FrameType
+
+	// Flags are the 1 byte of 8 potential bit flags per frame.
+	// They are specific to the frame type.
+	Flags Flags
+
+	// Length is the length of the frame, not including the 9 byte header.
+	// The maximum size is one byte less than 16MB (uint24), but only
+	// frames up to 16KB are allowed without peer agreement.
+	Length uint32
+
+	// StreamID is which stream this frame is for. Certain frames
+	// are not stream-specific, in which case this field is 0.
 	StreamID uint32
 }
 
@@ -633,9 +646,11 @@ type UnknownFrame struct {
 	p []byte
 }
 
-// Payload returns the frame's payload (after the header).
-// It is not valid to call this method after a subsequent
-// call to Framer.ReadFrame.
+// Payload returns the frame's payload (after the header).  It is not
+// valid to call this method after a subsequent call to
+// Framer.ReadFrame, nor is it valid to retain the returned slice.
+// The memory is owned by the Framer and is invalidated when the next
+// frame is read.
 func (f *UnknownFrame) Payload() []byte {
 	f.checkValid()
 	return f.p

+ 10 - 2
frame_test.go

@@ -8,6 +8,7 @@ import (
 	"bytes"
 	"reflect"
 	"testing"
+	"unsafe"
 )
 
 func testFramer() (*Framer, *bytes.Buffer) {
@@ -15,6 +16,13 @@ func testFramer() (*Framer, *bytes.Buffer) {
 	return NewFramer(buf, buf), buf
 }
 
+func TestFrameSizes(t *testing.T) {
+	// Catch people rearranging the FrameHeader fields.
+	if got, want := int(unsafe.Sizeof(FrameHeader{})), 12; got != want {
+		t.Errorf("FrameHeader size = %d; want %d", got, want)
+	}
+}
+
 func TestWriteRST(t *testing.T) {
 	fr, buf := testFramer()
 	var streamID uint32 = 1<<24 + 2<<16 + 3<<8 + 4
@@ -61,7 +69,7 @@ func TestWriteData(t *testing.T) {
 		t.Fatalf("got %T; want *DataFrame", f)
 	}
 	if !bytes.Equal(df.Data(), data) {
-		t.Errorf("got %q; want %q", df.Data, data)
+		t.Errorf("got %q; want %q", df.Data(), data)
 	}
 	if f.Header().Flags&1 == 0 {
 		t.Errorf("didn't see END_STREAM flag")
@@ -336,7 +344,7 @@ func TestWriteSettings(t *testing.T) {
 		got = append(got, s)
 		valBack, ok := sf.Value(s.ID)
 		if !ok || valBack != s.Val {
-			t.Errorf("Value(%d) = %v, %v; want %v, true", s.ID, valBack, ok)
+			t.Errorf("Value(%d) = %v, %v; want %v, true", s.ID, valBack, ok, s.Val)
 		}
 		return nil
 	})

+ 2 - 3
hpack/encode_test.go

@@ -81,7 +81,7 @@ func TestEncoderWriteField(t *testing.T) {
 		}
 		_, err := d.Write(buf.Bytes())
 		if err != nil {
-			t.Error("%d. Decoder Write = %v", i, err)
+			t.Errorf("%d. Decoder Write = %v", i, err)
 		}
 		if !reflect.DeepEqual(got, tt.hdrs) {
 			t.Errorf("%d. Decoded %+v; want %+v", i, got, tt.hdrs)
@@ -322,11 +322,10 @@ func TestEncoderSetMaxDynamicTableSizeLimit(t *testing.T) {
 		t.Errorf("e.dynTab.maxSize = %v; want %v", got, want)
 	}
 	if got, want := e.maxSizeLimit, uint32(8192); got != want {
-		t.Errorf("e.maxSizeLimit = %v; want %v", got, want);
+		t.Errorf("e.maxSizeLimit = %v; want %v", got, want)
 	}
 }
 
-
 func removeSpace(s string) string {
 	return strings.Replace(s, " ", "", -1)
 }

+ 5 - 5
hpack/hpack_test.go

@@ -127,18 +127,18 @@ func TestDynamicTableAt(t *testing.T) {
 	d := NewDecoder(4096, nil)
 	at := d.mustAt
 	if got, want := at(2), (pair(":method", "GET")); got != want {
-		t.Errorf("at(2) = %q; want %q", got, want)
+		t.Errorf("at(2) = %v; want %v", got, want)
 	}
 	d.dynTab.add(pair("foo", "bar"))
 	d.dynTab.add(pair("blake", "miz"))
 	if got, want := at(len(staticTable)+1), (pair("blake", "miz")); got != want {
-		t.Errorf("at(dyn 1) = %q; want %q", got, want)
+		t.Errorf("at(dyn 1) = %v; want %v", got, want)
 	}
 	if got, want := at(len(staticTable)+2), (pair("foo", "bar")); got != want {
-		t.Errorf("at(dyn 2) = %q; want %q", got, want)
+		t.Errorf("at(dyn 2) = %v; want %v", got, want)
 	}
 	if got, want := at(3), (pair(":method", "POST")); got != want {
-		t.Errorf("at(3) = %q; want %q", got, want)
+		t.Errorf("at(3) = %v; want %v", got, want)
 	}
 }
 
@@ -196,7 +196,7 @@ func TestDynamicTableSizeEvict(t *testing.T) {
 		t.Fatalf("after setMaxSize, size = %d; want %d", d.dynTab.size, want)
 	}
 	if got, want := d.mustAt(len(staticTable)+1), (pair("foo", "bar")); got != want {
-		t.Errorf("at(dyn 1) = %q; want %q", got, want)
+		t.Errorf("at(dyn 1) = %v; want %v", got, want)
 	}
 	add(pair("long", strings.Repeat("x", 500)))
 	if want := uint32(0); d.dynTab.size != want {

+ 0 - 45
http2_test.go

@@ -67,51 +67,6 @@ func (w twriter) Write(p []byte) (n int, err error) {
 	return len(p), nil
 }
 
-// encodeHeader encodes headers and returns their HPACK bytes. headers
-// must contain an even number of key/value pairs.  There may be
-// multiple pairs for keys (e.g. "cookie").  The :method, :path, and
-// :scheme headers default to GET, / and https.
-func encodeHeader(t *testing.T, headers ...string) []byte {
-	pseudoCount := map[string]int{}
-	if len(headers)%2 == 1 {
-		panic("odd number of kv args")
-	}
-	keys := []string{":method", ":path", ":scheme"}
-	vals := map[string][]string{
-		":method": {"GET"},
-		":path":   {"/"},
-		":scheme": {"https"},
-	}
-	for len(headers) > 0 {
-		k, v := headers[0], headers[1]
-		headers = headers[2:]
-		if _, ok := vals[k]; !ok {
-			keys = append(keys, k)
-		}
-		if strings.HasPrefix(k, ":") {
-			pseudoCount[k]++
-			if pseudoCount[k] == 1 {
-				vals[k] = []string{v}
-			} else {
-				// Allows testing of invalid headers w/ dup pseudo fields.
-				vals[k] = append(vals[k], v)
-			}
-		} else {
-			vals[k] = append(vals[k], v)
-		}
-	}
-	var buf bytes.Buffer
-	enc := hpack.NewEncoder(&buf)
-	for _, k := range keys {
-		for _, v := range vals[k] {
-			if err := enc.WriteField(hpack.HeaderField{Name: k, Value: v}); err != nil {
-				t.Fatalf("HPACK encoding error for %q/%q: %v", k, v, err)
-			}
-		}
-	}
-	return buf.Bytes()
-}
-
 // like encodeHeader, but don't add implicit psuedo headers.
 func encodeHeaderNoImplicit(t *testing.T, headers ...string) []byte {
 	var buf bytes.Buffer

+ 121 - 0
priority_test.go

@@ -0,0 +1,121 @@
+// Copyright 2014 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.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+	"testing"
+)
+
+func TestPriority(t *testing.T) {
+	// A -> B
+	// move A's parent to B
+	streams := make(map[uint32]*stream)
+	a := &stream{
+		parent: nil,
+		weight: 16,
+	}
+	streams[1] = a
+	b := &stream{
+		parent: a,
+		weight: 16,
+	}
+	streams[2] = b
+	adjustStreamPriority(streams, 1, PriorityParam{
+		Weight:    20,
+		StreamDep: 2,
+	})
+	if a.parent != b {
+		t.Errorf("Expected A's parent to be B")
+	}
+	if a.weight != 20 {
+		t.Errorf("Expected A's weight to be 20; got %d", a.weight)
+	}
+	if b.parent != nil {
+		t.Errorf("Expected B to have no parent")
+	}
+	if b.weight != 16 {
+		t.Errorf("Expected B's weight to be 16; got %d", b.weight)
+	}
+}
+
+func TestPriorityExclusiveZero(t *testing.T) {
+	// A B and C are all children of the 0 stream.
+	// Exclusive reprioritization to any of the streams
+	// should bring the rest of the streams under the
+	// reprioritized stream
+	streams := make(map[uint32]*stream)
+	a := &stream{
+		parent: nil,
+		weight: 16,
+	}
+	streams[1] = a
+	b := &stream{
+		parent: nil,
+		weight: 16,
+	}
+	streams[2] = b
+	c := &stream{
+		parent: nil,
+		weight: 16,
+	}
+	streams[3] = c
+	adjustStreamPriority(streams, 3, PriorityParam{
+		Weight:    20,
+		StreamDep: 0,
+		Exclusive: true,
+	})
+	if a.parent != c {
+		t.Errorf("Expected A's parent to be C")
+	}
+	if a.weight != 16 {
+		t.Errorf("Expected A's weight to be 16; got %d", a.weight)
+	}
+	if b.parent != c {
+		t.Errorf("Expected B's parent to be C")
+	}
+	if b.weight != 16 {
+		t.Errorf("Expected B's weight to be 16; got %d", b.weight)
+	}
+	if c.parent != nil {
+		t.Errorf("Expected C to have no parent")
+	}
+	if c.weight != 20 {
+		t.Errorf("Expected C's weight to be 20; got %d", b.weight)
+	}
+}
+
+func TestPriorityOwnParent(t *testing.T) {
+	streams := make(map[uint32]*stream)
+	a := &stream{
+		parent: nil,
+		weight: 16,
+	}
+	streams[1] = a
+	b := &stream{
+		parent: a,
+		weight: 16,
+	}
+	streams[2] = b
+	adjustStreamPriority(streams, 1, PriorityParam{
+		Weight:    20,
+		StreamDep: 1,
+	})
+	if a.parent != nil {
+		t.Errorf("Expected A's parent to be nil")
+	}
+	if a.weight != 20 {
+		t.Errorf("Expected A's weight to be 20; got %d", a.weight)
+	}
+	if b.parent != a {
+		t.Errorf("Expected B's parent to be A")
+	}
+	if b.weight != 16 {
+		t.Errorf("Expected B's weight to be 16; got %d", b.weight)
+	}
+
+}

+ 300 - 110
server.go

@@ -9,6 +9,34 @@
 // instead, and make sure that on close we close all open
 // streams. then remove doneServing?
 
+// TODO: finish GOAWAY support. Consider each incoming frame type and
+// whether it should be ignored during a shutdown race.
+
+// TODO: disconnect idle clients. GFE seems to do 4 minutes. make
+// configurable?  or maximum number of idle clients and remove the
+// oldest?
+
+// TODO: turn off the serve goroutine when idle, so
+// an idle conn only has the readFrames goroutine active. (which could
+// also be optimized probably to pin less memory in crypto/tls). This
+// would involve tracking when the serve goroutine is active (atomic
+// int32 read/CAS probably?) and starting it up when frames arrive,
+// and shutting it down when all handlers exit. the occasional PING
+// packets could use time.AfterFunc to call sc.wakeStartServeLoop()
+// (which is a no-op if already running) and then queue the PING write
+// as normal. The serve loop would then exit in most cases (if no
+// Handlers running) and not be woken up again until the PING packet
+// returns.
+
+// TODO (maybe): add a mechanism for Handlers to going into
+// half-closed-local mode (rw.(io.Closer) test?) but not exit their
+// handler, and continue to be able to read from the
+// Request.Body. This would be a somewhat semantic change from HTTP/1
+// (or at least what we expose in net/http), so I'd probably want to
+// add it there too. For now, this package says that returning from
+// the Handler ServeHTTP function means you're both done reading and
+// done writing, without a way to stop just one or the other.
+
 package http2
 
 import (
@@ -34,7 +62,7 @@ const (
 	prefaceTimeout        = 10 * time.Second
 	firstSettingsTimeout  = 2 * time.Second // should be in-flight with preface anyway
 	handlerChunkWriteSize = 4 << 10
-	defaultMaxStreams     = 250
+	defaultMaxStreams     = 250 // TODO: make this 100 as the GFE seems to?
 )
 
 var (
@@ -55,34 +83,10 @@ var responseWriterStatePool = sync.Pool{
 var (
 	testHookOnConn        func()
 	testHookGetServerConn func(*serverConn)
+	testHookOnPanicMu     *sync.Mutex // nil except in tests
+	testHookOnPanic       func(sc *serverConn, panicVal interface{}) (rePanic bool)
 )
 
-// TODO: finish GOAWAY support. Consider each incoming frame type and
-// whether it should be ignored during a shutdown race.
-
-// TODO: (edge case?) if peer sends a SETTINGS frame with e.g. a
-// SETTINGS_MAX_FRAME_SIZE that's lower than what we had before,
-// before we ACK it we have to make sure all currently-active streams
-// know about that and don't have existing too-large frames in flight?
-// Perhaps the settings processing should just wait for new frame to
-// be in-flight and then the frame scheduler in the serve goroutine
-// will be responsible for splitting things.
-
-// TODO: send PING frames to idle clients and disconnect them if no
-// reply
-
-// TODO: for bonus points: turn off the serve goroutine when idle, so
-// an idle conn only has the readFrames goroutine active. (which could
-// also be optimized probably to pin less memory in crypto/tls). This
-// would involve tracking when the serve goroutine is active (atomic
-// int32 read/CAS probably?) and starting it up when frames arrive,
-// and shutting it down when all handlers exit. the occasional PING
-// packets could use time.AfterFunc to call sc.wakeStartServeLoop()
-// (which is a no-op if already running) and then queue the PING write
-// as normal. The serve loop would then exit in most cases (if no
-// Handlers running) and not be woken up again until the PING packet
-// returns.
-
 // Server is an HTTP/2 server.
 type Server struct {
 	// MaxHandlers limits the number of http.Handler ServeHTTP goroutines
@@ -132,6 +136,31 @@ func ConfigureServer(s *http.Server, conf *Server) {
 	if s.TLSConfig == nil {
 		s.TLSConfig = new(tls.Config)
 	}
+
+	// Note: not setting MinVersion to tls.VersionTLS12,
+	// as we don't want to interfere with HTTP/1.1 traffic
+	// on the user's server. We enforce TLS 1.2 later once
+	// we accept a connection. Ideally this should be done
+	// during next-proto selection, but using TLS <1.2 with
+	// HTTP/2 is still the client's bug.
+
+	// Be sure we advertise tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+	// at least.
+	// TODO: enable PreferServerCipherSuites?
+	if s.TLSConfig.CipherSuites != nil {
+		const requiredCipher = tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+		haveRequired := false
+		for _, v := range s.TLSConfig.CipherSuites {
+			if v == requiredCipher {
+				haveRequired = true
+				break
+			}
+		}
+		if !haveRequired {
+			s.TLSConfig.CipherSuites = append(s.TLSConfig.CipherSuites, requiredCipher)
+		}
+	}
+
 	haveNPN := false
 	for _, p := range s.TLSConfig.NextProtos {
 		if p == NextProtoTLS {
@@ -159,6 +188,7 @@ func (srv *Server) handleConn(hs *http.Server, c net.Conn, h http.Handler) {
 		srv:              srv,
 		hs:               hs,
 		conn:             c,
+		remoteAddrStr:    c.RemoteAddr().String(),
 		bw:               newBufferedWriter(c),
 		handler:          h,
 		streams:          make(map[uint32]*stream),
@@ -166,6 +196,7 @@ func (srv *Server) handleConn(hs *http.Server, c net.Conn, h http.Handler) {
 		readFrameErrCh:   make(chan error, 1), // must be buffered for 1
 		wantWriteFrameCh: make(chan frameWriteMsg, 8),
 		wroteFrameCh:     make(chan struct{}, 1), // buffered; one send in reading goroutine
+		bodyReadCh:       make(chan bodyReadMsg), // buffering doesn't matter either way
 		doneServing:      make(chan struct{}),
 		advMaxStreams:    srv.maxConcurrentStreams(),
 		writeSched: writeScheduler{
@@ -177,6 +208,7 @@ func (srv *Server) handleConn(hs *http.Server, c net.Conn, h http.Handler) {
 		pushEnabled:       true,
 	}
 	sc.flow.add(initialWindowSize)
+	sc.inflow.add(initialWindowSize)
 	sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf)
 	sc.hpackDecoder = hpack.NewDecoder(initialHeaderTableSize, sc.onNewHeaderField)
 
@@ -184,12 +216,83 @@ func (srv *Server) handleConn(hs *http.Server, c net.Conn, h http.Handler) {
 	fr.SetMaxReadFrameSize(srv.maxReadFrameSize())
 	sc.framer = fr
 
+	if tc, ok := c.(*tls.Conn); ok {
+		sc.tlsState = new(tls.ConnectionState)
+		*sc.tlsState = tc.ConnectionState()
+		// 9.2 Use of TLS Features
+		// An implementation of HTTP/2 over TLS MUST use TLS
+		// 1.2 or higher with the restrictions on feature set
+		// and cipher suite described in this section. Due to
+		// implementation limitations, it might not be
+		// possible to fail TLS negotiation. An endpoint MUST
+		// immediately terminate an HTTP/2 connection that
+		// does not meet the TLS requirements described in
+		// this section with a connection error (Section
+		// 5.4.1) of type INADEQUATE_SECURITY.
+		if sc.tlsState.Version < tls.VersionTLS12 {
+			sc.rejectConn(ErrCodeInadequateSecurity, "TLS version too low")
+			return
+		}
+
+		// Client must use SNI:
+		if sc.tlsState.ServerName == "" {
+			sc.rejectConn(ErrCodeProtocol, "client didn't use SNI")
+			return
+		}
+
+		if isBadCipher(sc.tlsState.CipherSuite) {
+			// "Endpoints MAY choose to generate a connection error
+			// (Section 5.4.1) of type INADEQUATE_SECURITY if one of
+			// the prohibited cipher suites are negotiated."
+			//
+			// We choose that. In my opinion, the spec is weak
+			// here. It also says both parties must support at least
+			// TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 so there's no
+			// excuses here. If we really must, we could allow an
+			// "AllowInsecureWeakCiphers" option on the server later.
+			// Let's see how it plays out first.
+			sc.rejectConn(ErrCodeInadequateSecurity, "Prohibited TLS 1.2 Cipher Suite")
+			return
+		}
+	}
+
 	if hook := testHookGetServerConn; hook != nil {
 		hook(sc)
 	}
 	sc.serve()
 }
 
+// isBadCipher reports whether the cipher is blacklisted by the HTTP/2 spec.
+func isBadCipher(cipher uint16) bool {
+	switch cipher {
+	case tls.TLS_RSA_WITH_RC4_128_SHA,
+		tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+		tls.TLS_RSA_WITH_AES_128_CBC_SHA,
+		tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+		tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+		tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+		tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+		tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+		tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+		tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+		tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+		// Reject cipher suites from Appendix A.
+		// "This list includes those cipher suites that do not
+		// offer an ephemeral key exchange and those that are
+		// based on the TLS null, stream or block cipher type"
+		return true
+	default:
+		return false
+	}
+}
+
+func (sc *serverConn) rejectConn(err ErrCode, debug string) {
+	// ignoring errors. hanging up anyway.
+	sc.framer.WriteGoAway(0, err, []byte(debug))
+	sc.bw.Flush()
+	sc.conn.Close()
+}
+
 // frameAndGates coordinates the readFrames and serve
 // goroutines. Because the Framer interface only permits the most
 // recently-read Frame from being accessed, the readFrames goroutine
@@ -212,10 +315,14 @@ type serverConn struct {
 	doneServing      chan struct{}     // closed when serverConn.serve ends
 	readFrameCh      chan frameAndGate // written by serverConn.readFrames
 	readFrameErrCh   chan error
-	wantWriteFrameCh chan frameWriteMsg // from handlers -> serve
-	wroteFrameCh     chan struct{}      // from writeFrameAsync -> serve, tickles more frame writes
-	testHookCh       chan func()        // code to run on the serve loop
-	flow             flow               // connection-wide (not stream-specific) flow control
+	wantWriteFrameCh chan frameWriteMsg   // from handlers -> serve
+	wroteFrameCh     chan struct{}        // from writeFrameAsync -> serve, tickles more frame writes
+	bodyReadCh       chan bodyReadMsg     // from handlers -> serve
+	testHookCh       chan func()          // code to run on the serve loop
+	flow             flow                 // conn-wide (not stream-specific) outbound flow control
+	inflow           flow                 // conn-wide inbound flow control
+	tlsState         *tls.ConnectionState // shared by all handlers, like net/http
+	remoteAddrStr    string
 
 	// Everything following is owned by the serve loop; use serveG.check():
 	serveG                goroutineLock // used to verify funcs are on serve()
@@ -271,18 +378,19 @@ type requestParam struct {
 type stream struct {
 	// immutable:
 	id   uint32
-	flow flow        // limits writing from Handler to client
 	body *pipe       // non-nil if expecting DATA frames
 	cw   closeWaiter // closed wait stream transitions to closed state
 
 	// owned by serverConn's serve loop:
+	bodyBytes     int64   // body bytes seen so far
+	declBodyBytes int64   // or -1 if undeclared
+	flow          flow    // limits writing from Handler to client
+	inflow        flow    // what the client is allowed to POST/etc to us
 	parent        *stream // or nil
 	weight        uint8
 	state         streamState
-	bodyBytes     int64 // body bytes seen so far
-	declBodyBytes int64 // or -1 if undeclared
-	sentReset     bool  // only true once detached from streams map
-	gotReset      bool  // only true once detacted from streams map
+	sentReset     bool // only true once detached from streams map
+	gotReset      bool // only true once detacted from streams map
 }
 
 func (sc *serverConn) Framer() *Framer  { return sc.framer }
@@ -292,11 +400,11 @@ func (sc *serverConn) HeaderEncoder() (*hpack.Encoder, *bytes.Buffer) {
 	return sc.hpackEncoder, &sc.headerWriteBuf
 }
 
-func (sc *serverConn) state(streamID uint32) streamState {
+func (sc *serverConn) state(streamID uint32) (streamState, *stream) {
 	sc.serveG.check()
 	// http://http2.github.io/http2-spec/#rfc.section.5.1
 	if st, ok := sc.streams[streamID]; ok {
-		return st.state
+		return st.state, st
 	}
 	// "The first use of a new stream identifier implicitly closes all
 	// streams in the "idle" state that might have been initiated by
@@ -305,9 +413,9 @@ func (sc *serverConn) state(streamID uint32) streamState {
 	// frame on stream 5, then stream 5 transitions to the "closed"
 	// state when the first frame for stream 7 is sent or received."
 	if streamID <= sc.maxStreamID {
-		return stateClosed
+		return stateClosed, nil
 	}
-	return stateIdle
+	return stateIdle, nil
 }
 
 func (sc *serverConn) vlogf(format string, args ...interface{}) {
@@ -456,8 +564,23 @@ func (sc *serverConn) stopShutdownTimer() {
 	}
 }
 
+func (sc *serverConn) notePanic() {
+	if testHookOnPanicMu != nil {
+		testHookOnPanicMu.Lock()
+		defer testHookOnPanicMu.Unlock()
+	}
+	if testHookOnPanic != nil {
+		if e := recover(); e != nil {
+			if testHookOnPanic(sc, e) {
+				panic(e)
+			}
+		}
+	}
+}
+
 func (sc *serverConn) serve() {
 	sc.serveG.check()
+	defer sc.notePanic()
 	defer sc.conn.Close()
 	defer sc.closeAllStreamsOnConnClose()
 	defer sc.stopShutdownTimer()
@@ -504,6 +627,8 @@ func (sc *serverConn) serve() {
 				settingsTimer.Stop()
 				settingsTimer.C = nil
 			}
+		case m := <-sc.bodyReadCh:
+			sc.noteBodyRead(m.st, m.n)
 		case <-settingsTimer.C:
 			sc.logf("timeout waiting for SETTINGS frames from %v", sc.conn.RemoteAddr())
 			return
@@ -531,7 +656,7 @@ func (sc *serverConn) readPreface() error {
 			errc <- nil
 		}
 	}()
-	timer := time.NewTimer(5 * time.Second) // TODO: configurable on *Server?
+	timer := time.NewTimer(prefaceTimeout) // TODO: configurable on *Server?
 	defer timer.Stop()
 	select {
 	case <-timer.C:
@@ -634,7 +759,17 @@ func (sc *serverConn) startFrameWrite(wm frameWriteMsg) {
 		}
 		switch st.state {
 		case stateOpen:
-			st.state = stateHalfClosedLocal
+			// Here we would go to stateHalfClosedLocal in
+			// theory, but since our handler is done and
+			// the net/http package provides no mechanism
+			// for finishing writing to a ResponseWriter
+			// while still reading data (see possible TODO
+			// at top of this file), we go into closed
+			// state here anyway, after telling the peer
+			// we're hanging up on them.
+			st.state = stateHalfClosedLocal // won't last long, but necessary for closeStream via resetStream
+			errCancel := StreamError{st.id, ErrCodeCancel}
+			sc.resetStream(errCancel)
 		case stateHalfClosedRemote:
 			sc.closeStream(st, nil)
 		}
@@ -712,13 +847,11 @@ func (sc *serverConn) shutDownIn(d time.Duration) {
 
 func (sc *serverConn) resetStream(se StreamError) {
 	sc.serveG.check()
-	st, ok := sc.streams[se.StreamID]
-	if !ok {
-		panic("internal package error; resetStream called on non-existent stream")
-	}
 	sc.writeFrame(frameWriteMsg{write: se})
-	st.sentReset = true
-	sc.closeStream(st, se)
+	if st, ok := sc.streams[se.StreamID]; ok {
+		st.sentReset = true
+		sc.closeStream(st, se)
+	}
 }
 
 // curHeaderStreamID returns the stream ID of the header block we're
@@ -753,6 +886,9 @@ func (sc *serverConn) processFrameFromReader(fg frameAndGate, fgValid bool) bool
 			// (e.g. CloseWrite) because they're done
 			// sending frames but they're still wanting
 			// our open replies?  Investigate.
+			// TODO: add CloseWrite to crypto/tls.Conn first
+			// so we have a way to test this? I suppose
+			// just for testing we could have a non-TLS mode.
 			return false
 		}
 	}
@@ -880,7 +1016,9 @@ func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error {
 
 func (sc *serverConn) processResetStream(f *RSTStreamFrame) error {
 	sc.serveG.check()
-	if sc.state(f.StreamID) == stateIdle {
+
+	state, st := sc.state(f.StreamID)
+	if state == stateIdle {
 		// 6.4 "RST_STREAM frames MUST NOT be sent for a
 		// stream in the "idle" state. If a RST_STREAM frame
 		// identifying an idle stream is received, the
@@ -888,8 +1026,7 @@ func (sc *serverConn) processResetStream(f *RSTStreamFrame) error {
 		// (Section 5.4.1) of type PROTOCOL_ERROR.
 		return ConnectionError(ErrCodeProtocol)
 	}
-	st, ok := sc.streams[f.StreamID]
-	if ok {
+	if st != nil {
 		st.gotReset = true
 		sc.closeStream(st, StreamError{f.StreamID, f.ErrCode})
 	}
@@ -899,7 +1036,7 @@ func (sc *serverConn) processResetStream(f *RSTStreamFrame) error {
 func (sc *serverConn) closeStream(st *stream, err error) {
 	sc.serveG.check()
 	if st.state == stateIdle || st.state == stateClosed {
-		panic("invariant")
+		panic(fmt.Sprintf("invariant; can't close stream in state %v", st.state))
 	}
 	st.state = stateClosed
 	sc.curOpenStreams--
@@ -994,7 +1131,12 @@ func (sc *serverConn) processData(f *DataFrame) error {
 	// with a stream error (Section 5.4.2) of type STREAM_CLOSED."
 	id := f.Header().StreamID
 	st, ok := sc.streams[id]
-	if !ok || (st.state != stateOpen && st.state != stateHalfClosedLocal) {
+	if !ok || st.state != stateOpen {
+		// This includes sending a RST_STREAM if the stream is
+		// in stateHalfClosedLocal (which currently means that
+		// the http.Handler returned, so it's done reading &
+		// done writing). Try to stop the client from sending
+		// more DATA.
 		return StreamError{id, ErrCodeStreamClosed}
 	}
 	if st.body == nil {
@@ -1008,8 +1150,11 @@ func (sc *serverConn) processData(f *DataFrame) error {
 		return StreamError{id, ErrCodeStreamClosed}
 	}
 	if len(data) > 0 {
-		// TODO: verify they're allowed to write with the flow control
-		// window we'd advertised to them.
+		// Check whether the client has flow control quota.
+		if int(st.inflow.available()) < len(data) {
+			return StreamError{id, ErrCodeFlowControl}
+		}
+		st.inflow.take(int32(len(data)))
 		wrote, err := st.body.Write(data)
 		if err != nil {
 			return StreamError{id, ErrCodeStreamClosed}
@@ -1026,12 +1171,7 @@ func (sc *serverConn) processData(f *DataFrame) error {
 		} else {
 			st.body.Close(io.EOF)
 		}
-		switch st.state {
-		case stateOpen:
-			st.state = stateHalfClosedRemote
-		case stateHalfClosedLocal:
-			st.state = stateClosed
-		}
+		st.state = stateHalfClosedRemote
 	}
 	return nil
 }
@@ -1061,16 +1201,19 @@ func (sc *serverConn) processHeaders(f *HeadersFrame) error {
 		id:    id,
 		state: stateOpen,
 	}
-	// connection-level flow control is shared by all streams.
-	st.flow.conn = &sc.flow
-	st.flow.add(sc.initialWindowSize)
-	st.cw.Init() // make Cond use its Mutex, without heap-promoting them separately
 	if f.StreamEnded() {
 		st.state = stateHalfClosedRemote
 	}
+	st.cw.Init()
+
+	st.flow.conn = &sc.flow // link to conn-level counter
+	st.flow.add(sc.initialWindowSize)
+	st.inflow.conn = &sc.inflow      // link to conn-level counter
+	st.inflow.add(initialWindowSize) // TODO: update this when we send a higher initial window size in the initial settings
+
 	sc.streams[id] = st
 	if f.HasPriority() {
-		sc.adjustStreamPriority(st.id, f.Priority)
+		adjustStreamPriority(sc.streams, st.id, f.Priority)
 	}
 	sc.curOpenStreams++
 	sc.req = requestParam{
@@ -1133,13 +1276,12 @@ func (sc *serverConn) processHeaderBlockFragment(st *stream, frag []byte, end bo
 }
 
 func (sc *serverConn) processPriority(f *PriorityFrame) error {
-	sc.adjustStreamPriority(f.StreamID, f.PriorityParam)
+	adjustStreamPriority(sc.streams, f.StreamID, f.PriorityParam)
 	return nil
 }
 
-func (sc *serverConn) adjustStreamPriority(streamID uint32, priority PriorityParam) {
-	// TODO: untested
-	st, ok := sc.streams[streamID]
+func adjustStreamPriority(streams map[uint32]*stream, streamID uint32, priority PriorityParam) {
+	st, ok := streams[streamID]
 	if !ok {
 		// TODO: not quite correct (this streamID might
 		// already exist in the dep tree, but be closed), but
@@ -1147,10 +1289,27 @@ func (sc *serverConn) adjustStreamPriority(streamID uint32, priority PriorityPar
 		return
 	}
 	st.weight = priority.Weight
-	st.parent = sc.streams[priority.StreamDep] // might be nil
-	if priority.Exclusive && st.parent != nil {
-		for _, openStream := range sc.streams {
-			if openStream.parent == st.parent {
+	parent := streams[priority.StreamDep] // might be nil
+	if parent == st {
+		// if client tries to set this stream to be the parent of itself
+		// ignore and keep going
+		return
+	}
+
+	// section 5.3.3: If a stream is made dependent on one of its
+	// own dependencies, the formerly dependent stream is first
+	// moved to be dependent on the reprioritized stream's previous
+	// parent. The moved dependency retains its weight.
+	for piter := parent; piter != nil; piter = piter.parent {
+		if piter == st {
+			parent.parent = st.parent
+			break
+		}
+	}
+	st.parent = parent
+	if priority.Exclusive && (st.parent != nil || priority.StreamDep == 0) {
+		for _, openStream := range streams {
+			if openStream != st && openStream.parent == st.parent {
 				openStream.parent = st
 			}
 		}
@@ -1182,26 +1341,9 @@ func (sc *serverConn) newWriterAndRequest() (*responseWriter, *http.Request, err
 		// pseudo-header fields"
 		return nil, nil, StreamError{rp.stream.id, ErrCodeProtocol}
 	}
-	var tlsState *tls.ConnectionState // make this non-nil if https
+	var tlsState *tls.ConnectionState // nil if not scheme https
 	if rp.scheme == "https" {
-		tlsState = &tls.ConnectionState{}
-		if tc, ok := sc.conn.(*tls.Conn); ok {
-			*tlsState = tc.ConnectionState()
-			if tlsState.Version < tls.VersionTLS12 {
-				// 9.2 Use of TLS Features
-				// An implementation of HTTP/2 over TLS MUST use TLS
-				// 1.2 or higher with the restrictions on feature set
-				// and cipher suite described in this section. Due to
-				// implementation limitations, it might not be
-				// possible to fail TLS negotiation. An endpoint MUST
-				// immediately terminate an HTTP/2 connection that
-				// does not meet the TLS requirements described in
-				// this section with a connection error (Section
-				// 5.4.1) of type INADEQUATE_SECURITY.
-				return nil, nil, ConnectionError(ErrCodeInadequateSecurity)
-			}
-			// TODO: verify cipher suites. (9.2.1, 9.2.2)
-		}
+		tlsState = sc.tlsState
 	}
 	authority := rp.authority
 	if authority == "" {
@@ -1226,7 +1368,7 @@ func (sc *serverConn) newWriterAndRequest() (*responseWriter, *http.Request, err
 	req := &http.Request{
 		Method:     rp.method,
 		URL:        url,
-		RemoteAddr: sc.conn.RemoteAddr().String(),
+		RemoteAddr: sc.remoteAddrStr,
 		Header:     rp.header,
 		RequestURI: rp.path,
 		Proto:      "HTTP/2.0",
@@ -1307,24 +1449,72 @@ func (sc *serverConn) write100ContinueHeaders(st *stream) {
 	})
 }
 
-// called from handler goroutines
+// A bodyReadMsg tells the server loop that the http.Handler read n
+// bytes of the DATA from the client on the given stream.
+type bodyReadMsg struct {
+	st *stream
+	n  int
+}
+
+// called from handler goroutines.
+// Notes that the handler for the given stream ID read n bytes of its body
+// and schedules flow control tokens to be sent.
+func (sc *serverConn) noteBodyReadFromHandler(st *stream, n int) {
+	sc.serveG.checkNotOn() // NOT on
+	sc.bodyReadCh <- bodyReadMsg{st, n}
+}
+
+func (sc *serverConn) noteBodyRead(st *stream, n int) {
+	sc.serveG.check()
+	sc.sendWindowUpdate(nil, n) // conn-level
+	if st.state != stateHalfClosedRemote && st.state != stateClosed {
+		// Don't send this WINDOW_UPDATE if the stream is closed
+		// remotely.
+		sc.sendWindowUpdate(st, n)
+	}
+}
+
+// st may be nil for conn-level
 func (sc *serverConn) sendWindowUpdate(st *stream, n int) {
-	if st == nil {
-		panic("no stream")
+	sc.serveG.check()
+	// "The legal range for the increment to the flow control
+	// window is 1 to 2^31-1 (2,147,483,647) octets."
+	// A Go Read call on 64-bit machines could in theory read
+	// a larger Read than this. Very unlikely, but we handle it here
+	// rather than elsewhere for now.
+	const maxUint31 = 1<<31 - 1
+	for n >= maxUint31 {
+		sc.sendWindowUpdate32(st, maxUint31)
+		n -= maxUint31
 	}
-	const maxUint32 = 2147483647
-	for n >= maxUint32 {
-		sc.writeFrameFromHandler(frameWriteMsg{
-			write:  writeWindowUpdate{streamID: st.id, n: maxUint32},
-			stream: st,
-		})
-		n -= maxUint32
+	sc.sendWindowUpdate32(st, int32(n))
+}
+
+// st may be nil for conn-level
+func (sc *serverConn) sendWindowUpdate32(st *stream, n int32) {
+	sc.serveG.check()
+	if n == 0 {
+		return
 	}
-	if n > 0 {
-		sc.writeFrameFromHandler(frameWriteMsg{
-			write:  writeWindowUpdate{streamID: st.id, n: uint32(n)},
-			stream: st,
-		})
+	if n < 0 {
+		panic("negative update")
+	}
+	var streamID uint32
+	if st != nil {
+		streamID = st.id
+	}
+	sc.writeFrame(frameWriteMsg{
+		write:  writeWindowUpdate{streamID: streamID, n: uint32(n)},
+		stream: st,
+	})
+	var ok bool
+	if st == nil {
+		ok = sc.inflow.add(n)
+	} else {
+		ok = st.inflow.add(n)
+	}
+	if !ok {
+		panic("internal error; sent too many window updates without decrements?")
 	}
 }
 
@@ -1354,7 +1544,7 @@ func (b *requestBody) Read(p []byte) (n int, err error) {
 	}
 	n, err = b.pipe.Read(p)
 	if n > 0 {
-		b.conn.sendWindowUpdate(b.stream, n)
+		b.conn.noteBodyReadFromHandler(b.stream, n)
 	}
 	return
 }

+ 350 - 73
server_test.go

@@ -33,24 +33,65 @@ import (
 
 type serverTester struct {
 	cc        net.Conn // client conn
-	t         *testing.T
+	t         testing.TB
 	ts        *httptest.Server
 	fr        *Framer
 	logBuf    *bytes.Buffer
 	sc        *serverConn
 	logFilter []string // substrings to filter out
+
+	// writing headers:
+	headerBuf bytes.Buffer
+	hpackEnc  *hpack.Encoder
+
+	// reading frames:
+	frc       chan Frame
+	frErrc    chan error
+	readTimer *time.Timer
 }
 
-func newServerTester(t *testing.T, handler http.HandlerFunc) *serverTester {
+func init() {
+	testHookOnPanicMu = new(sync.Mutex)
+}
+
+func resetHooks() {
+	testHookOnPanicMu.Lock()
+	testHookOnPanic = nil
+	testHookOnPanicMu.Unlock()
+}
+
+func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}) *serverTester {
+	resetHooks()
+
 	logBuf := new(bytes.Buffer)
 	ts := httptest.NewUnstartedServer(handler)
+
+	tlsConfig := &tls.Config{
+		InsecureSkipVerify: true,
+		NextProtos:         []string{NextProtoTLS},
+	}
+
+	for _, opt := range opts {
+		switch v := opt.(type) {
+		case func(*tls.Config):
+			v(tlsConfig)
+		case func(*httptest.Server):
+			v(ts)
+		default:
+			t.Fatalf("unknown newServerTester option type %T", v)
+		}
+	}
+
 	ConfigureServer(ts.Config, &Server{})
 
 	st := &serverTester{
 		t:      t,
 		ts:     ts,
 		logBuf: logBuf,
+		frc:    make(chan Frame, 1),
+		frErrc: make(chan error, 1),
 	}
+	st.hpackEnc = hpack.NewEncoder(&st.headerBuf)
 
 	ts.TLS = ts.Config.TLSConfig // the httptest.Server has its own copy of this TLS config
 	ts.Config.ErrorLog = log.New(io.MultiWriter(twriter{t: t, st: st}, logBuf), "", log.LstdFlags)
@@ -69,10 +110,7 @@ func newServerTester(t *testing.T, handler http.HandlerFunc) *serverTester {
 		sc = v
 		sc.testHookCh = make(chan func())
 	}
-	cc, err := tls.Dial("tcp", ts.Listener.Addr().String(), &tls.Config{
-		InsecureSkipVerify: true,
-		NextProtos:         []string{NextProtoTLS},
-	})
+	cc, err := tls.Dial("tcp", ts.Listener.Addr().String(), tlsConfig)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -102,7 +140,8 @@ func (st *serverTester) stream(id uint32) *stream {
 func (st *serverTester) streamState(id uint32) streamState {
 	ch := make(chan streamState, 1)
 	st.sc.testHookCh <- func() {
-		ch <- st.sc.state(id)
+		state, _ := st.sc.state(id)
+		ch <- state
 	}
 	return <-ch
 }
@@ -151,11 +190,80 @@ func (st *serverTester) writeHeaders(p HeadersFrameParam) {
 	}
 }
 
+func (st *serverTester) encodeHeaderField(k, v string) {
+	err := st.hpackEnc.WriteField(hpack.HeaderField{Name: k, Value: v})
+	if err != nil {
+		st.t.Fatalf("HPACK encoding error for %q/%q: %v", k, v, err)
+	}
+}
+
+// encodeHeader encodes headers and returns their HPACK bytes. headers
+// must contain an even number of key/value pairs.  There may be
+// multiple pairs for keys (e.g. "cookie").  The :method, :path, and
+// :scheme headers default to GET, / and https.
+func (st *serverTester) encodeHeader(headers ...string) []byte {
+	if len(headers)%2 == 1 {
+		panic("odd number of kv args")
+	}
+
+	st.headerBuf.Reset()
+
+	if len(headers) == 0 {
+		// Fast path, mostly for benchmarks, so test code doesn't pollute
+		// profiles when we're looking to improve server allocations.
+		st.encodeHeaderField(":method", "GET")
+		st.encodeHeaderField(":path", "/")
+		st.encodeHeaderField(":scheme", "https")
+		return st.headerBuf.Bytes()
+	}
+
+	if len(headers) == 2 && headers[0] == ":method" {
+		// Another fast path for benchmarks.
+		st.encodeHeaderField(":method", headers[1])
+		st.encodeHeaderField(":path", "/")
+		st.encodeHeaderField(":scheme", "https")
+		return st.headerBuf.Bytes()
+	}
+
+	pseudoCount := map[string]int{}
+	keys := []string{":method", ":path", ":scheme"}
+	vals := map[string][]string{
+		":method": {"GET"},
+		":path":   {"/"},
+		":scheme": {"https"},
+	}
+	for len(headers) > 0 {
+		k, v := headers[0], headers[1]
+		headers = headers[2:]
+		if _, ok := vals[k]; !ok {
+			keys = append(keys, k)
+		}
+		if strings.HasPrefix(k, ":") {
+			pseudoCount[k]++
+			if pseudoCount[k] == 1 {
+				vals[k] = []string{v}
+			} else {
+				// Allows testing of invalid headers w/ dup pseudo fields.
+				vals[k] = append(vals[k], v)
+			}
+		} else {
+			vals[k] = append(vals[k], v)
+		}
+	}
+	st.headerBuf.Reset()
+	for _, k := range keys {
+		for _, v := range vals[k] {
+			st.encodeHeaderField(k, v)
+		}
+	}
+	return st.headerBuf.Bytes()
+}
+
 // bodylessReq1 writes a HEADERS frames with StreamID 1 and EndStream and EndHeaders set.
 func (st *serverTester) bodylessReq1(headers ...string) {
 	st.writeHeaders(HeadersFrameParam{
 		StreamID:      1, // clients send odd numbers
-		BlockFragment: encodeHeader(st.t, headers...),
+		BlockFragment: st.encodeHeader(headers...),
 		EndStream:     true,
 		EndHeaders:    true,
 	})
@@ -168,22 +276,25 @@ func (st *serverTester) writeData(streamID uint32, endStream bool, data []byte)
 }
 
 func (st *serverTester) readFrame() (Frame, error) {
-	frc := make(chan Frame, 1)
-	errc := make(chan error, 1)
 	go func() {
 		fr, err := st.fr.ReadFrame()
 		if err != nil {
-			errc <- err
+			st.frErrc <- err
 		} else {
-			frc <- fr
+			st.frc <- fr
 		}
 	}()
-	t := time.NewTimer(2 * time.Second)
+	t := st.readTimer
+	if t == nil {
+		t = time.NewTimer(2 * time.Second)
+		st.readTimer = t
+	}
+	t.Reset(2 * time.Second)
 	defer t.Stop()
 	select {
-	case f := <-frc:
+	case f := <-st.frc:
 		return f, nil
-	case err := <-errc:
+	case err := <-st.frErrc:
 		return nil, err
 	case <-t.C:
 		return nil, errors.New("timeout waiting for frame")
@@ -282,7 +393,7 @@ func (st *serverTester) wantRSTStream(streamID uint32, errCode ErrCode) {
 func (st *serverTester) wantWindowUpdate(streamID, incr uint32) {
 	f, err := st.readFrame()
 	if err != nil {
-		st.t.Fatalf("Error while expecting an RSTStream frame: %v", err)
+		st.t.Fatalf("Error while expecting a WINDOW_UPDATE frame: %v", err)
 	}
 	wu, ok := f.(*WindowUpdateFrame)
 	if !ok {
@@ -333,7 +444,7 @@ func TestServer(t *testing.T) {
 
 	st.writeHeaders(HeadersFrameParam{
 		StreamID:      1, // clients send odd numbers
-		BlockFragment: encodeHeader(t),
+		BlockFragment: st.encodeHeader(),
 		EndStream:     true, // no DATA frames
 		EndHeaders:    true,
 	})
@@ -349,7 +460,7 @@ func TestServer_Request_Get(t *testing.T) {
 	testServerRequest(t, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1, // clients send odd numbers
-			BlockFragment: encodeHeader(t, "foo-bar", "some-value"),
+			BlockFragment: st.encodeHeader("foo-bar", "some-value"),
 			EndStream:     true, // no DATA frames
 			EndHeaders:    true,
 		})
@@ -388,13 +499,13 @@ func TestServer_Request_Get_PathSlashes(t *testing.T) {
 	testServerRequest(t, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1, // clients send odd numbers
-			BlockFragment: encodeHeader(t, ":path", "/%2f/"),
+			BlockFragment: st.encodeHeader(":path", "/%2f/"),
 			EndStream:     true, // no DATA frames
 			EndHeaders:    true,
 		})
 	}, func(r *http.Request) {
 		if r.RequestURI != "/%2f/" {
-			t.Errorf("RequestURI = %q; want /%2f/", r.RequestURI)
+			t.Errorf("RequestURI = %q; want /%%2f/", r.RequestURI)
 		}
 		if r.URL.Path != "///" {
 			t.Errorf("URL.Path = %q; want ///", r.URL.Path)
@@ -410,7 +521,7 @@ func TestServer_Request_Post_NoContentLength_EndStream(t *testing.T) {
 	testServerRequest(t, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1, // clients send odd numbers
-			BlockFragment: encodeHeader(t, ":method", "POST"),
+			BlockFragment: st.encodeHeader(":method", "POST"),
 			EndStream:     true,
 			EndHeaders:    true,
 		})
@@ -431,7 +542,7 @@ func TestServer_Request_Post_Body_ImmediateEOF(t *testing.T) {
 	testBodyContents(t, -1, "", func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1, // clients send odd numbers
-			BlockFragment: encodeHeader(t, ":method", "POST"),
+			BlockFragment: st.encodeHeader(":method", "POST"),
 			EndStream:     false, // to say DATA frames are coming
 			EndHeaders:    true,
 		})
@@ -444,7 +555,7 @@ func TestServer_Request_Post_Body_OneData(t *testing.T) {
 	testBodyContents(t, -1, content, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1, // clients send odd numbers
-			BlockFragment: encodeHeader(t, ":method", "POST"),
+			BlockFragment: st.encodeHeader(":method", "POST"),
 			EndStream:     false, // to say DATA frames are coming
 			EndHeaders:    true,
 		})
@@ -457,7 +568,7 @@ func TestServer_Request_Post_Body_TwoData(t *testing.T) {
 	testBodyContents(t, -1, content, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1, // clients send odd numbers
-			BlockFragment: encodeHeader(t, ":method", "POST"),
+			BlockFragment: st.encodeHeader(":method", "POST"),
 			EndStream:     false, // to say DATA frames are coming
 			EndHeaders:    true,
 		})
@@ -471,7 +582,7 @@ func TestServer_Request_Post_Body_ContentLength_Correct(t *testing.T) {
 	testBodyContents(t, int64(len(content)), content, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID: 1, // clients send odd numbers
-			BlockFragment: encodeHeader(t,
+			BlockFragment: st.encodeHeader(
 				":method", "POST",
 				"content-length", strconv.Itoa(len(content)),
 			),
@@ -487,7 +598,7 @@ func TestServer_Request_Post_Body_ContentLength_TooLarge(t *testing.T) {
 		func(st *serverTester) {
 			st.writeHeaders(HeadersFrameParam{
 				StreamID: 1, // clients send odd numbers
-				BlockFragment: encodeHeader(t,
+				BlockFragment: st.encodeHeader(
 					":method", "POST",
 					"content-length", "3",
 				),
@@ -503,7 +614,7 @@ func TestServer_Request_Post_Body_ContentLength_TooSmall(t *testing.T) {
 		func(st *serverTester) {
 			st.writeHeaders(HeadersFrameParam{
 				StreamID: 1, // clients send odd numbers
-				BlockFragment: encodeHeader(t,
+				BlockFragment: st.encodeHeader(
 					":method", "POST",
 					"content-length", "4",
 				),
@@ -563,7 +674,7 @@ func TestServer_Request_Get_Host(t *testing.T) {
 	testServerRequest(t, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1, // clients send odd numbers
-			BlockFragment: encodeHeader(t, "host", host),
+			BlockFragment: st.encodeHeader("host", host),
 			EndStream:     true,
 			EndHeaders:    true,
 		})
@@ -580,7 +691,7 @@ func TestServer_Request_Get_Authority(t *testing.T) {
 	testServerRequest(t, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1, // clients send odd numbers
-			BlockFragment: encodeHeader(t, ":authority", host),
+			BlockFragment: st.encodeHeader(":authority", host),
 			EndStream:     true,
 			EndHeaders:    true,
 		})
@@ -598,7 +709,7 @@ func TestServer_Request_WithContinuation(t *testing.T) {
 		"Foo-Three": []string{"value-three"},
 	}
 	testServerRequest(t, func(st *serverTester) {
-		fullHeaders := encodeHeader(t,
+		fullHeaders := st.encodeHeader(
 			"foo-one", "value-one",
 			"foo-two", "value-two",
 			"foo-three", "value-three",
@@ -781,37 +892,24 @@ func TestServer_Handler_Sends_WindowUpdate(t *testing.T) {
 
 	st.writeHeaders(HeadersFrameParam{
 		StreamID:      1, // clients send odd numbers
-		BlockFragment: encodeHeader(t, ":method", "POST"),
+		BlockFragment: st.encodeHeader(":method", "POST"),
 		EndStream:     false, // data coming
 		EndHeaders:    true,
 	})
-	st.writeData(1, true, []byte("abcdef"))
-	puppet.do(func(w http.ResponseWriter, r *http.Request) {
-		buf := make([]byte, 3)
-		_, err := io.ReadFull(r.Body, buf)
-		if err != nil {
-			t.Error(err)
-			return
-		}
-		if string(buf) != "abc" {
-			t.Errorf("read %q; want abc", buf)
-		}
-	})
+	st.writeData(1, false, []byte("abcdef"))
+	puppet.do(readBodyHandler(t, "abc"))
 	st.wantWindowUpdate(0, 3)
 	st.wantWindowUpdate(1, 3)
-	puppet.do(func(w http.ResponseWriter, r *http.Request) {
-		buf := make([]byte, 3)
-		_, err := io.ReadFull(r.Body, buf)
-		if err != nil {
-			t.Error(err)
-			return
-		}
-		if string(buf) != "def" {
-			t.Errorf("read %q; want abc", buf)
-		}
-	})
+
+	puppet.do(readBodyHandler(t, "def"))
 	st.wantWindowUpdate(0, 3)
 	st.wantWindowUpdate(1, 3)
+
+	st.writeData(1, true, []byte("ghijkl")) // END_STREAM here
+	puppet.do(readBodyHandler(t, "ghi"))
+	puppet.do(readBodyHandler(t, "jkl"))
+	st.wantWindowUpdate(0, 3)
+	st.wantWindowUpdate(0, 3) // no more stream-level, since END_STREAM
 }
 
 func TestServer_Send_GoAway_After_Bogus_WindowUpdate(t *testing.T) {
@@ -842,7 +940,7 @@ func TestServer_Send_RstStream_After_Bogus_WindowUpdate(t *testing.T) {
 	st.greet()
 	st.writeHeaders(HeadersFrameParam{
 		StreamID:      1,
-		BlockFragment: encodeHeader(st.t, ":method", "POST"),
+		BlockFragment: st.encodeHeader(":method", "POST"),
 		EndStream:     false, // keep it open
 		EndHeaders:    true,
 	})
@@ -871,7 +969,7 @@ func testServerPostUnblock(t *testing.T,
 	st.greet()
 	st.writeHeaders(HeadersFrameParam{
 		StreamID:      1,
-		BlockFragment: encodeHeader(st.t, append([]string{":method", "POST"}, otherHeaders...)...),
+		BlockFragment: st.encodeHeader(append([]string{":method", "POST"}, otherHeaders...)...),
 		EndStream:     false, // keep it open
 		EndHeaders:    true,
 	})
@@ -982,7 +1080,7 @@ func TestServer_StateTransitions(t *testing.T) {
 
 	st.writeHeaders(HeadersFrameParam{
 		StreamID:      1,
-		BlockFragment: encodeHeader(st.t, ":method", "POST"),
+		BlockFragment: st.encodeHeader(":method", "POST"),
 		EndStream:     false, // keep it open
 		EndHeaders:    true,
 	})
@@ -1009,13 +1107,13 @@ func TestServer_Rejects_HeadersNoEnd_Then_Headers(t *testing.T) {
 	testServerRejects(t, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1,
-			BlockFragment: encodeHeader(st.t),
+			BlockFragment: st.encodeHeader(),
 			EndStream:     true,
 			EndHeaders:    false,
 		})
 		st.writeHeaders(HeadersFrameParam{ // Not a continuation.
 			StreamID:      3, // different stream.
-			BlockFragment: encodeHeader(st.t),
+			BlockFragment: st.encodeHeader(),
 			EndStream:     true,
 			EndHeaders:    true,
 		})
@@ -1027,7 +1125,7 @@ func TestServer_Rejects_HeadersNoEnd_Then_Ping(t *testing.T) {
 	testServerRejects(t, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1,
-			BlockFragment: encodeHeader(st.t),
+			BlockFragment: st.encodeHeader(),
 			EndStream:     true,
 			EndHeaders:    false,
 		})
@@ -1042,7 +1140,7 @@ func TestServer_Rejects_HeadersEnd_Then_Continuation(t *testing.T) {
 	testServerRejects(t, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1,
-			BlockFragment: encodeHeader(st.t),
+			BlockFragment: st.encodeHeader(),
 			EndStream:     true,
 			EndHeaders:    true,
 		})
@@ -1058,7 +1156,7 @@ func TestServer_Rejects_HeadersNoEnd_Then_ContinuationWrongStream(t *testing.T)
 	testServerRejects(t, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1,
-			BlockFragment: encodeHeader(st.t),
+			BlockFragment: st.encodeHeader(),
 			EndStream:     true,
 			EndHeaders:    false,
 		})
@@ -1074,7 +1172,7 @@ func TestServer_Rejects_Headers0(t *testing.T) {
 		st.fr.AllowIllegalWrites = true
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      0,
-			BlockFragment: encodeHeader(st.t),
+			BlockFragment: st.encodeHeader(),
 			EndStream:     true,
 			EndHeaders:    true,
 		})
@@ -1085,7 +1183,7 @@ func TestServer_Rejects_Headers0(t *testing.T) {
 func TestServer_Rejects_Continuation0(t *testing.T) {
 	testServerRejects(t, func(st *serverTester) {
 		st.fr.AllowIllegalWrites = true
-		if err := st.fr.WriteContinuation(0, true, encodeHeader(t)); err != nil {
+		if err := st.fr.WriteContinuation(0, true, st.encodeHeader()); err != nil {
 			t.Fatal(err)
 		}
 	})
@@ -1616,7 +1714,7 @@ func TestServer_Response_Automatic100Continue(t *testing.T) {
 	}, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1, // clients send odd numbers
-			BlockFragment: encodeHeader(st.t, ":method", "POST", "expect", "100-continue"),
+			BlockFragment: st.encodeHeader(":method", "POST", "expect", "100-continue"),
 			EndStream:     false,
 			EndHeaders:    true,
 		})
@@ -1640,7 +1738,6 @@ func TestServer_Response_Automatic100Continue(t *testing.T) {
 		st.writeData(1, true, []byte(msg))
 
 		st.wantWindowUpdate(0, uint32(len(msg)))
-		st.wantWindowUpdate(1, uint32(len(msg)))
 
 		hf = st.wantHeaders()
 		if hf.StreamEnded() {
@@ -1683,7 +1780,7 @@ func TestServer_HandlerWriteErrorOnDisconnect(t *testing.T) {
 	}, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1,
-			BlockFragment: encodeHeader(st.t),
+			BlockFragment: st.encodeHeader(),
 			EndStream:     false,
 			EndHeaders:    true,
 		})
@@ -1727,7 +1824,7 @@ func TestServer_Rejects_Too_Many_Streams(t *testing.T) {
 	sendReq := func(id uint32, headers ...string) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      id,
-			BlockFragment: encodeHeader(st.t, headers...),
+			BlockFragment: st.encodeHeader(headers...),
 			EndStream:     true,
 			EndHeaders:    true,
 		})
@@ -1746,7 +1843,7 @@ func TestServer_Rejects_Too_Many_Streams(t *testing.T) {
 	// (It's also sent as a CONTINUATION, to verify we still track the decoder context,
 	// even if we're rejecting it)
 	rejectID := streamID()
-	headerBlock := encodeHeader(st.t, ":path", testPath)
+	headerBlock := st.encodeHeader(":path", testPath)
 	frag1, frag2 := headerBlock[:3], headerBlock[3:]
 	st.writeHeaders(HeadersFrameParam{
 		StreamID:      rejectID,
@@ -1799,20 +1896,26 @@ func TestServer_Response_ManyHeaders_With_Continuation(t *testing.T) {
 			}
 		}
 		if n < 5 {
-			t.Errorf("Only got %d CONTINUATION frames; expected 5+ (currently 6)")
+			t.Errorf("Only got %d CONTINUATION frames; expected 5+ (currently 6)", n)
 		}
 	})
 }
 
+// This previously crashed (reported by Mathieu Lonjaret as observed
+// while using Camlistore) because we got a DATA frame from the client
+// after the handler exited and our logic at the time was wrong,
+// keeping a stream in the map in stateClosed, which tickled an
+// invariant check later when we tried to remove that stream (via
+// defer sc.closeAllStreamsOnConnClose) when the serverConn serve loop
+// ended.
 func TestServer_NoCrash_HandlerClose_Then_ClientClose(t *testing.T) {
-	condSkipFailingTest(t)
 	testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
 		// nothing
 		return nil
 	}, func(st *serverTester) {
 		st.writeHeaders(HeadersFrameParam{
 			StreamID:      1,
-			BlockFragment: encodeHeader(st.t),
+			BlockFragment: st.encodeHeader(),
 			EndStream:     false, // DATA is coming
 			EndHeaders:    true,
 		})
@@ -1820,22 +1923,116 @@ func TestServer_NoCrash_HandlerClose_Then_ClientClose(t *testing.T) {
 		if !hf.HeadersEnded() || !hf.StreamEnded() {
 			t.Fatalf("want END_HEADERS+END_STREAM, got %v", hf)
 		}
+
+		// Sent when the a Handler closes while a client has
+		// indicated it's still sending DATA:
+		st.wantRSTStream(1, ErrCodeCancel)
+
 		// Now the handler has ended, so it's ended its
 		// stream, but the client hasn't closed its side
 		// (stateClosedLocal).  So send more data and verify
 		// it doesn't crash with an internal invariant panic, like
 		// it did before.
 		st.writeData(1, true, []byte("foo"))
+
+		// Sent after a peer sends data anyway (admittedly the
+		// previous RST_STREAM might've still been in-flight),
+		// but they'll get the more friendly 'cancel' code
+		// first.
+		st.wantRSTStream(1, ErrCodeStreamClosed)
+
+		// Set up a bunch of machinery to record the panic we saw
+		// previously.
+		var (
+			panMu    sync.Mutex
+			panicVal interface{}
+		)
+
+		testHookOnPanicMu.Lock()
+		testHookOnPanic = func(sc *serverConn, pv interface{}) bool {
+			panMu.Lock()
+			panicVal = pv
+			panMu.Unlock()
+			return true
+		}
+		testHookOnPanicMu.Unlock()
+
+		// Now force the serve loop to end, via closing the connection.
 		st.cc.Close()
 		select {
 		case <-st.sc.doneServing:
 			// Loop has exited.
+			panMu.Lock()
+			got := panicVal
+			panMu.Unlock()
+			if got != nil {
+				t.Errorf("Got panic: %v", got)
+			}
 		case <-time.After(5 * time.Second):
 			t.Error("timeout")
 		}
 	})
 }
 
+func TestServer_Rejects_TLS10(t *testing.T) { testRejectTLS(t, tls.VersionTLS10) }
+func TestServer_Rejects_TLS11(t *testing.T) { testRejectTLS(t, tls.VersionTLS11) }
+
+func testRejectTLS(t *testing.T, max uint16) {
+	st := newServerTester(t, nil, func(c *tls.Config) {
+		c.MaxVersion = max
+	})
+	defer st.Close()
+	gf := st.wantGoAway()
+	if got, want := gf.ErrCode, ErrCodeInadequateSecurity; got != want {
+		t.Errorf("Got error code %v; want %v", got, want)
+	}
+}
+
+func TestServer_Rejects_TLSBadCipher(t *testing.T) {
+	st := newServerTester(t, nil, func(c *tls.Config) {
+		// Only list bad ones:
+		c.CipherSuites = []uint16{
+			tls.TLS_RSA_WITH_RC4_128_SHA,
+			tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+			tls.TLS_RSA_WITH_AES_128_CBC_SHA,
+			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+			tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+			tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+			tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+			tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+			tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+		}
+	})
+	defer st.Close()
+	gf := st.wantGoAway()
+	if got, want := gf.ErrCode, ErrCodeInadequateSecurity; got != want {
+		t.Errorf("Got error code %v; want %v", got, want)
+	}
+}
+
+func TestServer_Advertises_Common_Cipher(t *testing.T) {
+	const requiredSuite = tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+	st := newServerTester(t, nil, func(c *tls.Config) {
+		// Have the client only support the one required by the spec.
+		c.CipherSuites = []uint16{requiredSuite}
+	}, func(ts *httptest.Server) {
+		var srv *http.Server = ts.Config
+		// Have the server configured with one specific cipher suite
+		// which is banned. This tests that ConfigureServer ends up
+		// adding the good one to this list.
+		srv.TLSConfig = &tls.Config{
+			CipherSuites: []uint16{tls.TLS_RSA_WITH_AES_128_CBC_SHA}, // just a banned one
+		}
+	})
+	defer st.Close()
+	st.greet()
+}
+
+// TODO: move this onto *serverTester, and re-use the same hpack
+// decoding context throughout.  We're just getting lucky here with
+// creating a new decoder each time.
 func decodeHeader(t *testing.T, headerBlock []byte) (pairs [][2]string) {
 	d := hpack.NewDecoder(initialHeaderTableSize, func(f hpack.HeaderField) {
 		pairs = append(pairs, [2]string{f.Name, f.Value})
@@ -1852,7 +2049,7 @@ func decodeHeader(t *testing.T, headerBlock []byte) (pairs [][2]string) {
 // testServerResponse sets up an idle HTTP/2 connection and lets you
 // write a single request with writeReq, and then reply to it in some way with the provided handler,
 // and then verify the output with the serverTester again (assuming the handler returns nil)
-func testServerResponse(t *testing.T,
+func testServerResponse(t testing.TB,
 	handler func(http.ResponseWriter, *http.Request) error,
 	client func(*serverTester),
 ) {
@@ -1889,6 +2086,23 @@ func testServerResponse(t *testing.T,
 	}
 }
 
+// readBodyHandler returns an http Handler func that reads len(want)
+// bytes from r.Body and fails t if the contents read were not
+// the value of want.
+func readBodyHandler(t *testing.T, want string) func(w http.ResponseWriter, r *http.Request) {
+	return func(w http.ResponseWriter, r *http.Request) {
+		buf := make([]byte, len(want))
+		_, err := io.ReadFull(r.Body, buf)
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		if string(buf) != want {
+			t.Errorf("read %q; want %q", buf, want)
+		}
+	}
+}
+
 func TestServerWithCurl(t *testing.T) {
 	if runtime.GOOS == "darwin" {
 		t.Skip("skipping Docker test on Darwin; requires --net which won't work with boot2docker anyway")
@@ -1940,3 +2154,66 @@ func TestServerWithCurl(t *testing.T) {
 		t.Error("never saw an http2 connection")
 	}
 }
+
+func BenchmarkServerGets(b *testing.B) {
+	b.ReportAllocs()
+
+	const msg = "Hello, world"
+	st := newServerTester(b, func(w http.ResponseWriter, r *http.Request) {
+		io.WriteString(w, msg)
+	})
+	defer st.Close()
+	st.greet()
+
+	// Give the server quota to reply. (plus it has the the 64KB)
+	if err := st.fr.WriteWindowUpdate(0, uint32(b.N*len(msg))); err != nil {
+		b.Fatal(err)
+	}
+
+	for i := 0; i < b.N; i++ {
+		id := 1 + uint32(i)*2
+		st.writeHeaders(HeadersFrameParam{
+			StreamID:      id,
+			BlockFragment: st.encodeHeader(),
+			EndStream:     true,
+			EndHeaders:    true,
+		})
+		st.wantHeaders()
+		df := st.wantData()
+		if !df.StreamEnded() {
+			b.Fatalf("DATA didn't have END_STREAM; got %v", df)
+		}
+	}
+}
+
+func BenchmarkServerPosts(b *testing.B) {
+	b.ReportAllocs()
+
+	const msg = "Hello, world"
+	st := newServerTester(b, func(w http.ResponseWriter, r *http.Request) {
+		io.WriteString(w, msg)
+	})
+	defer st.Close()
+	st.greet()
+
+	// Give the server quota to reply. (plus it has the the 64KB)
+	if err := st.fr.WriteWindowUpdate(0, uint32(b.N*len(msg))); err != nil {
+		b.Fatal(err)
+	}
+
+	for i := 0; i < b.N; i++ {
+		id := 1 + uint32(i)*2
+		st.writeHeaders(HeadersFrameParam{
+			StreamID:      id,
+			BlockFragment: st.encodeHeader(":method", "POST"),
+			EndStream:     false,
+			EndHeaders:    true,
+		})
+		st.writeData(id, true, nil)
+		st.wantHeaders()
+		df := st.wantData()
+		if !df.StreamEnded() {
+			b.Fatalf("DATA didn't have END_STREAM; got %v", df)
+		}
+	}
+}

+ 2 - 9
write.go

@@ -195,17 +195,10 @@ func (w write100ContinueHeadersFrame) writeFrame(ctx writeContext) error {
 }
 
 type writeWindowUpdate struct {
-	streamID uint32
+	streamID uint32 // or 0 for conn-level
 	n        uint32
 }
 
 func (wu writeWindowUpdate) writeFrame(ctx writeContext) error {
-	fr := ctx.Framer()
-	if err := fr.WriteWindowUpdate(0, wu.n); err != nil {
-		return err
-	}
-	if err := fr.WriteWindowUpdate(wu.streamID, wu.n); err != nil {
-		return err
-	}
-	return nil
+	return ctx.Framer().WriteWindowUpdate(wu.streamID, wu.n)
 }

+ 11 - 4
z_spec_test.go

@@ -17,6 +17,7 @@ import (
 	"sort"
 	"strconv"
 	"strings"
+	"sync"
 	"testing"
 )
 
@@ -25,17 +26,21 @@ var coverSpec = flag.Bool("coverspec", false, "Run spec coverage tests")
 // The global map of sentence coverage for the http2 spec.
 var defaultSpecCoverage specCoverage
 
-func init() {
-	f, err := os.Open("testdata/draft-ietf-httpbis-http2.xml")
-	if err != nil {
+var loadSpecOnce sync.Once
+
+func loadSpec() {
+	if f, err := os.Open("testdata/draft-ietf-httpbis-http2.xml"); err != nil {
 		panic(err)
+	} else {
+		defaultSpecCoverage = readSpecCov(f)
+		f.Close()
 	}
-	defaultSpecCoverage = readSpecCov(f)
 }
 
 // covers marks all sentences for section sec in defaultSpecCoverage. Sentences not
 // "covered" will be included in report outputed by TestSpecCoverage.
 func covers(sec, sentences string) {
+	loadSpecOnce.Do(loadSpec)
 	defaultSpecCoverage.cover(sec, sentences)
 }
 
@@ -281,6 +286,8 @@ func TestSpecCoverage(t *testing.T) {
 		t.Skip()
 	}
 
+	loadSpecOnce.Do(loadSpec)
+
 	var (
 		list     []specPart
 		cv       = defaultSpecCoverage.coverage