@@ -2,14 +2,8 @@ use crate::{coding, serve, setup};
22
33#[ derive( thiserror:: Error , Debug , Clone ) ]
44pub enum SessionError {
5- #[ error( "webtransport session: {0}" ) ]
6- Session ( #[ from] web_transport:: SessionError ) ,
7-
8- #[ error( "webtransport write: {0}" ) ]
9- Write ( #[ from] web_transport:: WriteError ) ,
10-
11- #[ error( "webtransport read: {0}" ) ]
12- Read ( #[ from] web_transport:: ReadError ) ,
5+ #[ error( "webtransport error: {0}" ) ]
6+ WebTransport ( #[ from] web_transport:: Error ) ,
137
148 #[ error( "encode error: {0}" ) ]
159 Encode ( #[ from] coding:: EncodeError ) ,
@@ -53,9 +47,7 @@ impl SessionError {
5347 // PROTOCOL_VIOLATION (0x3) - The role negotiated in the handshake was violated
5448 Self :: RoleViolation => 0x3 ,
5549 // INTERNAL_ERROR (0x1) - Generic internal errors
56- Self :: Session ( _) => 0x1 ,
57- Self :: Read ( _) => 0x1 ,
58- Self :: Write ( _) => 0x1 ,
50+ Self :: WebTransport ( _) => 0x1 ,
5951 Self :: Encode ( _) => 0x1 ,
6052 Self :: BoundsExceeded ( _) => 0x1 ,
6153 Self :: Internal => 0x1 ,
@@ -79,42 +71,48 @@ impl SessionError {
7971
8072 /// Returns true if this error represents a graceful connection close.
8173 ///
82- /// A graceful close occurs when the peer sends APPLICATION_CLOSE with error code 0
83- /// (NO_ERROR). This is normal session termination, not an error condition.
74+ /// For WebTransport, a graceful close is a `CLOSE_WEBTRANSPORT_SESSION` capsule
75+ /// with code 0. For raw QUIC, it's `APPLICATION_CLOSE` with code 0 (NO_ERROR).
76+ /// Both are normal session termination, not error conditions.
8477 ///
8578 /// This method checks for:
86- /// - WebTransport close with code 0 (HTTP/3 encoded as 0x52e4a40fa8db)
79+ /// - WebTransport `Closed(0, _)` — web-transport-quinn v0.11+ typically converts
80+ /// HTTP/3-encoded `ApplicationClosed` codes into `WebTransportError::Closed(code, reason)`
81+ /// during `SessionError` conversion when decoding via `error_from_http3` succeeds
8782 /// - Raw QUIC `ApplicationClosed` with code 0
8883 /// - The local side closing the connection (`LocallyClosed`)
8984 ///
9085 /// ## Implementation Notes
9186 ///
92- /// We pattern match on `web_transport_quinn::SessionError` variants to access the
93- /// underlying `quinn::ConnectionError`. For WebTransport connections, the close code
94- /// is encoded using HTTP/3 error code space, which we decode using
95- /// `web_transport_proto::error_from_http3() `.
87+ /// We pattern match on `web_transport_quinn::SessionError` variants. In v0.11+,
88+ /// WebTransport graceful closes arrive as `WebTransportError::Closed(0, _)` because
89+ /// the crate decodes HTTP/3 error codes at the `SessionError` level. For raw QUIC
90+ /// connections, the close code is checked directly on `ConnectionError::ApplicationClosed `.
9691 ///
9792 /// **Coupling note**: This implementation is coupled to `web-transport-quinn` and
9893 /// `quinn`. When transitioning to a different WebTransport backend (e.g., tokio-quiche),
9994 /// ensure the replacement provides equivalent error introspection, or update this
10095 /// method to handle the new error types.
10196 pub fn is_graceful_close ( & self ) -> bool {
10297 match self {
103- Self :: Session ( session_err) => is_session_error_graceful ( session_err) ,
104- Self :: Read ( read_err) => {
105- // ReadError::SessionError wraps SessionError
106- if let web_transport:: ReadError :: SessionError ( session_err) = read_err {
107- return is_session_error_graceful ( session_err) ;
98+ Self :: WebTransport ( wt_err) => match wt_err {
99+ web_transport:: Error :: Session ( session_err) => {
100+ is_session_error_graceful ( session_err)
108101 }
109- false
110- }
111- Self :: Write ( write_err) => {
112- // WriteError::SessionError wraps SessionError
113- if let web_transport:: WriteError :: SessionError ( session_err) = write_err {
114- return is_session_error_graceful ( session_err) ;
102+ web_transport:: Error :: Read ( read_err) => {
103+ if let web_transport:: quinn:: ReadError :: SessionError ( session_err) = read_err {
104+ return is_session_error_graceful ( session_err) ;
105+ }
106+ false
115107 }
116- false
117- }
108+ web_transport:: Error :: Write ( write_err) => {
109+ if let web_transport:: quinn:: WriteError :: SessionError ( session_err) = write_err {
110+ return is_session_error_graceful ( session_err) ;
111+ }
112+ false
113+ }
114+ _ => false ,
115+ } ,
118116 _ => false ,
119117 }
120118 }
@@ -129,26 +127,37 @@ impl From<SessionError> for serve::ServeError {
129127 }
130128}
131129
132- /// Helper to check if a `web_transport ::SessionError` represents a graceful close.
130+ /// Helper to check if a `web_transport_quinn ::SessionError` represents a graceful close.
133131///
134- /// This handles both:
135- /// - Raw QUIC connections: `ApplicationClosed` with code 0
136- /// - WebTransport connections: `ApplicationClosed` with HTTP/3 encoded code that decodes to 0
137- fn is_session_error_graceful ( err : & web_transport:: SessionError ) -> bool {
138- use web_transport_quinn:: SessionError ;
132+ /// This handles:
133+ /// - WebTransport connections: `WebTransportError::Closed(0, _)` — web-transport-quinn v0.11+
134+ /// typically decodes HTTP/3-encoded close codes at this layer (when `SessionError` conversion
135+ /// applies), so graceful closes usually arrive here rather than as a raw
136+ /// `ConnectionError::ApplicationClosed`.
137+ /// - Raw QUIC connections: `ConnectionError::ApplicationClosed` with code 0
138+ /// - Local close: `ConnectionError::LocallyClosed`
139+ fn is_session_error_graceful ( err : & web_transport:: quinn:: SessionError ) -> bool {
140+ use web_transport:: quinn:: { SessionError , WebTransportError } ;
139141
140142 match err {
141143 SessionError :: ConnectionError ( conn_err) => is_connection_error_graceful ( conn_err) ,
142- // WebTransportError doesn't represent connection close in 0.3.x
144+ // WebTransport graceful close: peer sent close with code 0
145+ SessionError :: WebTransportError ( WebTransportError :: Closed ( 0 , _) ) => true ,
146+ // Other WebTransport errors (UnknownSession, read/write errors, non-zero close codes)
143147 SessionError :: WebTransportError ( _) => false ,
144148 // SendDatagramError doesn't represent connection close
145149 SessionError :: SendDatagramError ( _) => false ,
146150 }
147151}
148152
149153/// Helper to check if a `quinn::ConnectionError` represents a graceful close.
150- fn is_connection_error_graceful ( err : & quinn:: ConnectionError ) -> bool {
151- use quinn:: ConnectionError ;
154+ ///
155+ /// Note: In web-transport-quinn v0.11+, WebTransport `ApplicationClosed` with an HTTP/3-encoded
156+ /// close code is usually converted to `WebTransportError::Closed` during `SessionError` conversion
157+ /// when decoding succeeds. This function primarily handles raw QUIC (moqt:// ALPN) connections
158+ /// or non-decodable cases where the close code is not HTTP/3 encoded.
159+ fn is_connection_error_graceful ( err : & web_transport:: quinn:: quinn:: ConnectionError ) -> bool {
160+ use web_transport:: quinn:: quinn:: ConnectionError ;
152161
153162 match err {
154163 ConnectionError :: ApplicationClosed ( close) => {
@@ -160,8 +169,9 @@ fn is_connection_error_graceful(err: &quinn::ConnectionError) -> bool {
160169 }
161170
162171 // Check for WebTransport code 0 (HTTP/3 encoded)
163- // WebTransport code 0 maps to HTTP/3 code 0x52e4a40fa8db
164- if let Some ( wt_code) = web_transport_proto:: error_from_http3 ( code) {
172+ // This is a fallback — in v0.11+, WebTransport closes are typically caught
173+ // by is_session_error_graceful's WebTransportError::Closed branch.
174+ if let Some ( wt_code) = web_transport:: quinn:: proto:: error_from_http3 ( code) {
165175 return wt_code == 0 ;
166176 }
167177
0 commit comments