1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-21 04:52:26 -05:00

Compare commits

...

2 commits

Author SHA1 Message Date
snek
02a363c6f0
Merge 2b1fb7dc86 into 342ccbb99d 2025-01-17 15:39:16 +01:00
snek
2b1fb7dc86
feat(unstable): WebTransport 2025-01-15 09:43:52 +01:00
18 changed files with 1935 additions and 17 deletions

15
Cargo.lock generated
View file

@ -2001,9 +2001,12 @@ dependencies = [
"quinn",
"rustls-tokio-stream",
"serde",
"sha2",
"socket2",
"thiserror 2.0.3",
"tokio",
"url",
"web-transport-proto",
]
[[package]]
@ -8836,6 +8839,18 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web-transport-proto"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a3806ea43df5817f0d90618c842d28db5946bc18a5db0659b2275c2be48d472"
dependencies = [
"bytes",
"http 1.1.0",
"thiserror 1.0.64",
"url",
]
[[package]]
name = "webpki-root-certs"
version = "0.26.6"

View file

@ -1481,9 +1481,15 @@ delete Object.prototype.__proto__;
options: SNAPSHOT_COMPILE_OPTIONS,
host,
});
const errors = ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM);
assert(
ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM).length === 0,
"lib.d.ts files have errors",
errors.length === 0,
`lib.d.ts files have errors:\n${
ts.formatDiagnosticsWithColorAndContext(
errors,
host,
)
}`,
);
// remove this now that we don't need it anymore for warming up tsc

View file

@ -34,6 +34,8 @@ import {
op_quic_send_stream_get_id,
op_quic_send_stream_get_priority,
op_quic_send_stream_set_priority,
op_webtransport_accept,
op_webtransport_connect,
} from "ext:core/ops";
import {
getReadableStreamResourceBacking,
@ -50,6 +52,7 @@ const {
const {
ObjectPrototypeIsPrototypeOf,
PromisePrototypeThen,
ReflectConstruct,
Symbol,
SymbolAsyncIterator,
SafePromisePrototypeFinally,
@ -205,6 +208,9 @@ class QuicIncoming {
}
}
let webtransportConnect;
let webtransportAccept;
class QuicConn {
#resource;
#bidiStream = null;
@ -309,6 +315,43 @@ class QuicConn {
close({ closeCode = 0, reason = "" } = { __proto__: null }) {
op_quic_connection_close(this.#resource, closeCode, reason);
}
static {
webtransportConnect = async function webtransportConnect(conn, url) {
const {
0: connectTxRid,
1: connectRxRid,
2: settingsTxRid,
3: settingsRxRid,
} = await op_webtransport_connect(conn.#resource, url);
const connect = new QuicBidirectionalStream(
connectTxRid,
connectRxRid,
conn.closed,
);
const settingsTx = writableStream(settingsTxRid, conn.closed);
const settingsRx = readableStream(settingsRxRid, conn.closed);
return { connect, settingsTx, settingsRx };
};
webtransportAccept = async function webtransportAccept(conn) {
const {
0: url,
1: connectTxRid,
2: connectRxRid,
3: settingsTxRid,
4: settingsRxRid,
} = await op_webtransport_accept(conn.#resource);
const connect = new QuicBidirectionalStream(
connectTxRid,
connectRxRid,
conn.closed,
);
const settingsTx = writableStream(settingsTxRid, conn.closed);
const settingsRx = readableStream(settingsRxRid, conn.closed);
return { url, connect, settingsTx, settingsRx };
};
}
}
class QuicSendStream extends WritableStream {
@ -345,7 +388,11 @@ function readableStream(rid, closed) {
SafePromisePrototypeFinally(closed, () => {
core.tryClose(rid);
});
return readableStreamForRid(rid, true, QuicReceiveStream);
return readableStreamForRid(
rid,
true,
(...args) => ReflectConstruct(QuicReceiveStream, args),
);
}
function writableStream(rid, closed) {
@ -353,7 +400,11 @@ function writableStream(rid, closed) {
SafePromisePrototypeFinally(closed, () => {
core.tryClose(rid);
});
return writableStreamForRid(rid, true, QuicSendStream);
return writableStreamForRid(
rid,
true,
(...args) => ReflectConstruct(QuicSendStream, args),
);
}
class QuicBidirectionalStream {
@ -421,6 +472,7 @@ function connectQuic(options) {
caCerts: options.caCerts,
alpnProtocols: options.alpnProtocols,
serverName: options.serverName,
serverCertificateHashes: options.serverCertificateHashes,
},
transportOptions(options),
keyPair,
@ -448,4 +500,6 @@ export {
QuicListener,
QuicReceiveStream,
QuicSendStream,
webtransportAccept,
webtransportConnect,
};

View file

@ -24,6 +24,9 @@ pin-project.workspace = true
quinn = { version = "0.11.6", default-features = false, features = ["runtime-tokio", "rustls", "ring"] }
rustls-tokio-stream.workspace = true
serde.workspace = true
sha2.workspace = true
socket2.workspace = true
thiserror.workspace = true
tokio.workspace = true
url.workspace = true
web-transport-proto = "0.2.3"

View file

@ -196,6 +196,8 @@ deno_core::extension!(deno_net,
quic::op_quic_send_stream_get_id,
quic::op_quic_send_stream_get_priority,
quic::op_quic_send_stream_set_priority,
quic::webtransport::op_webtransport_accept,
quic::webtransport::op_webtransport_connect,
],
esm = [ "01_net.js", "02_tls.js" ],
lazy_loaded_esm = [ "03_quic.js" ],

View file

@ -93,6 +93,12 @@ pub enum QuicError {
#[class("BadResource")]
#[error("{0}")]
ClosedStream(#[from] quinn::ClosedStream),
#[class(generic)]
#[error("{0}")]
ReadError(#[from] quinn::ReadError),
#[class(generic)]
#[error("{0}")]
WriteError(#[from] quinn::WriteError),
#[class("BadResource")]
#[error("Invalid {0} resource")]
BadResource(&'static str),
@ -102,6 +108,15 @@ pub enum QuicError {
#[class(generic)]
#[error("{0}")]
Core(#[from] deno_core::error::AnyError),
#[class(generic)]
#[error("Peer does not support WebTransport")]
WebTransportPeerUnsupported,
#[class(generic)]
#[error("{0}")]
WebTransportSettingsError(#[from] web_transport_proto::SettingsError),
#[class(generic)]
#[error("{0}")]
WebTransportConnectError(#[from] web_transport_proto::ConnectError),
#[class(inherit)]
#[error(transparent)]
Other(#[from] JsErrorBox),
@ -478,6 +493,14 @@ struct ConnectArgs {
ca_certs: Option<Vec<String>>,
alpn_protocols: Option<Vec<String>>,
server_name: Option<String>,
server_certificate_hashes: Option<Vec<CertificateHash>>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct CertificateHash {
algorithm: String,
value: JsBuffer,
}
#[op2]
@ -518,13 +541,28 @@ where
.map(|s| s.into_bytes())
.collect::<Vec<_>>();
let mut tls_config = create_client_config(
root_cert_store,
ca_certs,
unsafely_ignore_certificate_errors,
key_pair.take(),
SocketUse::GeneralSsl,
)?;
let mut tls_config = if let Some(hashes) = args.server_certificate_hashes {
deno_tls::rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(
webtransport::ServerFingerprints::new(
hashes
.into_iter()
.filter(|h| h.algorithm.to_lowercase() == "sha-256")
.map(|h| h.value.to_vec())
.collect(),
),
))
.with_no_client_auth()
} else {
create_client_config(
root_cert_store,
ca_certs,
unsafely_ignore_certificate_errors,
key_pair.take(),
SocketUse::GeneralSsl,
)?
};
if let Some(alpn_protocols) = args.alpn_protocols {
tls_config.alpn_protocols =
@ -928,3 +966,305 @@ pub(crate) fn op_quic_recv_stream_get_id(
let stream_id = quinn::VarInt::from(resource.stream_id).into_inner();
Ok(stream_id)
}
pub(crate) mod webtransport {
// MIT License
//
// Copyright (c) 2023 Luke Curley
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://github.com/kixelated/web-transport-rs
use deno_core::futures::try_join;
use deno_tls::rustls;
use rustls::client::danger::ServerCertVerifier;
use rustls::crypto::verify_tls12_signature;
use rustls::crypto::verify_tls13_signature;
use rustls::crypto::CryptoProvider;
use sha2::Digest;
use sha2::Sha256;
use super::*;
async fn exchange_settings(
state: Rc<RefCell<OpState>>,
conn: quinn::Connection,
) -> Result<(u32, u32), QuicError> {
use web_transport_proto::Settings;
use web_transport_proto::SettingsError;
let settings_tx_rid = async {
let mut tx = conn.open_uni().await?;
let mut settings = Settings::default();
settings.enable_webtransport(1);
let mut buf = vec![];
settings.encode(&mut buf);
tx.write_all(&buf).await?;
let rid = state
.borrow_mut()
.resource_table
.add(SendStreamResource::new(tx));
Ok(rid)
};
let settings_rx_rid = async {
let mut rx = conn.accept_uni().await?;
let mut buf = Vec::new();
loop {
let chunk = rx.read_chunk(usize::MAX, true).await?;
let chunk = chunk.ok_or(QuicError::WebTransportPeerUnsupported)?;
buf.extend_from_slice(&chunk.bytes);
let mut limit = std::io::Cursor::new(&buf);
let settings = match Settings::decode(&mut limit) {
Ok(settings) => settings,
Err(SettingsError::UnexpectedEnd) => continue,
Err(e) => return Err(e.into()),
};
if settings.supports_webtransport() == 0 {
return Err(QuicError::WebTransportPeerUnsupported);
}
break;
}
let rid = state
.borrow_mut()
.resource_table
.add(RecvStreamResource::new(rx));
Ok(rid)
};
let (settings_tx_rid, settings_rx_rid) =
try_join!(settings_tx_rid, settings_rx_rid)?;
Ok((settings_tx_rid, settings_rx_rid))
}
#[op2(async)]
#[serde]
pub(crate) async fn op_webtransport_connect(
state: Rc<RefCell<OpState>>,
#[cppgc] connection_resource: &ConnectionResource,
#[string] url: String,
) -> Result<(u32, u32, u32, u32), QuicError> {
use web_transport_proto::ConnectError;
use web_transport_proto::ConnectRequest;
use web_transport_proto::ConnectResponse;
let conn = connection_resource.0.clone();
let url = url::Url::parse(&url).unwrap();
let (settings_tx_rid, settings_rx_rid) =
exchange_settings(state.clone(), conn.clone()).await?;
let (connect_tx_rid, connect_rx_rid) = {
let (mut tx, mut rx) = conn.open_bi().await?;
let request = ConnectRequest { url: url.clone() };
let mut buf = Vec::new();
request.encode(&mut buf);
tx.write_all(&buf).await?;
buf.clear();
loop {
let chunk = rx.read_chunk(usize::MAX, true).await?;
let chunk = chunk.ok_or(QuicError::WebTransportPeerUnsupported)?;
buf.extend_from_slice(&chunk.bytes);
let mut limit = std::io::Cursor::new(&buf);
let res = match ConnectResponse::decode(&mut limit) {
Ok(res) => res,
Err(ConnectError::UnexpectedEnd) => {
continue;
}
Err(e) => return Err(e.into()),
};
if res.status != 200 {
return Err(QuicError::WebTransportPeerUnsupported);
}
break;
}
let mut state = state.borrow_mut();
let tx_rid = state.resource_table.add(SendStreamResource::new(tx));
let rx_rid = state.resource_table.add(RecvStreamResource::new(rx));
(tx_rid, rx_rid)
};
Ok((
connect_tx_rid,
connect_rx_rid,
settings_tx_rid,
settings_rx_rid,
))
}
#[op2(async)]
#[serde]
pub(crate) async fn op_webtransport_accept(
state: Rc<RefCell<OpState>>,
#[cppgc] connection_resource: &ConnectionResource,
) -> Result<(String, u32, u32, u32, u32), QuicError> {
use web_transport_proto::ConnectError;
use web_transport_proto::ConnectRequest;
use web_transport_proto::ConnectResponse;
let conn = connection_resource.0.clone();
let (settings_tx_rid, settings_rx_rid) =
exchange_settings(state.clone(), conn.clone()).await?;
let (url, connect_tx_rid, connect_rx_rid) = {
let (mut tx, mut rx) = conn.accept_bi().await?;
let mut buf = Vec::new();
let req = loop {
let chunk = rx.read_chunk(usize::MAX, true).await?;
let chunk = chunk.ok_or(QuicError::WebTransportPeerUnsupported)?;
buf.extend_from_slice(&chunk.bytes);
let mut limit = std::io::Cursor::new(&buf);
let req = match ConnectRequest::decode(&mut limit) {
Ok(res) => res,
Err(ConnectError::UnexpectedEnd) => {
continue;
}
Err(e) => return Err(e.into()),
};
break req;
};
buf.clear();
let resp = ConnectResponse {
status: 200u16.try_into().unwrap(),
};
resp.encode(&mut buf);
tx.write_all(&buf).await?;
let mut state = state.borrow_mut();
let tx_rid = state.resource_table.add(SendStreamResource::new(tx));
let rx_rid = state.resource_table.add(RecvStreamResource::new(rx));
(req.url, tx_rid, rx_rid)
};
Ok((
url.to_string(),
connect_tx_rid,
connect_rx_rid,
settings_tx_rid,
settings_rx_rid,
))
}
#[derive(Debug)]
pub(crate) struct ServerFingerprints {
fingerprints: Vec<Vec<u8>>,
provider: CryptoProvider,
}
impl ServerFingerprints {
pub(crate) fn new(fingerprints: Vec<Vec<u8>>) -> ServerFingerprints {
Self {
fingerprints,
provider: rustls::crypto::ring::default_provider(),
}
}
}
impl ServerCertVerifier for ServerFingerprints {
fn verify_server_cert(
&self,
end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
let cert_hash = Sha256::digest(end_entity);
if self
.fingerprints
.iter()
.any(|fingerprint| fingerprint == cert_hash.as_slice())
{
return Ok(rustls::client::danger::ServerCertVerified::assertion());
}
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::UnknownIssuer,
))
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
verify_tls12_signature(
message,
cert,
dss,
&self.provider.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
verify_tls13_signature(
message,
cert,
dss,
&self.provider.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self
.provider
.signature_verification_algorithms
.supported_schemes()
}
}
}

View file

@ -908,8 +908,8 @@ const _original = Symbol("[[original]]");
* @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true.
* @returns {ReadableStream<Uint8Array>}
*/
function readableStreamForRid(rid, autoClose = true, Super, onError) {
const stream = new (Super ?? ReadableStream)(_brand);
function readableStreamForRid(rid, autoClose = true, cfn, onError) {
const stream = cfn ? cfn(_brand) : new ReadableStream(_brand);
stream[_resourceBacking] = { rid, autoClose };
const tryClose = () => {
@ -1134,8 +1134,8 @@ async function readableStreamCollectIntoUint8Array(stream) {
* @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true.
* @returns {ReadableStream<Uint8Array>}
*/
function writableStreamForRid(rid, autoClose = true, Super) {
const stream = new (Super ?? WritableStream)(_brand);
function writableStreamForRid(rid, autoClose = true, cfn) {
const stream = cfn ? cfn(_brand) : new WritableStream(_brand);
stream[_resourceBacking] = { rid, autoClose };
const tryClose = () => {

View file

@ -1378,3 +1378,221 @@ declare var ImageData: {
settings?: ImageDataSettings,
): ImageData;
};
/** @category Platform */
interface WebTransportCloseInfo {
closeCode?: number;
reason?: string;
}
/** @category Platform */
interface WebTransportErrorOptions {
source?: WebTransportErrorSource;
streamErrorCode?: number | null;
}
/** @category Platform */
interface WebTransportHash {
algorithm?: string;
value?: BufferSource;
}
/** @category Platform */
interface WebTransportOptions {
allowPooling?: boolean;
congestionControl?: WebTransportCongestionControl;
requireUnreliable?: boolean;
serverCertificateHashes?: WebTransportHash[];
}
/** @category Platform */
interface WebTransportSendStreamOptions {
sendGroup?: WebTransportSendGroup;
sendOrder?: number;
waitUntilAvailable?: boolean;
}
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport)
* @category Platform
*/
interface WebTransport {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/closed) */
readonly closed: Promise<WebTransportCloseInfo>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/datagrams) */
readonly datagrams: WebTransportDatagramDuplexStream;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/incomingBidirectionalStreams) */
readonly incomingBidirectionalStreams: ReadableStream<
WebTransportBidirectionalStream
>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/incomingUnidirectionalStreams) */
readonly incomingUnidirectionalStreams: ReadableStream<
WebTransportReceiveStream
>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/ready) */
readonly ready: Promise<undefined>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/close) */
close(closeInfo?: WebTransportCloseInfo): void;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/createBidirectionalStream) */
createBidirectionalStream(
options?: WebTransportSendStreamOptions,
): Promise<WebTransportBidirectionalStream>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/createUnidirectionalStream) */
createUnidirectionalStream(
options?: WebTransportSendStreamOptions,
): Promise<WebTransportSendStream>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/createSendGroup) */
createSendGroup(): WebTransportSendGroup;
}
/** @category Platform */
declare var WebTransport: {
prototype: WebTransport;
new (url: string | URL, options?: WebTransportOptions): WebTransport;
};
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportBidirectionalStream)
* @category Platform
*/
interface WebTransportBidirectionalStream {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportBidirectionalStream/readable) */
readonly readable: WebTransportReceiveStream;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportBidirectionalStream/writable) */
readonly writable: WebTransportSendStream;
}
/** @category Platform */
declare var WebTransportBidirectionalStream: {
prototype: WebTransportBidirectionalStream;
new (): WebTransportBidirectionalStream;
};
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream)
* @category Platform
*/
interface WebTransportDatagramDuplexStream {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/incomingHighWaterMark) */
incomingHighWaterMark: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/incomingMaxAge) */
incomingMaxAge: number | null;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/maxDatagramSize) */
readonly maxDatagramSize: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/outgoingHighWaterMark) */
outgoingHighWaterMark: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/outgoingMaxAge) */
outgoingMaxAge: number | null;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/readable) */
readonly readable: WebTransportReceiveStream;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/writable) */
readonly writable: WebTransportSendStream;
}
/** @category Platform */
declare var WebTransportDatagramDuplexStream: {
prototype: WebTransportDatagramDuplexStream;
new (): WebTransportDatagramDuplexStream;
};
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendStream)
* @category Platform
*/
interface WebTransportSendStream extends WritableStream<Uint8Array> {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendStream/sendOrder) */
sendOrder: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendStream/sendGroup) */
sendGroup?: WebTransportSendGroup;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendStream/getStats) */
getStats(): Promise<WebTransportSendStreamStats>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendStream/getWriter) */
getWriter(): WebTransportWriter;
}
/** @category Platform */
declare var WebTransportSendStream: {
prototype: WebTransportSendStream;
new (): WebTransportSendStream;
};
/** @category Platform */
interface WebTransportSendStreamStats {
bytesWritten: number;
bytesSent: number;
bytesAcknowledged: number;
}
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportWriter)
* @category Platform
*/
interface WebTransportWriter extends WritableStreamDefaultWriter<Uint8Array> {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportWriter/atomicWrite) */
atomicWrite(chunk: any): Promise<undefined>;
}
/** @category Platform */
declare var WebTransportWriter: {
prototype: WebTransportWriter;
new (): WebTransportWriter;
};
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportReceiveStream)
* @category Platform
*/
interface WebTransportReceiveStream extends ReadableStream<Uint8Array> {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportReceiveStream/getStats) */
getStats(): Promise<WebTransportReceiveStreamStats>;
}
/** @category Platform */
declare var WebTransportReceiveStream: {
prototype: WebTransportReceiveStream;
new (): WebTransportReceiveStream;
};
/** @category Platform */
interface WebTransportReceiveStreamStats {
bytesReceived: number;
bytesRead: number;
}
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendGroup)
* @category Platform
*/
interface WebTransportSendGroup {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendGroup/getStats) */
getStats(): Promise<WebTransportSendStreamStats>;
}
/** @category Platform */
declare var WebTransportSendGroup: {
prototype: WebTransportSendGroup;
new (): WebTransportSendGroup;
};
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportError)
* @category Platform
*/
interface WebTransportError extends DOMException {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportError/source) */
readonly source: WebTransportErrorSource;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportError/streamErrorCode) */
readonly streamErrorCode: number | null;
}
/** @category Platform */
declare var WebTransportError: {
prototype: WebTransportError;
new (message?: string, options?: WebTransportErrorOptions): WebTransportError;
};
/** @category Platform */
type WebTransportCongestionControl = "default" | "low-latency" | "throughput";
/** @category Platform */
type WebTransportErrorSource = "session" | "stream";

View file

@ -113,6 +113,7 @@ deno_core::extension!(deno_web,
"15_performance.js",
"16_image_data.js",
],
lazy_loaded_esm = [ "webtransport.js" ],
options = {
blob_store: Arc<BlobStore>,
maybe_location: Option<Url>,

1125
ext/web/webtransport.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -324,9 +324,15 @@ converters.short = createIntegerConversion(16, { unsigned: false });
converters["unsigned short"] = createIntegerConversion(16, {
unsigned: true,
});
converters["unsigned short?"] = createNullableConverter(
converters["unsigned short"],
);
converters.long = createIntegerConversion(32, { unsigned: false });
converters["unsigned long"] = createIntegerConversion(32, { unsigned: true });
converters["unsigned long?"] = createNullableConverter(
converters["unsigned long"],
);
converters["long long"] = createLongLongConversion(64, { unsigned: false });
converters["unsigned long long"] = createLongLongConversion(64, {
@ -398,6 +404,10 @@ converters["unrestricted double"] = (V, _prefix, _context, _opts) => {
return x;
};
converters["unrestricted double?"] = createNullableConverter(
converters["unrestricted double"],
);
converters.DOMString = function (
V,
prefix,

View file

@ -34,6 +34,7 @@ import * as telemetry from "ext:deno_telemetry/telemetry.ts";
const { ObjectDefineProperties } = primordials;
const loadQuic = core.createLazyLoader("ext:deno_net/03_quic.js");
const loadWebTransport = core.createLazyLoader("ext:deno_web/webtransport.js");
const denoNs = {
Process: process.Process,
@ -198,6 +199,10 @@ ObjectDefineProperties(denoNsUnstableById[unstableIds.net], {
loadQuic,
),
QuicIncoming: core.propWritableLazyLoaded((q) => q.QuicIncoming, loadQuic),
upgradeWebTransport: core.propWritableLazyLoaded(
(wt) => wt.upgradeWebTransport,
loadWebTransport,
),
});
// denoNsUnstableById[unstableIds.unsafeProto] = { __proto__: null }

View file

@ -39,6 +39,7 @@ import * as webgpuSurface from "ext:deno_webgpu/02_surface.js";
import { unstableIds } from "ext:runtime/90_deno_ns.js";
const loadImage = core.createLazyLoader("ext:deno_canvas/01_image.js");
const loadWebTransport = core.createLazyLoader("ext:deno_web/webtransport.js");
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
const windowOrWorkerGlobalScope = {
@ -298,6 +299,34 @@ unstableForWindowOrWorkerGlobalScope[unstableIds.broadcastChannel] = {
unstableForWindowOrWorkerGlobalScope[unstableIds.net] = {
WebSocketStream: core.propNonEnumerable(webSocketStream.WebSocketStream),
WebSocketError: core.propNonEnumerable(webSocketStream.WebSocketError),
WebTransport: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransport,
loadWebTransport,
),
WebTransportBidirectionalStream: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportBidirectionalStream,
loadWebTransport,
),
WebTransportDatagramDuplexStream: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportDatagramDuplexStream,
loadWebTransport,
),
WebTransportReceiveStream: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportReceiveStream,
loadWebTransport,
),
WebTransportSendGroup: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportSendGroup,
loadWebTransport,
),
WebTransportSendStream: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportSendStream,
loadWebTransport,
),
WebTransportError: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportError,
loadWebTransport,
),
};
unstableForWindowOrWorkerGlobalScope[unstableIds.webgpu] = {};

View file

@ -5764,7 +5764,7 @@ fn lsp_jsr_auto_import_completion() {
json!({ "triggerKind": 1 }),
);
assert!(!list.is_incomplete);
assert_eq!(list.items.len(), 268);
assert_eq!(list.items.len(), 276);
let item = list.items.iter().find(|i| i.label == "add").unwrap();
assert_eq!(&item.label, "add");
assert_eq!(
@ -5844,7 +5844,7 @@ fn lsp_jsr_auto_import_completion_import_map() {
json!({ "triggerKind": 1 }),
);
assert!(!list.is_incomplete);
assert_eq!(list.items.len(), 268);
assert_eq!(list.items.len(), 276);
let item = list.items.iter().find(|i| i.label == "add").unwrap();
assert_eq!(&item.label, "add");
assert_eq!(json!(&item.label_details), json!({ "description": "add" }));

View file

@ -0,0 +1,5 @@
{
"args": "run --quiet --unstable-net -A main.ts",
"output": "",
"exitCode": 0
}

View file

@ -0,0 +1,4 @@
{
"lock": false,
"importMap": "../../../../import_map.json"
}

View file

@ -0,0 +1,100 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { decodeBase64 } from "@std/encoding/base64";
import { assertEquals } from "@std/assert";
const cert = Deno.readTextFileSync("../../../testdata/tls/localhost.crt");
const certHash = await crypto.subtle.digest(
"SHA-256",
decodeBase64(cert.split("\n").slice(1, -2).join("")),
);
const server = new Deno.QuicEndpoint({
hostname: "localhost",
port: 0,
});
const listener = server.listen({
cert,
key: Deno.readTextFileSync("../../../testdata/tls/localhost.key"),
alpnProtocols: ["h3"],
});
(async () => {
for await (const conn of listener) {
const wt = await Deno.upgradeWebTransport(conn);
assertEquals(wt.url, `https://localhost:${server.addr.port}/path`);
wt.ready.then(() => {
(async () => {
for await (const bidi of wt.incomingBidirectionalStreams) {
bidi.readable.pipeTo(bidi.writable).catch(() => {});
}
})();
(async () => {
for await (const stream of wt.incomingUnidirectionalStreams) {
const out = await wt.createUnidirectionalStream();
stream.pipeTo(out).catch(() => {});
}
})();
wt.datagrams.readable.pipeTo(wt.datagrams.writable);
});
}
})();
const client = new WebTransport(`https://localhost:${server.addr.port}/path`, {
serverCertificateHashes: [{
algorithm: "sha-256",
value: certHash,
}],
});
client.ready.then(async () => {
const bi = await client.createBidirectionalStream();
{
const writer = bi.writable.getWriter();
await writer.write(new Uint8Array([1, 0, 1, 0]));
writer.releaseLock();
}
{
const reader = bi.readable.getReader();
assertEquals(await reader.read(), {
value: new Uint8Array([1, 0, 1, 0]),
done: false,
});
reader.releaseLock();
}
{
const uni = await client.createUnidirectionalStream();
const writer = uni.getWriter();
await writer.write(new Uint8Array([0, 2, 0, 2]));
writer.releaseLock();
}
{
const uni =
(await client.incomingUnidirectionalStreams.getReader().read()).value;
const reader = uni.getReader();
assertEquals(await reader.read(), {
value: new Uint8Array([0, 2, 0, 2]),
done: false,
});
reader.releaseLock();
}
await client.datagrams.writable.getWriter().write(
new Uint8Array([3, 0, 3, 0]),
);
assertEquals(await client.datagrams.readable.getReader().read(), {
value: new Uint8Array([3, 0, 3, 0]),
done: false,
});
client.close();
server.close();
});

View file

@ -233,6 +233,7 @@
"ext:deno_web/14_compression.js": "../ext/web/14_compression.js",
"ext:deno_web/15_performance.js": "../ext/web/15_performance.js",
"ext:deno_web/16_image_data.js": "../ext/web/16_image_data.js",
"ext:deno_web/webtransport.js": "../ext/web/webtransport.js",
"ext:deno_webidl/00_webidl.js": "../ext/webidl/00_webidl.js",
"ext:deno_websocket/01_websocket.js": "../ext/websocket/01_websocket.js",
"ext:deno_websocket/02_websocketstream.js": "../ext/websocket/02_websocketstream.js",