mirror of
https://github.com/denoland/deno.git
synced 2025-01-20 20:42:19 -05:00
Compare commits
2 commits
1a369707bc
...
922069fe6c
Author | SHA1 | Date | |
---|---|---|---|
|
922069fe6c | ||
|
ad87d6f4b0 |
18 changed files with 1935 additions and 17 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -2013,9 +2013,12 @@ dependencies = [
|
|||
"quinn",
|
||||
"rustls-tokio-stream",
|
||||
"serde",
|
||||
"sha2",
|
||||
"socket2",
|
||||
"thiserror 2.0.3",
|
||||
"tokio",
|
||||
"url",
|
||||
"web-transport-proto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -8869,6 +8872,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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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" ],
|
||||
|
|
354
ext/net/quic.rs
354
ext/net/quic.rs
|
@ -93,12 +93,27 @@ 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),
|
||||
#[class(range)]
|
||||
#[error("Connection has reached the maximum number of concurrent outgoing {0} streams")]
|
||||
MaxStreams(&'static str),
|
||||
#[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),
|
||||
|
@ -475,6 +490,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]
|
||||
|
@ -515,13 +538,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 =
|
||||
|
@ -925,3 +963,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = () => {
|
||||
|
|
218
ext/web/lib.deno_web.d.ts
vendored
218
ext/web/lib.deno_web.d.ts
vendored
|
@ -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";
|
||||
|
|
|
@ -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
1125
ext/web/webtransport.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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,
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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] = {};
|
||||
|
|
|
@ -5762,7 +5762,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!(
|
||||
|
@ -5842,7 +5842,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" }));
|
||||
|
|
5
tests/specs/run/webtransport/__test__.jsonc
Normal file
5
tests/specs/run/webtransport/__test__.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"args": "run --quiet --unstable-net -A main.ts",
|
||||
"output": "",
|
||||
"exitCode": 0
|
||||
}
|
4
tests/specs/run/webtransport/deno.json
Normal file
4
tests/specs/run/webtransport/deno.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"lock": false,
|
||||
"importMap": "../../../../import_map.json"
|
||||
}
|
100
tests/specs/run/webtransport/main.ts
Normal file
100
tests/specs/run/webtransport/main.ts
Normal 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();
|
||||
});
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue