Skip to content

Async accept#159

Merged
kixelated merged 5 commits intomainfrom
async-accept
Feb 11, 2026
Merged

Async accept#159
kixelated merged 5 commits intomainfrom
async-accept

Conversation

@kixelated
Copy link
Collaborator

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

Walkthrough

The PR renames CONNECT state types (ConnectConnecting, ConnectCompleteConnected) and changes the respond API to ok()/reject(). Connected now holds request, response, and send/recv streams; connection types store ConnectRequest/ConnectResponse instead of URL/protocol fields. Added ConnectResponse::OK and Default impl. Exposed TLS SNI, ALPN, and local address accessors and an Incoming::reject API. ClientBuilder gained Clone. Client connect APIs now accept impl Into<ConnectRequest>. Package version bump to 0.1.0.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Async accept' is vague and does not clearly summarize the substantial API refactoring present in this changeset, which involves renaming Connect/ConnectComplete to Connecting/Connected, restructuring request/response handling, and updating method signatures across multiple crates. Provide a more specific title that describes the primary architectural change, such as 'Refactor WebTransport API: Rename Connect/ConnectComplete to Connecting/Connected' or similar.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether the intent aligns with the changeset. Add a detailed pull request description explaining the motivation for the API refactoring, the rationale for renaming types, and the impact on users of these crates.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 92.86% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch async-accept

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@web-transport-quiche/src/h3/request.rs`:
- Around line 34-37: The close method in request.rs is calling
self.connect.ok(status) which only sends the response but doesn't finish the
send stream; change it to delegate to self.connect.close(status) so the
Connecting::close implementation (which calls connect.send.finish()) runs;
update the call in pub async fn close(self, status: http::StatusCode) ->
Result<(), ServerError> to call self.connect.close(status).await? and propagate
the Result as before, ensuring the await and error type match the existing
method signature.
🧹 Nitpick comments (6)
web-transport-quiche/examples/echo-server.rs (1)

74-76: Nit: .ok(http::StatusCode::OK) is redundant.

The method name ok() already implies acceptance, so passing StatusCode::OK explicitly reads as a tautology. If the library API allows calling .ok() without an argument (or always implies 200), that would be cleaner. If the status code parameter exists to allow other 2xx codes, a more neutral method name (e.g., accept) might be better — but that's a library-level design decision, not something to change here.

web-transport/src/quinn.rs (1)

77-77: Consider using ConnectResponse::ok() instead of http::StatusCode::OK.

Since the method is already named ok(), passing ConnectResponse::ok() (or even just ConnectResponse::ok() with a protocol) would be more idiomatic and self-documenting than raw http::StatusCode::OK. Minor nit — current code works correctly via the From<StatusCode> impl.

web-transport-quinn/examples/echo-server.rs (1)

94-101: Consider using the new ConnectResponse::ok() convenience constructor.

Line 94 manually constructs ConnectResponse::new(http::StatusCode::OK), but this PR adds ConnectResponse::ok() for exactly this purpose.

Suggested simplification
-    let mut response = ConnectResponse::new(http::StatusCode::OK);
+    let mut response = ConnectResponse::ok();
web-transport-quiche/src/h3/connect.rs (3)

61-76: Missing protocol validation in ok() – inconsistent with quinn counterpart.

The quinn version of Connecting::ok() (web-transport-quinn/src/connect.rs lines 66-70) validates that the response protocol is present in the client's request protocols list, returning Err(ConnectError::ProtocolMismatch(...)) on mismatch. This quiche version skips that check entirely, which could allow the server to respond with a protocol the client never offered.

Consider whether this omission is intentional. If not:

♻️ Add protocol validation to match quinn behavior
     pub async fn ok(
         mut self,
         response: impl Into<ConnectResponse>,
     ) -> Result<Connected, ConnectError> {
         let response = response.into();
 
+        // Validate that our protocol was in the client's request.
+        if let Some(protocol) = &response.protocol {
+            if !self.request.protocols.contains(protocol) {
+                return Err(ConnectError::Proto(
+                    web_transport_proto::ConnectError::ProtocolMismatch(protocol.clone()),
+                ));
+            }
+        }
+
         tracing::debug!(?response, "sending CONNECT");
         response.write(&mut self.send).await?;

105-138: Missing protocol validation in open() – inconsistent with quinn counterpart.

Similarly, the quinn version of Connected::open() (web-transport-quinn/src/connect.rs lines 136-139) validates the server's response protocol against the request. This quiche version only checks for a 200 OK status. If the server returns a protocol the client didn't request, this would silently succeed.

♻️ Add protocol validation to match quinn behavior
         // Throw an error if we didn't get a 200 OK.
         if response.status != http::StatusCode::OK {
             return Err(ConnectError::Status(response.status));
         }
 
+        // Validate that the server's protocol was in our request.
+        if let Some(protocol) = &response.protocol {
+            if !request.protocols.contains(protocol) {
+                return Err(ConnectError::Proto(
+                    web_transport_proto::ConnectError::ProtocolMismatch(protocol.clone()),
+                ));
+            }
+        }
+
         Ok(Self {

27-36: Nit: recv field on Connecting is #[allow(dead_code)] – consider if it should be used.

The recv stream is marked dead code in Connecting but is passed through to Connected in ok(). If it's not read before the response is sent, this is fine as a placeholder for the CONNECT stream lifecycle. Just noting the asymmetry with the quinn version where recv is pub(crate).

Comment on lines 34 to 37
pub async fn close(self, status: http::StatusCode) -> Result<(), ServerError> {
self.connect.ok(status).await?;
Ok(())
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: close should delegate to self.connect.close(status), not self.connect.ok(status).

Calling ok(status) sends the response but does not finish the send stream. The Connecting::close method (in web-transport-quiche/src/h3/connect.rs lines 78-82) additionally calls connect.send.finish() to properly tear down the stream. The quinn counterpart (web-transport-quinn/src/server.rs line 171) correctly delegates to self.connect.close(status).

🐛 Proposed fix
     pub async fn close(self, status: http::StatusCode) -> Result<(), ServerError> {
-        self.connect.ok(status).await?;
+        self.connect.close(status).await?;
         Ok(())
     }
🤖 Prompt for AI Agents
In `@web-transport-quiche/src/h3/request.rs` around lines 34 - 37, The close
method in request.rs is calling self.connect.ok(status) which only sends the
response but doesn't finish the send stream; change it to delegate to
self.connect.close(status) so the Connecting::close implementation (which calls
connect.send.finish()) runs; update the call in pub async fn close(self, status:
http::StatusCode) -> Result<(), ServerError> to call
self.connect.close(status).await? and propagate the Result as before, ensuring
the await and error type match the existing method signature.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web-transport-quiche/src/client.rs (1)

90-102: ⚠️ Potential issue | 🟠 Major

ConnectRequest fields (e.g. protocols) are silently discarded.

Line 92 moves only url out of the ConnectRequest, and the remaining fields are dropped. Connection::connect (line 147 of connection.rs) accepts url: Url, so any extra request metadata (like protocols) never reaches the server.

Compare with the other Client::connect in lines 216–258 of this same file, which passes the full request through to Session::connect(conn, request), preserving all fields.

Connection::connect should accept impl Into<ConnectRequest> (or ConnectRequest directly) and forward it to h3::Connected::open, just like the Session path does.

Proposed fix sketch

In connection.rs, update connect to take the full request:

-    pub async fn connect(conn: ez::Connection, url: Url) -> Result<Connection, ClientError> {
+    pub async fn connect(conn: ez::Connection, request: impl Into<ConnectRequest>) -> Result<Connection, ClientError> {
         let settings = h3::Settings::connect(&conn).await?;
-        let connect = h3::Connected::open(&conn, url).await?;
+        let connect = h3::Connected::open(&conn, request).await?;
         let session = Connection::new(conn, settings, connect);
         Ok(session)
     }

Then in ClientBuilder::connect:

-        let url = request.url;
-        let port = url.port().unwrap_or(443);
+        let port = request.url.port().unwrap_or(443);
 
-        let host = match url.host() {
-            Some(host) => host.to_string(),
-            None => return Err(ClientError::InvalidUrl(url.to_string())),
+        let host = match request.url.host() {
+            Some(host) => host.to_string(),
+            None => return Err(ClientError::InvalidUrl(request.url.to_string())),
         };
 
         let conn = self.0.connect(&host, port).await?;
 
-        Connection::connect(conn, url).await
+        Connection::connect(conn, request).await
web-transport-quiche/src/connection.rs (1)

147-152: ⚠️ Potential issue | 🟠 Major

connect still accepts Url, losing non-URL request fields.

This is the root cause of the issue flagged in client.rs. h3::Connected::open already accepts impl Into<ConnectRequest>, so this method can be updated to forward the full request. This would also let you remove the use url::Url import on line 13 if no other usage remains.

Proposed fix
-    pub async fn connect(conn: ez::Connection, url: Url) -> Result<Connection, ClientError> {
+    pub async fn connect(conn: ez::Connection, request: impl Into<ConnectRequest>) -> Result<Connection, ClientError> {
         // Perform the H3 handshake by sending/reciving SETTINGS frames.
         let settings = h3::Settings::connect(&conn).await?;
 
         // Send the HTTP/3 CONNECT request.
-        let connect = h3::Connected::open(&conn, url).await?;
+        let connect = h3::Connected::open(&conn, request).await?;
 
         // Return the resulting session with a reference to the control/connect streams.
         // If either stream is closed, then the session will be closed, so we need to keep them around.
         let session = Connection::new(conn, settings, connect);
 
         Ok(session)
     }

@kixelated kixelated enabled auto-merge (squash) February 11, 2026 18:38
@kixelated kixelated merged commit f2f8e9f into main Feb 11, 2026
1 check passed
@kixelated kixelated deleted the async-accept branch February 11, 2026 18:44
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web-transport-quiche/src/h3/connect.rs (1)

109-142: ⚠️ Potential issue | 🟡 Minor

Missing response protocol validation in Connected::open (client-side).

The quiche client-side open only validates that the response status is 200 OK (line 132), but does not check whether the server's response.protocol was in the client's original request.protocols. The quinn counterpart (web-transport-quinn/src/connect.rs, lines 135-140) performs this check and returns ProtocolMismatch if violated.

This could allow a quiche client to silently accept a server response advertising a protocol it never requested.

🛡️ Proposed fix — add protocol validation
         // Throw an error if we didn't get a 200 OK.
         if response.status != http::StatusCode::OK {
             return Err(ConnectError::Status(response.status));
         }

+        // Validate that the server's protocol was in our request.
+        // TODO: Add ProtocolMismatch variant to ConnectError if needed.
+
         Ok(Self {
web-transport-quinn/src/connect.rs (1)

58-81: ⚠️ Potential issue | 🟡 Minor

Quinn validates protocol membership before responding; quiche does not — inconsistent implementations.

The quinn Connecting::respond (lines 66–70) validates that the response protocol is in the client's requested protocols before sending the response. The quiche counterpart (web-transport-quiche/src/h3/connect.rs, lines 65–79) omits this validation entirely, potentially allowing a quiche server to respond with a protocol the client did not request.

🤖 Fix all issues with AI agents
In `@web-transport-quiche/src/h3/request.rs`:
- Around line 41-45: Update the doc comment for the reject method in request.rs
to fix the typo "returing" → "returning" so the comment reads "Reject the
session, returning your favorite HTTP status code."; locate the doc on the pub
async fn reject(self, status: http::StatusCode) -> Result<(), ServerError>
method and correct the misspelling.
🧹 Nitpick comments (1)
web-transport-quiche/src/h3/connect.rs (1)

82-86: reject propagates finish() error, while quinn's equivalent ignores it.

Line 84: connect.send.finish()? propagates the error, whereas the quinn counterpart uses connect.send.finish().ok() to silently discard it. Both approaches are defensible, but the inconsistency may be worth noting — if the intent is to best-effort close the stream during rejection, ignoring the error (as quinn does) may be more resilient.

Comment on lines 41 to 45
/// Reject the session, returing your favorite HTTP status code.
pub async fn close(mut self, status: http::StatusCode) -> Result<(), ServerError> {
self.connect.respond(status).await?;
pub async fn reject(self, status: http::StatusCode) -> Result<(), ServerError> {
self.connect.reject(status).await?;
Ok(())
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo: "returing" → "returning".

Line 41 doc comment has a typo.

📝 Proposed fix
-    /// Reject the session, returing your favorite HTTP status code.
+    /// Reject the session, returning your favorite HTTP status code.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Reject the session, returing your favorite HTTP status code.
pub async fn close(mut self, status: http::StatusCode) -> Result<(), ServerError> {
self.connect.respond(status).await?;
pub async fn reject(self, status: http::StatusCode) -> Result<(), ServerError> {
self.connect.reject(status).await?;
Ok(())
}
/// Reject the session, returning your favorite HTTP status code.
pub async fn reject(self, status: http::StatusCode) -> Result<(), ServerError> {
self.connect.reject(status).await?;
Ok(())
}
🤖 Prompt for AI Agents
In `@web-transport-quiche/src/h3/request.rs` around lines 41 - 45, Update the doc
comment for the reject method in request.rs to fix the typo "returing" →
"returning" so the comment reads "Reject the session, returning your favorite
HTTP status code."; locate the doc on the pub async fn reject(self, status:
http::StatusCode) -> Result<(), ServerError> method and correct the misspelling.

This was referenced Feb 11, 2026
This was referenced Feb 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant