-
Notifications
You must be signed in to change notification settings - Fork 39
Initial web-transport-quiche support #118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
764a98d
f900a79
f32b3b6
74fa8a8
4e58051
a2520c8
6a24905
5169f5a
78151ee
33f7ae1
78e0c21
b9bacd4
031c93f
bf1d141
bb5de09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| // @ts-expect-error embed the certificate fingerprint using bundler | ||
| import fingerprintHex from "bundle-text:../dev/localhost.hex"; | ||
|
|
||
| // Convert the hex to binary. | ||
| const fingerprint = []; | ||
| for (let c = 0; c < fingerprintHex.length - 1; c += 2) { | ||
| fingerprint.push(parseInt(fingerprintHex.substring(c, c + 2), 16)); | ||
| } | ||
|
|
||
| const params = new URLSearchParams(window.location.search); | ||
|
|
||
| const url = params.get("url") || "https://localhost:4443"; | ||
| const datagram = params.get("datagram") || false; | ||
|
|
||
| function log(msg) { | ||
| const element = document.createElement("div"); | ||
| element.innerText = msg; | ||
|
|
||
| document.body.appendChild(element); | ||
| } | ||
|
|
||
| async function run() { | ||
| // Connect using the hex fingerprint in the cert folder. | ||
| const transport = new WebTransport(url, { | ||
| serverCertificateHashes: [ | ||
| { | ||
| algorithm: "sha-256", | ||
| value: new Uint8Array(fingerprint), | ||
| }, | ||
| ], | ||
| }); | ||
| await transport.ready; | ||
|
|
||
| log("connected"); | ||
|
|
||
| let writer; | ||
| let reader; | ||
|
|
||
| if (!datagram) { | ||
| // Create a bidirectional stream | ||
| const stream = await transport.createBidirectionalStream(); | ||
| log("created stream"); | ||
|
|
||
| writer = stream.writable.getWriter(); | ||
| reader = stream.readable.getReader(); | ||
| } else { | ||
| log("using datagram"); | ||
|
|
||
| // Create a datagram | ||
| writer = transport.datagrams.writable.getWriter(); | ||
| reader = transport.datagrams.readable.getReader(); | ||
| } | ||
|
|
||
| // Create a message | ||
| const msg = "Hello, world!"; | ||
| const encoded = new TextEncoder().encode(msg); | ||
|
|
||
| await writer.write(encoded); | ||
| await writer.close(); | ||
| writer.releaseLock(); | ||
|
|
||
| log("send: " + msg); | ||
|
|
||
| // Read a message from it | ||
| // TODO handle partial reads | ||
| const { value } = await reader.read(); | ||
|
|
||
| const recv = new TextDecoder().decode(value); | ||
| log("recv: " + recv); | ||
|
|
||
| transport.close(); | ||
| log("closed"); | ||
| } | ||
|
|
||
| run(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| use bytes::{Buf, BufMut, Bytes}; | ||
| use bytes::{Buf, BufMut, Bytes, BytesMut}; | ||
| use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; | ||
|
|
||
| use crate::{VarInt, VarIntUnexpectedEnd}; | ||
|
|
||
|
|
@@ -68,6 +69,22 @@ impl Capsule { | |
| } | ||
| } | ||
|
|
||
| pub async fn read<S: AsyncRead + Unpin>(stream: &mut S) -> Result<Self, CapsuleError> { | ||
| let mut buf = Vec::new(); | ||
| loop { | ||
| stream | ||
| .read_buf(&mut buf) | ||
| .await | ||
| .map_err(|_| CapsuleError::UnexpectedEnd)?; | ||
| let mut limit = std::io::Cursor::new(&buf); | ||
| match Self::decode(&mut limit) { | ||
| Ok(capsule) => return Ok(capsule), | ||
| Err(CapsuleError::UnexpectedEnd) => continue, | ||
| Err(e) => return Err(e), | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
74
to
88
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider limiting buffer growth to prevent memory exhaustion. The Consider adding a maximum buffer size check: pub async fn read<S: AsyncRead + Unpin>(stream: &mut S) -> Result<Self, CapsuleError> {
let mut buf = Vec::new();
loop {
+ if buf.len() > MAX_MESSAGE_SIZE {
+ return Err(CapsuleError::MessageTooLong);
+ }
stream
.read_buf(&mut buf)
.awaitNote: This pattern appears in other files (connect.rs, settings.rs) and should be addressed consistently across the codebase.
🤖 Prompt for AI Agents |
||
|
|
||
| pub fn encode<B: BufMut>(&self, buf: &mut B) { | ||
| match self { | ||
| Self::CloseWebTransportSession { | ||
|
|
@@ -101,6 +118,16 @@ impl Capsule { | |
| } | ||
| } | ||
| } | ||
|
|
||
| pub async fn write<S: AsyncWrite + Unpin>(&self, stream: &mut S) -> Result<(), CapsuleError> { | ||
| let mut buf = BytesMut::new(); | ||
| self.encode(&mut buf); | ||
| stream | ||
| .write_all_buf(&mut buf) | ||
| .await | ||
| .map_err(|_| CapsuleError::UnexpectedEnd)?; | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| fn is_grease(val: u64) -> bool { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| use std::str::FromStr; | ||
|
|
||
| use bytes::{Buf, BufMut}; | ||
| use bytes::{Buf, BufMut, BytesMut}; | ||
| use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; | ||
| use url::Url; | ||
|
|
||
| use super::{qpack, Frame, VarInt}; | ||
|
|
@@ -97,6 +98,22 @@ impl ConnectRequest { | |
| Ok(Self { url }) | ||
| } | ||
|
|
||
| pub async fn read<S: AsyncRead + Unpin>(stream: &mut S) -> Result<Self, ConnectError> { | ||
| let mut buf = Vec::new(); | ||
| loop { | ||
| stream | ||
| .read_buf(&mut buf) | ||
| .await | ||
| .map_err(|_| ConnectError::UnexpectedEnd)?; | ||
| let mut limit = std::io::Cursor::new(&buf); | ||
| match Self::decode(&mut limit) { | ||
| Ok(request) => return Ok(request), | ||
| Err(ConnectError::UnexpectedEnd) => continue, | ||
| Err(e) => return Err(e), | ||
| } | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| pub fn encode<B: BufMut>(&self, buf: &mut B) { | ||
| let mut headers = qpack::Headers::default(); | ||
| headers.set(":method", "CONNECT"); | ||
|
|
@@ -118,6 +135,16 @@ impl ConnectRequest { | |
| size.encode(buf); | ||
| buf.put_slice(&tmp); | ||
| } | ||
|
|
||
| pub async fn write<S: AsyncWrite + Unpin>(&self, stream: &mut S) -> Result<(), ConnectError> { | ||
| let mut buf = BytesMut::new(); | ||
| self.encode(&mut buf); | ||
| stream | ||
| .write_all_buf(&mut buf) | ||
| .await | ||
| .map_err(|_| ConnectError::UnexpectedEnd)?; | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
|
|
@@ -148,6 +175,22 @@ impl ConnectResponse { | |
| Ok(Self { status }) | ||
| } | ||
|
|
||
| pub async fn read<S: AsyncRead + Unpin>(stream: &mut S) -> Result<Self, ConnectError> { | ||
| let mut buf = Vec::new(); | ||
| loop { | ||
| stream | ||
| .read_buf(&mut buf) | ||
| .await | ||
| .map_err(|_| ConnectError::UnexpectedEnd)?; | ||
| let mut limit = std::io::Cursor::new(&buf); | ||
| match Self::decode(&mut limit) { | ||
| Ok(response) => return Ok(response), | ||
| Err(ConnectError::UnexpectedEnd) => continue, | ||
| Err(e) => return Err(e), | ||
| } | ||
| } | ||
|
||
| } | ||
|
|
||
| pub fn encode<B: BufMut>(&self, buf: &mut B) { | ||
| let mut headers = qpack::Headers::default(); | ||
| headers.set(":status", self.status.as_str()); | ||
|
|
@@ -162,4 +205,14 @@ impl ConnectResponse { | |
| size.encode(buf); | ||
| buf.put_slice(&tmp); | ||
| } | ||
|
|
||
| pub async fn write<S: AsyncWrite + Unpin>(&self, stream: &mut S) -> Result<(), ConnectError> { | ||
| let mut buf = BytesMut::new(); | ||
| self.encode(&mut buf); | ||
| stream | ||
| .write_all_buf(&mut buf) | ||
| .await | ||
| .map_err(|_| ConnectError::UnexpectedEnd)?; | ||
| Ok(()) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,10 @@ use std::{ | |
| ops::{Deref, DerefMut}, | ||
| }; | ||
|
|
||
| use bytes::{Buf, BufMut}; | ||
| use bytes::{Buf, BufMut, BytesMut}; | ||
|
|
||
| use thiserror::Error; | ||
| use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; | ||
|
|
||
| use super::{Frame, StreamUni, VarInt, VarIntUnexpectedEnd}; | ||
|
|
||
|
|
@@ -96,6 +97,9 @@ pub enum SettingsError { | |
|
|
||
| #[error("invalid size")] | ||
| InvalidSize, | ||
|
|
||
| #[error("unsupported")] | ||
| Unsupported, | ||
| } | ||
|
|
||
| // A map of settings to values. | ||
|
|
@@ -128,11 +132,32 @@ impl Settings { | |
| Ok(settings) | ||
| } | ||
|
|
||
| pub async fn read<S: AsyncRead + Unpin>(stream: &mut S) -> Result<Self, SettingsError> { | ||
| let mut buf = Vec::new(); | ||
|
|
||
| loop { | ||
| stream | ||
| .read_buf(&mut buf) | ||
| .await | ||
| .map_err(|_| SettingsError::UnexpectedEnd)?; | ||
|
|
||
| // Look at the buffer we've already read. | ||
| let mut limit = std::io::Cursor::new(&buf); | ||
|
|
||
| match Settings::decode(&mut limit) { | ||
| Ok(settings) => return Ok(settings), | ||
| Err(SettingsError::UnexpectedEnd) => continue, // More data needed. | ||
| Err(e) => return Err(e), | ||
| }; | ||
| } | ||
| } | ||
|
Comment on lines
142
to
159
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider limiting buffer growth to prevent memory exhaustion. Similar to the issue in capsule.rs, the Consider adding a buffer size limit before the read operation or after accumulating data. 🤖 Prompt for AI Agents |
||
|
|
||
| pub fn encode<B: BufMut>(&self, buf: &mut B) { | ||
| StreamUni::CONTROL.encode(buf); | ||
| Frame::SETTINGS.encode(buf); | ||
|
|
||
| // Encode to a temporary buffer so we can learn the length. | ||
| // TODO avoid doing this, just use a fixed size varint. | ||
| let mut tmp = Vec::new(); | ||
| for (id, value) in &self.0 { | ||
| id.encode(&mut tmp); | ||
|
|
@@ -143,6 +168,17 @@ impl Settings { | |
| buf.put_slice(&tmp); | ||
| } | ||
|
|
||
| pub async fn write<S: AsyncWrite + Unpin>(&self, stream: &mut S) -> Result<(), SettingsError> { | ||
| // TODO avoid allocating to the heap | ||
| let mut buf = BytesMut::new(); | ||
| self.encode(&mut buf); | ||
| stream | ||
| .write_all_buf(&mut buf) | ||
| .await | ||
| .map_err(|_| SettingsError::UnexpectedEnd)?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| pub fn enable_webtransport(&mut self, max_sessions: u32) { | ||
| let max = VarInt::from_u32(max_sessions); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix datagram parameter parsing logic.
Line 13 has a logic error:
params.get("datagram")returns either a string ornull, so|| falsewill evaluate to the string value (e.g.,"false") rather than a boolean. Any non-empty string (including"false") is truthy in JavaScript.Apply this fix:
📝 Committable suggestion
🤖 Prompt for AI Agents