Skip to content

Commit f97258d

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

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
@@ -271,6 +271,18 @@ where
271271
recv: RecvStream,
272272
}
273273

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

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

368400
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)