internal/http3: send appropriate error codes in STOP_SENDING frames

Now that the quic package supports setting an error code for
STOP_SENDING frames, update the HTTP/3 layer accordingly.

For golang/go#70914

Change-Id: I0f439c685e4fda883dddeb92fae78ddc6a6a6964
Reviewed-on: https://go-review.googlesource.com/c/net/+/825364
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: David Chase <drchase@google.com>
diff --git a/internal/http3/body.go b/internal/http3/body.go
index c80bc75..ed27a83 100644
--- a/internal/http3/body.go
+++ b/internal/http3/body.go
@@ -225,7 +225,7 @@
 func (r *bodyReader) Close() error {
 	// Unlike the HTTP/1 and HTTP/2 body readers (at the time of this comment being written),
 	// calling Close concurrently with Read will interrupt the read.
-	r.st.CloseRead()
+	r.st.CloseRead(uint64(errH3NoError))
 	// Make sure that any data that has already been written to bodyReader
 	// cannot be read after it has been closed.
 	r.mu.Lock()
diff --git a/internal/http3/conn.go b/internal/http3/conn.go
index a90464e..02776d6 100644
--- a/internal/http3/conn.go
+++ b/internal/http3/conn.go
@@ -74,12 +74,10 @@
 		// "Recipients of unknown stream types MUST either abort reading
 		// of the stream or discard incoming data without further processing."
 		// https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2-7
-		//
-		// We should send the H3_STREAM_CREATION_ERROR error code,
-		// but the quic package currently doesn't allow setting error codes
-		// for STOP_SENDING frames.
-		// TODO: Should CloseRead take an error code?
-		err = nil
+		err = &streamError{
+			code:    errH3StreamCreationError,
+			message: "unknown stream type",
+		}
 	}
 	if err == io.EOF {
 		err = &connectionError{
@@ -99,13 +97,13 @@
 	case *connectionError:
 		h.abort(err)
 	case nil:
-		st.CloseRead()
+		st.CloseRead(uint64(errH3NoError))
 		st.CloseWrite()
 	case *streamError:
-		st.CloseRead()
+		st.CloseRead(uint64(err.code))
 		st.Reset(uint64(err.code))
 	default:
-		st.CloseRead()
+		st.CloseRead(uint64(errH3InternalError))
 		st.Reset(uint64(errH3InternalError))
 	}
 }
diff --git a/internal/http3/conn_test.go b/internal/http3/conn_test.go
index 91eb404..9cc7305 100644
--- a/internal/http3/conn_test.go
+++ b/internal/http3/conn_test.go
@@ -5,8 +5,11 @@
 package http3
 
 import (
+	"errors"
 	"testing"
 	"testing/synctest"
+
+	"golang.org/x/net/quic"
 )
 
 // Tests which apply to both client and server connections.
@@ -31,8 +34,9 @@
 		// The endpoint should send a STOP_SENDING for this stream,
 		// but it should not close the connection.
 		synctest.Wait()
-		if _, err := st.Write([]byte("hello")); err == nil {
-			t.Fatalf("write to send-only stream with an unknown type succeeded; want error")
+		wantErr := quic.StreamError(errH3StreamCreationError)
+		if _, err := st.Write([]byte("hello")); !errors.Is(err, wantErr) {
+			t.Fatalf("write to send-only stream with an unknown type returned error %v; want %v", err, wantErr)
 		}
 		tc.wantNotClosed("after receiving unknown unidirectional stream type")
 	})
diff --git a/internal/http3/roundtrip.go b/internal/http3/roundtrip.go
index 3fd80dc..36d72fb 100644
--- a/internal/http3/roundtrip.go
+++ b/internal/http3/roundtrip.go
@@ -53,10 +53,10 @@
 		case *connectionError:
 			rt.cc.abort(e)
 		case *streamError:
-			rt.st.CloseRead()
+			rt.st.CloseRead(uint64(e.code))
 			rt.st.Reset(uint64(e.code))
 		default:
-			rt.st.CloseRead()
+			rt.st.CloseRead(uint64(errH3NoError))
 			rt.st.Reset(uint64(errH3NoError))
 		}
 	})
@@ -285,11 +285,7 @@
 // code, and the client MUST NOT discard the response.
 func reqBodyIgnored(err error) bool {
 	if streamErr, ok := errors.AsType[quic.StreamError](err); ok {
-		// TODO: the H3_NO_ERROR should arrive in a QUIC STOP_SENDING frame.
-		// However, the quic package currently only sends code 0 in the
-		// STOP_SENDING frame due to its API limitation.
-		// For now, accept 0, in addition to H3_NO_ERROR.
-		return streamErr == 0 || http3Error(streamErr) == errH3NoError
+		return http3Error(streamErr) == errH3NoError
 	}
 	return false
 }
diff --git a/internal/http3/roundtrip_test.go b/internal/http3/roundtrip_test.go
index 54dd10f..f5f9a9e 100644
--- a/internal/http3/roundtrip_test.go
+++ b/internal/http3/roundtrip_test.go
@@ -413,7 +413,7 @@
 
 			// Server stops reading the request because it has enough
 			// information already to construct its response.
-			st.CloseRead()
+			st.CloseRead(uint64(errH3NoError))
 			st.writeHeaders(http.Header{
 				":status": {"200"},
 			})
diff --git a/internal/http3/stream.go b/internal/http3/stream.go
index 3529983..b2e7eac 100644
--- a/internal/http3/stream.go
+++ b/internal/http3/stream.go
@@ -82,9 +82,9 @@
 	return st.stream.Close()
 }
 
-func (st *stream) CloseRead() {
+func (st *stream) CloseRead(code uint64) {
 	st.readDeadline.stop()
-	st.stream.CloseRead()
+	st.stream.StopSending(code)
 }
 
 func (st *stream) CloseWrite() {