Repository: hyperium/h2 (rust)

Issue:
Accept stream WINDOW_UPDATE frames in reserved state
In general, the various states in which frames can be accepted should be audited.

http://httpwg.org/specs/rfc7540.html#StreamStates


Discussion:
I'm fairly sure this is working, as the `recv_window_update` only checks that the target stream is not idle.
I guess this would require verifying that it currently works and add a test.

Patch:
diff --git a/src/proto/streams/prioritize.rs b/src/proto/streams/prioritize.rs
--- a/src/proto/streams/prioritize.rs
+++ b/src/proto/streams/prioritize.rs
@@ -238,6 +238,11 @@ impl Prioritize {
             stream.send_flow
         );
 
+        if stream.state.is_send_closed() && stream.buffered_send_data == 0 {
+            // We can't send any data, so don't bother doing anything else.
+            return Ok(());
+        }
+
         // Update the stream level flow control.
         stream.send_flow.inc_window(inc)?;
 
diff --git a/src/proto/streams/recv.rs b/src/proto/streams/recv.rs
--- a/src/proto/streams/recv.rs
+++ b/src/proto/streams/recv.rs
@@ -745,6 +745,10 @@ impl Recv {
             if !stream.state.is_recv_streaming() {
                 // No need to send window updates on the stream if the stream is
                 // no longer receiving data.
+                //
+                // TODO: is this correct? We could possibly send a window
+                // update on a ReservedRemote stream if we already know
+                // we want to stream the data faster...
                 continue;
             }
 
diff --git a/src/proto/streams/state.rs b/src/proto/streams/state.rs
--- a/src/proto/streams/state.rs
+++ b/src/proto/streams/state.rs
@@ -361,6 +361,13 @@ impl State {
         }
     }
 
+    pub fn is_send_closed(&self) -> bool {
+        match self.inner {
+            Closed(..) | HalfClosedLocal(..) | ReservedRemote => true,
+            _ => false,
+        }
+    }
+
     pub fn is_idle(&self) -> bool {
         match self.inner {
             Idle => true,


Test patch:
diff --git a/tests/stream_states.rs b/tests/stream_states.rs
--- a/tests/stream_states.rs
+++ b/tests/stream_states.rs
@@ -666,6 +666,58 @@ fn rst_stream_max() {
         });
 
 
+    client.join(srv).wait().expect("wait");
+}
+
+#[test]
+fn reserved_state_recv_window_update() {
+    let _ = ::env_logger::init();
+    let (io, srv) = mock::new();
+
+    let srv = srv.assert_client_handshake()
+        .unwrap()
+        .recv_settings()
+        .recv_frame(
+            frames::headers(1)
+                .request("GET", "https://example.com/")
+                .eos(),
+        )
+        .send_frame(
+            frames::push_promise(1, 2)
+                .request("GET", "https://example.com/push")
+        )
+        // it'd be weird to send a window update on a push promise,
+        // since the client can't send us data, but whatever. The
+        // point is that it's allowed, so we're testing it.
+        .send_frame(frames::window_update(2, 128))
+        .send_frame(frames::headers(1).response(200).eos())
+        // ping pong to ensure no goaway
+        .ping_pong([1; 8])
+        .close();
+
+    let client = Client::handshake(io)
+        .expect("handshake")
+        .and_then(|(mut client, conn)| {
+            let request = Request::builder()
+                .method(Method::GET)
+                .uri("https://example.com/")
+                .body(())
+                .unwrap();
+
+            let req = client.send_request(request, true)
+                .unwrap()
+                .0.expect("response")
+                .and_then(|resp| {
+                    assert_eq!(resp.status(), StatusCode::OK);
+                    Ok(())
+                });
+
+
+            conn.drive(req)
+                .and_then(|(conn, _)| conn.expect("client"))
+        });
+
+
     client.join(srv).wait().expect("wait");
 }
 /*