Skip to content

Commit b2939cf

Browse files
committed
feat: expose 0-RTT detection at stream level
1 parent d89f0fa commit b2939cf

File tree

5 files changed

+75
-1
lines changed

5 files changed

+75
-1
lines changed

h3-quinn/src/lib.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,18 @@ where
270270
recv: RecvStream,
271271
}
272272

273+
impl<B> BidiStream<B>
274+
where
275+
B: Buf,
276+
{
277+
/// Check if this stream was opened during 0-RTT.
278+
///
279+
/// See [RFC 8470 Section 5.2](https://www.rfc-editor.org/rfc/rfc8470.html#section-5.2).
280+
pub fn is_0rtt(&self) -> bool {
281+
self.recv.is_0rtt()
282+
}
283+
}
284+
273285
impl<B> quic::BidiStream<B> for BidiStream<B>
274286
where
275287
B: Buf,
@@ -338,6 +350,15 @@ where
338350
}
339351
}
340352

353+
impl<B> h3::server::Is0rtt for BidiStream<B>
354+
where
355+
B: Buf,
356+
{
357+
fn is_0rtt(&self) -> bool {
358+
BidiStream::is_0rtt(self)
359+
}
360+
}
361+
341362
/// Quinn-backed receive stream
342363
///
343364
/// Implements a [`quic::RecvStream`] backed by a [`quinn::RecvStream`].
@@ -362,6 +383,17 @@ impl RecvStream {
362383
read_chunk_fut: ReusableBoxFuture::new(async { unreachable!() }),
363384
}
364385
}
386+
387+
/// Check if this stream has been opened during 0-RTT.
388+
///
389+
/// In which case any non-idempotent request should be considered dangerous at the application
390+
/// level. Because read data is subject to replay attacks.
391+
pub fn is_0rtt(&self) -> bool {
392+
self.stream
393+
.as_ref()
394+
.map(|s| s.is_0rtt())
395+
.unwrap_or(false)
396+
}
365397
}
366398

367399
impl quic::RecvStream for RecvStream {

h3/src/frame.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ impl<S, B> FrameStream<S, B> {
4141
pub fn into_inner(self) -> BufRecvStream<S, B> {
4242
self.stream
4343
}
44+
45+
pub(crate) fn is_0rtt(&self) -> bool
46+
where
47+
S: crate::server::Is0rtt,
48+
{
49+
self.stream.is_0rtt()
50+
}
4451
}
4552

4653
impl<S, B> FrameStream<S, B>

h3/src/server/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,4 @@ pub use builder::builder;
5656
pub use builder::Builder;
5757
pub use connection::Connection;
5858
pub use request::RequestResolver;
59-
pub use stream::RequestStream;
59+
pub use stream::{Is0rtt, RequestStream};

h3/src/server/stream.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,34 @@ where
105105
pub fn id(&self) -> StreamId {
106106
self.inner.stream.id()
107107
}
108+
109+
/// Check if this stream was opened during 0-RTT.
110+
///
111+
/// See [RFC 8470 Section 5.2](https://www.rfc-editor.org/rfc/rfc8470.html#section-5.2).
112+
///
113+
/// # Example
114+
///
115+
/// ```no_run
116+
/// # use h3::server::RequestStream;
117+
/// # async fn example(mut stream: RequestStream<impl h3::quic::BidiStream<bytes::Bytes>, bytes::Bytes>) {
118+
/// if stream.is_0rtt() {
119+
/// // Reject non-idempotent methods (e.g., POST, PUT, DELETE)
120+
/// // to prevent replay attacks
121+
/// }
122+
/// # }
123+
/// ```
124+
pub fn is_0rtt(&self) -> bool
125+
where
126+
S: Is0rtt,
127+
{
128+
self.inner.stream.is_0rtt()
129+
}
130+
}
131+
132+
/// Trait for QUIC streams that support 0-RTT detection.
133+
pub trait Is0rtt {
134+
/// Check if this stream was opened during 0-RTT.
135+
fn is_0rtt(&self) -> bool;
108136
}
109137

110138
impl<S, B> RequestStream<S, B>

h3/src/stream.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,13 @@ impl<S, B> BufRecvStream<S, B> {
437437
_marker: PhantomData,
438438
}
439439
}
440+
441+
pub(crate) fn is_0rtt(&self) -> bool
442+
where
443+
S: crate::server::Is0rtt,
444+
{
445+
self.stream.is_0rtt()
446+
}
440447
}
441448

442449
impl<B, S: RecvStream> BufRecvStream<S, B> {

0 commit comments

Comments
 (0)