Repository: hyperium/h2 (rust)

Issue:
Beware: Idle states
I noticed [the following](http://httpwg.org/specs/rfc7540.html#StreamIdentifiers):

> The first use of a new stream identifier implicitly closes all streams in the "idle" state that might have been initiated by that peer with a lower-valued stream identifier. For example, if a client sends a HEADERS frame on stream 7 without ever sending a frame on stream 5, then stream 5 transitions to the "closed" state when the first frame for stream 7 is sent or received.

We need to be careful that we don't initialize streams in the idle state such that they may be moved into reserved/open out of order. We should probably only ever initialize streams directly to a Reserved or Open (or HalfOpenRemote) state.


Discussion:
This issue can be closed once there is a test ensuring this behavior is followed. I am pretty sure this behavior is already implemented.
To expand on my comment above: we need the client's send side (i.e. prioritize.rs) to ensure that headers are sent in stream-id-order.
> In fact, h2 probably should have an assert that stream id of initial headers frame is greater than all previously sent stream ids

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
@@ -1,7 +1,7 @@
 use super::*;
 use super::store::Resolve;
 
-use frame::Reason;
+use frame::{Reason, StreamId};
 
 use codec::UserError;
 use codec::UserError::*;
@@ -11,9 +11,18 @@ use bytes::buf::Take;
 use std::{cmp, fmt};
 use std::io;
 
+/// # Warning
+///
+/// Queued streans are ordered by stream ID, as we need to ensure that
+/// lower-numbered streams are sent headers before higher-numbered ones.
+/// This is because "idle" stream IDs – those which have been initiated but
+/// have yet to receive frames – will be implicitly closed on receipt of a
+/// frame on a higher stream ID. If these queues was not ordered by stream
+/// IDs, some mechanism would be necessary to ensure that the lowest-numberedh]
+/// idle stream is opened first.
 #[derive(Debug)]
 pub(super) struct Prioritize {
-    /// Queue of streams waiting for socket capacity to send a frame
+    /// Queue of streams waiting for socket capacity to send a frame.
     pending_send: store::Queue<stream::NextSend>,
 
     /// Queue of streams waiting for window capacity to produce data.
@@ -24,6 +33,9 @@ pub(super) struct Prioritize {
 
     /// Connection level flow control governing sent data
     flow: FlowControl,
+
+    /// Stream ID of the last stream opened.
+    last_opened_id: StreamId,
 }
 
 pub(crate) struct Prioritized<B> {
@@ -55,6 +67,7 @@ impl Prioritize {
             pending_capacity: store::Queue::new(),
             pending_open: store::Queue::new(),
             flow: flow,
+            last_opened_id: StreamId::ZERO
         }
     }
 
@@ -613,6 +626,11 @@ impl Prioritize {
 
                     trace!("pop_frame; frame={:?}", frame);
 
+                    if cfg!(debug_assertions) && stream.state.is_idle() {
+                        debug_assert!(stream.id > self.last_opened_id);
+                        self.last_opened_id = stream.id;
+                    }
+
                     if !stream.pending_send.is_empty() || stream.state.is_canceled() {
                         // TODO: Only requeue the sender IF it is ready to send
                         // the next frame. i.e. don't requeue it if the next
@@ -636,6 +654,7 @@ impl Prioritize {
         while counts.can_inc_num_send_streams() {
             if let Some(mut stream) = self.pending_open.pop(store) {
                 trace!("schedule_pending_open; stream={:?}", stream.id);
+
                 counts.inc_num_send_streams();
                 self.pending_send.push(&mut stream);
                 if let Some(task) = stream.open_task.take() {
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
@@ -331,6 +331,13 @@ impl State {
         }
     }
 
+    pub fn is_idle(&self) -> bool {
+        match self.inner {
+            Idle => true,
+            _ => false,
+        }
+    }
+
     pub fn ensure_recv_open(&self) -> Result<bool, proto::Error> {
         use std::io;
 


Test patch:
diff --git a/tests/stream_states.rs b/tests/stream_states.rs
--- a/tests/stream_states.rs
+++ b/tests/stream_states.rs
@@ -367,6 +367,60 @@ fn recv_goaway_finishes_processed_streams() {
     h2.join(srv).wait().expect("wait");
 }
 
+#[test]
+fn skipped_stream_ids_are_implicitly_closed() {
+    let _ = ::env_logger::init();
+    let (io, srv) = mock::new();
+
+    let srv = srv
+        .assert_client_handshake()
+        .expect("handshake")
+        .recv_settings()
+        .recv_frame(frames::headers(5)
+            .request("GET", "https://example.com/")
+            .eos(),
+        )
+        // send the response on a lower-numbered stream, which should be
+        // implicitly closed.
+        .send_frame(frames::headers(3).response(200));
+
+        let h2 = Client::builder()
+            .initial_stream_id(5)
+            .handshake::<_, Bytes>(io)
+            .expect("handshake")
+            .and_then(|(mut client, h2)| {
+                let request = Request::builder()
+                    .method(Method::GET)
+                    .uri("https://example.com/")
+                    .body(())
+                    .unwrap();
+
+                let req = client.send_request(request, true)
+                    .unwrap()
+                    .0.then(|res| {
+                        let err = res.unwrap_err();
+                        assert_eq!(
+                            err.to_string(),
+                            "protocol error: unspecific protocol error detected");
+                        Ok::<(), ()>(())
+                    });
+                    // client should see a conn error
+                    let conn = h2.then(|res| {
+                        let err = res.unwrap_err();
+                        assert_eq!(
+                            err.to_string(),
+                            "protocol error: unspecific protocol error detected"
+                        );
+                        Ok::<(), ()>(())
+                    });
+                    conn.unwrap().join(req)
+            });
+
+
+        h2.join(srv).wait().expect("wait");
+
+}
+
 /*
 #[test]
 fn send_data_after_headers_eos() {
diff --git a/tests/support/future_ext.rs b/tests/support/future_ext.rs
--- a/tests/support/future_ext.rs
+++ b/tests/support/future_ext.rs
@@ -15,6 +15,30 @@ pub trait FutureExt: Future {
         }
     }
 
+    /// Panic on success, yielding the content of an `Err`.
+    fn unwrap_err(self) -> UnwrapErr<Self>
+    where
+        Self: Sized,
+        Self::Error: fmt::Debug,
+    {
+        UnwrapErr {
+            inner: self,
+        }
+    }
+
+    /// Panic on success, with a message.
+    fn expect_err<T>(self, msg: T) -> ExpectErr<Self>
+    where
+        Self: Sized,
+        Self::Error: fmt::Debug,
+        T: fmt::Display,
+    {
+        ExpectErr{
+            inner: self,
+            msg: msg.to_string(),
+        }
+    }
+
     /// Panic on error, with a message.
     fn expect<T>(self, msg: T) -> Expect<Self>
     where
@@ -68,6 +92,32 @@ where
     }
 }
 
+// ===== UnwrapErr ======
+
+/// Panic on success.
+pub struct UnwrapErr<T> {
+    inner: T,
+}
+
+impl<T> Future for UnwrapErr<T>
+where
+    T: Future,
+    T::Item: fmt::Debug,
+    T::Error: fmt::Debug,
+{
+    type Item = T::Error;
+    type Error = ();
+
+    fn poll(&mut self) -> Poll<T::Error, ()> {
+        let poll =
+            self.inner.poll()
+                .map_err(Async::Ready)
+                .unwrap_err();
+        Ok(poll)
+    }
+}
+
+
 
 // ===== Expect ======
 
@@ -91,6 +141,32 @@ where
     }
 }
 
+// ===== ExpectErr ======
+
+/// Panic on success
+pub struct ExpectErr<T> {
+    inner: T,
+    msg: String,
+}
+
+impl<T> Future for ExpectErr<T>
+where
+    T: Future,
+    T::Item: fmt::Debug,
+    T::Error: fmt::Debug,
+{
+    type Item = T::Error;
+    type Error = ();
+
+    fn poll(&mut self) -> Poll<T::Error, ()> {
+        let poll =
+            self.inner.poll()
+                .map_err(Async::Ready)
+                .expect_err(&self.msg);
+        Ok(poll)
+    }
+}
+
 // ===== Drive ======
 
 /// Drive a future to completion while also polling the driver