Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions h3-quinn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,12 +338,22 @@ where
}
}

impl<B> quic::Is0rtt for BidiStream<B>
where
B: Buf,
{
fn is_0rtt(&self) -> bool {
self.recv.is_0rtt()
}
}

/// Quinn-backed receive stream
///
/// Implements a [`quic::RecvStream`] backed by a [`quinn::RecvStream`].
pub struct RecvStream {
stream: Option<quinn::RecvStream>,
read_chunk_fut: ReadChunkFuture,
is_0rtt: bool,
}

type ReadChunkFuture = ReusableBoxFuture<
Expand All @@ -356,10 +366,12 @@ type ReadChunkFuture = ReusableBoxFuture<

impl RecvStream {
fn new(stream: quinn::RecvStream) -> Self {
let is_0rtt = stream.is_0rtt();
Self {
stream: Some(stream),
// Should only allocate once the first time it's used
read_chunk_fut: ReusableBoxFuture::new(async { unreachable!() }),
is_0rtt,
}
}
}
Expand Down Expand Up @@ -403,6 +415,16 @@ impl quic::RecvStream for RecvStream {
}
}

impl quic::Is0rtt for RecvStream {
/// Check if this stream has been opened during 0-RTT.
///
/// In which case any non-idempotent request should be considered dangerous at the application
/// level. Because read data is subject to replay attacks.
fn is_0rtt(&self) -> bool {
self.is_0rtt
}
}

fn convert_read_error_to_stream_error(error: ReadError) -> StreamErrorIncoming {
match error {
ReadError::Reset(var_int) => StreamErrorIncoming::StreamTerminated {
Expand Down
10 changes: 10 additions & 0 deletions h3/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ impl<S, B> FrameStream<S, B> {
}
}

impl<S, B> FrameStream<S, B>
where
S: crate::quic::Is0rtt,
{
/// Checks if the stream was opened in 0-RTT mode
pub(crate) fn is_0rtt(&self) -> bool {
self.stream.is_0rtt()
}
}

impl<S, B> FrameStream<S, B>
where
S: RecvStream,
Expand Down
16 changes: 16 additions & 0 deletions h3/src/quic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,19 @@ pub trait BidiStream<B: Buf>: SendStream<B> + RecvStream {
/// Split this stream into two halves.
fn split(self) -> (Self::SendStream, Self::RecvStream);
}

/// Trait for QUIC streams that support 0-RTT detection.
///
/// This allows detection of streams opened during the 0-RTT phase of a QUIC connection.
/// 0-RTT data is vulnerable to replay attacks, so applications should be cautious when
/// processing non-idempotent requests on such streams.
///
/// See [RFC 8470 Section 5.2](https://www.rfc-editor.org/rfc/rfc8470.html#section-5.2)
/// for guidance on handling 0-RTT data in HTTP/3.
pub trait Is0rtt {
/// Check if this stream was opened during 0-RTT.
///
/// Returns `true` if the stream was opened during the 0-RTT phase,
/// `false` otherwise.
fn is_0rtt(&self) -> bool;
}
22 changes: 22 additions & 0 deletions h3/src/server/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@ where
pub fn id(&self) -> StreamId {
self.inner.stream.id()
}

/// Check if this stream was opened during 0-RTT.
///
/// See [RFC 8470 Section 5.2](https://www.rfc-editor.org/rfc/rfc8470.html#section-5.2).
///
/// # Example
///
/// ```no_run
/// # use h3::server::RequestStream;
/// # async fn example(mut stream: RequestStream<impl h3::quic::BidiStream<bytes::Bytes> + h3::quic::Is0rtt, bytes::Bytes>) {
/// if stream.is_0rtt() {
/// // Reject non-idempotent methods (e.g., POST, PUT, DELETE)
/// // to prevent replay attacks
/// }
/// # }
/// ```
pub fn is_0rtt(&self) -> bool
where
S: quic::Is0rtt,
{
self.inner.stream.is_0rtt()
}
}

impl<S, B> RequestStream<S, B>
Expand Down
10 changes: 10 additions & 0 deletions h3/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,16 @@ impl<S, B> BufRecvStream<S, B> {
}
}

impl<S, B> BufRecvStream<S, B>
where
S: crate::quic::Is0rtt,
{
/// Checks if the stream was opened in 0-RTT mode
pub(crate) fn is_0rtt(&self) -> bool {
self.stream.is_0rtt()
}
}

impl<B, S: RecvStream> BufRecvStream<S, B> {
/// Reads more data into the buffer, returning the number of bytes read.
///
Expand Down
Loading