Skip to content

Commit c6cad67

Browse files
committed
Return InvalidStreamState from stream_send() for collected streams
When a stream has been completed and garbage collected (for example after the peer sends STOP_SENDING, the fin bit is received, and the data is drained via stream_recv()), a subsequent stream_send() call hits get_or_create() for an id that is in the collected set, which returns Error::Done. That error was forwarded unchanged, so callers saw Done (normally "no capacity") for a stream that no longer exists. Map that case to Error::InvalidStreamState instead, matching the behaviour of stream_recv() which already returns InvalidStreamState for a non-existent stream. This lets applications distinguish a lack of send capacity from an invalid stream id without an extra stream_writable() check. Fixes #1695
1 parent f0c7193 commit c6cad67

2 files changed

Lines changed: 44 additions & 2 deletions

File tree

quiche/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6025,6 +6025,10 @@ impl<F: BufFactory> Connection<F> {
60256025
return Err(Error::StreamLimit);
60266026
},
60276027

6028+
// Stream already collected; mirror stream_recv()'s
6029+
// InvalidStreamState.
6030+
Err(Error::Done) => return Err(Error::InvalidStreamState(stream_id)),
6031+
60286032
Err(e) => return Err(e),
60296033
};
60306034

quiche/src/tests.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,6 +3472,38 @@ fn stop_sending_fin(
34723472
assert_eq!(iter.next(), None);
34733473
}
34743474

3475+
#[rstest]
3476+
/// Tests that stream_send() on a collected stream returns InvalidStreamState
3477+
/// rather than Done (issue #1695).
3478+
fn stream_send_after_stop_sending_and_fin(
3479+
#[values("cubic", "bbr2_gcongestion")] cc_algorithm_name: &str,
3480+
) {
3481+
let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();
3482+
assert_eq!(pipe.handshake(), Ok(()));
3483+
3484+
// Server opens a bidi stream and sends data with fin.
3485+
assert_eq!(pipe.server.stream_send(5, b"hello", true), Ok(5));
3486+
3487+
// Server also sends STOP_SENDING, indicating it will not read from the
3488+
// client on this stream.
3489+
assert_eq!(pipe.server.stream_shutdown(5, Shutdown::Read, 42), Ok(()));
3490+
3491+
// Exchange all packets: client receives the STOP_SENDING and STREAM(fin).
3492+
assert_eq!(pipe.advance(), Ok(()));
3493+
3494+
// Client reads the data. The stream is now complete on both sides (recv
3495+
// finished, send stopped), so quiche collects it.
3496+
let mut buf = [0; 64];
3497+
assert_eq!(pipe.client.stream_recv(5, &mut buf), Ok((5, true)));
3498+
3499+
// The stream has been collected. stream_send() must return
3500+
// InvalidStreamState, not Done.
3501+
assert_eq!(
3502+
pipe.client.stream_send(5, b"world", false),
3503+
Err(Error::InvalidStreamState(5)),
3504+
);
3505+
}
3506+
34753507
#[rstest]
34763508
/// Tests that resetting a stream restores flow control for unsent data.
34773509
fn stop_sending_unsent_tx_cap(
@@ -5541,7 +5573,10 @@ fn collect_streams(
55415573
assert!(pipe.client.stream_finished(0));
55425574
assert!(pipe.server.stream_finished(0));
55435575

5544-
assert_eq!(pipe.client.stream_send(0, b"", true), Err(Error::Done));
5576+
assert_eq!(
5577+
pipe.client.stream_send(0, b"", true),
5578+
Err(Error::InvalidStreamState(0))
5579+
);
55455580

55465581
let frames = [frame::Frame::Stream {
55475582
stream_id: 0,
@@ -11812,7 +11847,10 @@ fn stop_sending_stream_send_after_reset_stream_ack(
1181211847

1181311848
// If we called send before the client ACK of reset stream, it would
1181411849
// have failed with StreamStopped.
11815-
assert_eq!(pipe.server.stream_send(0, b"world", true), Err(Error::Done),);
11850+
assert_eq!(
11851+
pipe.server.stream_send(0, b"world", true),
11852+
Err(Error::InvalidStreamState(0)),
11853+
);
1181611854

1181711855
// Stream 0 is still not writable.
1181811856
let mut w = pipe.server.writable();

0 commit comments

Comments
 (0)