WebTransport

Editor’s Draft,

This version:
https://w3c.github.io/webtransport/
Latest published version:
https://www.w3.org/TR/webtransport/
Feedback:
public-webtransport@w3.org with subject line “[webtransport] … message topic …” (archives)
Issue Tracking:
GitHub
Inline In Spec
Editors:
Bernard Aboba (Microsoft Corporation)
Victor Vasiliev (Google)
Yutaka Hirano (Google)
Former Editors:
Peter Thatcher (Google)
Robin Raymond (Optical Tone Ltd.)

Abstract

This document defines a set of ECMAScript APIs in WebIDL to allow data to be sent and received between a browser and server, utilizing [WEB-TRANSPORT-HTTP3]. This specification is being developed in conjunction with a protocol specification developed by the IETF WEBTRANS Working Group.

Status of this document

This is a public copy of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

Feedback and comments on this document are welcome. Please file an issue in this document’s GitHub repository.

This document was produced by the WebTransport Working Group.

This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

This document is governed by the 15 September 2020 W3C Process Document.

1. Introduction

This section is non-normative.

This specification uses [WEB-TRANSPORT-HTTP3] to send data to and receive data from servers. It can be used like WebSockets but with support for multiple streams, unidirectional streams, out-of-order delivery, and reliable as well as unreliable transport.

Note: The API presented in this specification represents a preliminary proposal based on work-in-progress within the IETF WEBTRANS WG. Since the [WEB-TRANSPORT-HTTP3] specification is a work-in-progress, both the protocol and API are likely to change significantly going forward.

2. Conformance

As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.

The key words MUST and SHOULD are to be interpreted as described in [RFC2119].

This specification defines conformance criteria that apply to a single product: the user agent that implements the interfaces that it contains.

Conformance requirements phrased as algorithms or specific steps may be implemented in any manner, so long as the end result is equivalent. (In particular, the algorithms defined in this specification are intended to be easy to follow, and not intended to be performant.)

Implementations that use ECMAScript to implement the APIs defined in this specification MUST implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [WEBIDL], as this specification uses that specification and terminology.

3. Terminology

The EventHandler interface, representing a callback used for event handlers, and the ErrorEvent interface are defined in [HTML].

The concepts queue a task and networking task source are defined in [HTML].

The terms event, event handlers and event handler event types are defined in [HTML].

When referring to exceptions, the terms throw and create are defined in [WEBIDL].

The terms fulfilled, rejected, resolved, pending and settled used in the context of Promises are defined in [ECMASCRIPT-6.0].

The terms ReadableStream and WritableStream are defined in [WHATWG-STREAMS].

4. Protocol concepts

A WebTransport session is a session of WebTransport over HTTP/3. There may be multiple WebTransport sessions on one connection, when pooling is enabled.

WebTransport session has the following capabilities defined in [WEB-TRANSPORT-HTTP3].

capability definition
send a datagram [WEB-TRANSPORT-HTTP3] section 4.4
receive a datagram [WEB-TRANSPORT-HTTP3] section 4.4
create an outgoing unidirectional stream [WEB-TRANSPORT-HTTP3] section 4.1
create a bidirectional stream [WEB-TRANSPORT-HTTP3] section 4.2
receive an incoming unidirectional stream [WEB-TRANSPORT-HTTP3] section 4.1
receive a bidirectional stream [WEB-TRANSPORT-HTTP3] section 4.2

To establish a WebTransport session with an origin origin, follow [WEB-TRANSPORT-HTTP3] section 3.3, with using origin, serialized and isomorphic encoded, as the "Origin" header of the request.

To terminate a WebTransport session session with an optional integer code and an optional byte sequence reason, follow [WEB-TRANSPORT-HTTP3] section 5.

A WebTransport session session is terminated, with optionally an integer code and a byte sequence reason, when the HTTP/3 stream associated with the CONNECT request that initiated session is closed by the server, as described at [WEB-TRANSPORT-HTTP3] section 5.

WebTransport stream is a concept for HTTP/3 stream on a WebTransport session.

A WebTransport stream is one of incoming unidirectional, outgoing unidirectional or bidirectional.

WebTransport stream has the following capabilities:

capability definition incoming unidirectional outgoing unidirectional bidirectional
send bytes (potentially with FIN) [QUIC] section 2.2 No Yes Yes
receive bytes (potentially with FIN) [QUIC] section 2.2 Yes No Yes
send STOP_SENDING [QUIC] section 3.5 Yes No Yes
reset a WebTransport stream [QUIC] section 19.4 No Yes Yes

WebTransport stream has the following signals:

event definition incoming unidirectional outgoing unidirectional bidirectional
STOP_SENDING [QUIC] section 3.5 No Yes Yes
reset [QUIC] section 19.4 Yes No Yes

5. WebTransportDatagramDuplexStream Interface

A WebTransportDatagramDuplexStream is a generic duplex stream.

[Exposed=(Window,Worker), SecureContext]
interface WebTransportDatagramDuplexStream {
  readonly attribute ReadableStream readable;
  readonly attribute WritableStream writable;

  readonly attribute unsigned long maxDatagramSize;
  attribute double? incomingMaxAge;
  attribute double? outgoingMaxAge;
  attribute long incomingHighWaterMark;
  attribute long outgoingHighWaterMark;
};

5.1. Internal slots

A WebTransportDatagramDuplexStream object has the following internal slots.

Internal Slot Description (non-normative)
[[Readable]] A ReadableStream for incoming datagrams.
[[Writable]] A WritableStream for outgoing datagrams.
[[IncomingDatagramsQueue]] A queue of pairs of an incoming datagram and a timestamp.
[[IncomingDatagramsPullPromise]] A promise set by pullDatagrams, to wait for an incoming datagram.
[[IncomingDatagramsHighWaterMark]] An integer representing the high water mark of the incoming datagrams.
[[IncomingDatagramsExpirationDuration]] A double value representing the expiration duration for incoming datagrams (in milliseconds), or null.
[[OutgoingDatagramsQueue]] A queue of tuples of an outgoing datagram, a timestamp and a promise which is resolved when the datagram is sent or discarded.
[[OutgoingDatagramsHighWaterMark]] An integer representing the high water mark of the outgoing datagrams.
[[OutgoingDatagramsExpirationDuration]] A double value representing the expiration duration for outgoing datagrams (in milliseconds), or null.
[[OutgoingMaxDatagramSize]] An integer representing the maximum size for an outgoing datagram.

The user agent MAY update [[OutgoingMaxDatagramSize]] for any WebTransport object whose [[State]] is either "connecting" or "connected".

To create a WebTransportDatagramDuplexStream given a readable, and a writable, perform the following steps.

  1. Let stream be a new WebTransportDatagramDuplexStream, with:

    [[Readable]]

    readable

    [[Writable]]

    writable

    [[IncomingDatagramsQueue]]

    an empty queue

    [[IncomingDatagramsPullPromise]]

    null

    [[IncomingDatagramsHighWaterMark]]

    an implementation-defined integer

    [[IncomingDatagramsExpirationDuration]]

    null

    [[OutgoingDatagramsQueue]]

    an empty queue

    [[OutgoingDatagramsHighWaterMark]]

    an implementation-defined integer

    This implementation-defined value should be tuned to ensure decent throughput, without jeopardizing the timeliness of transmitted data.

    [[OutgoingDatagramsExpirationDuration]]

    null

    [[OutgoingMaxDatagramSize]]

    an implementation-defined integer.

  2. Return stream.

5.2. Attributes

readable, of type ReadableStream, readonly

The getter steps are:

  1. Return this.[[Readable]].

writable, of type WritableStream, readonly

The getter steps are:

  1. Return this.[[Writable]].

incomingMaxAge, of type double, nullable

The getter steps are:

  1. Return this's [[IncomingDatagramsExpirationDuration]].

The setter steps are:

  1. Let value be the given value.

  2. If value is null or value > 0:

    1. Set this's [[IncomingDatagramsExpirationDuration]] to value.

maxDatagramSize, of type unsigned long, readonly

The maximum size data that may be passed to writable. The getter steps are to return this's [[Datagrams]]'s [[OutgoingMaxDatagramSize]].

outgoingMaxAge, of type double, nullable

The getter steps are:

  1. Return this's [[OutgoingDatagramsExpirationDuration]].

The setter steps are:

  1. Let value be the given value.

  2. If value is null or value > 0:

    1. Set this's [[OutgoingDatagramsExpirationDuration]] to value.

incomingHighWaterMark, of type long

The getter steps are:

  1. Return this's [[IncomingDatagramsHighWaterMark]].

The setter steps are:

  1. Let value be the given value.

  2. If value ≥ 0:

    1. Set this's [[IncomingDatagramsHighWaterMark]] to value.

outgoingHighWaterMark, of type long

The getter steps are:

  1. Return this's [[OutgoingDatagramsHighWaterMark]].

The setter steps are:

  1. Let value be the given value.

  2. If value ≥ 0:

    1. Set this's [[OutgoingDatagramsHighWaterMark]] to value.

5.3. Procedures

To pullDatagrams, given a WebTransport object transport, run these steps:

  1. Let datagrams be transport’s [[Datagrams]].

  2. Assert: datagrams’s [[IncomingDatagramsPullPromise]] is null.

  3. Let queue be datagrams’s [[IncomingDatagramsQueue]].

  4. If queue is empty, then:

    1. Set datagrams’s [[IncomingDatagramsPullPromise]] to a new promise.

    2. Return datagrams’s [[IncomingDatagramsPullPromise]].

  5. Let bytes and timestamp be the result of dequeuing queue.

  6. Let chunk be a new Uint8Array object representing bytes.

  7. Enqueue chunk to transport’s [[Datagrams]]' s [[Readable]].

  8. Return a promise resolved with undefined.

To receiveDatagrams, given a WebTransport object transport, run these steps:

  1. Let timestamp be a timestamp representing now.

  2. Let queue be datagrams’s [[IncomingDatagramsQueue]].

  3. Let duration be datagrams’s [[IncomingDatagramsExpirationDuration]].

  4. If duration is null, then set duration to an implementation-defined value.

  5. Let session be transport’s [[Session]].

  6. While there are available incoming datagrams on session:

    1. Let datagram be the result of receiving a datagram with session.

    2. Let timestamp be a timestamp representing now.

    3. Let chunk be a pair of datagram and timestamp.

    4. Enqueue chunk to queue.

  7. Let toBeRemoved be the length of queue minus datagrams’s [[IncomingDatagramsHighWaterMark]].

  8. If toBeRemoved is positive, repeat dequeuing queue toBeRemoved times.

  9. While queue is not empty:

    1. Let bytes and timestamp be queue’s first element.

    2. If more than duration milliseconds have passed since timestamp, then dequeue queue.

    3. Otherwise, break this loop.

  10. If queue is not empty and datagrams’s [[IncomingDatagramsPullPromise]] is non-null, then:

    1. Let bytes and timestamp be the result of dequeuing queue.

    2. Let promise be datagrams’s [[IncomingDatagramsPullPromise]].

    3. Set datagrams’s [[IncomingDatagramsPullPromise]] to null.

    4. Queue a network task with transport to run the following steps:

      1. Let chunk be a new Uint8Array object representing bytes.

      2. Enqueue chunk to datagrams’s [[Readable]].

      3. Resolve promise with undefined.

The user agent SHOULD run receiveDatagrams for any WebTransport object whose [[State]] is "connected" as soon as reasonably possible whenever the algorithm can make progress.

The writeDatagrams algorithm is given a transport as parameter and data as input. It is defined by running the following steps:

  1. Let timestamp be a timestamp representing now.

  2. If data is not a BufferSource object, then return a promise rejected with a TypeError.

  3. Let datagrams be transport’s [[Datagrams]].

  4. If datagrams’s [[OutgoingMaxDatagramSize]] is less than data’s [[ByteLength]], return a promise resolved with undefined.

  5. Let promise be a new promise.

  6. Let bytes be a copy of bytes which data represents.

  7. Let chunk be a tuple of bytes, timestamp and promise.

  8. Enqueue chunk to datagrams’s [[OutgoingDatagramsQueue]].

  9. If the length of datagrams’s [[OutgoingDatagramsQueue]] is less than datagrams’s [[OutgoingDatagramsHighWaterMark]], then resolve promise with undefined.

  10. Return promise.

Note: The associated WritableStream calls writeDatagrams only when all the promises that have been returned by writeDatagrams have been resolved. Hence the timestamp and the expiration duration work well only when the web developer pays attention to WritableStreamDefaultWriter.ready.

To sendDatagrams, given a WebTransport object transport, run these steps:

  1. Let queue be datagrams’s [[OutgoingDatagramsQueue]].

  2. Let duration be datagrams’s [[OutgoingDatagramsExpirationDuration]].

  3. If duration is null, then set duration to an implementation-defined value.

  4. While queue is not empty:

    1. Let bytes, timestamp and promise be queue’s first element.

    2. If more than duration milliseconds have passed since timestamp, then:

      1. Remove the first element from queue.

      2. Queue a network task with transport to resolve promise with undefined.

    3. Otherwise, break this loop.

  5. If transport’s [[State]] is not "connected", then return.

  6. Let maxSize be datagrams’s [[OutgoingMaxDatagramSize]].

  7. While queue is not empty:

    1. Let bytes, timestamp and promise be queue’s first element.

    2. If bytes’s length ≤ maxSize:

      1. If it is not possible to send bytes to the network immediately, then break this loop.

      2. Send a datagram, with transport’s [[Session]] and bytes.

    3. Remove the first element from queue.

    4. Queue a network task with transport to resolve promise with undefined.

The user agent SHOULD run sendDatagrams for any WebTransport object whose [[State]] is "connecting" or "connected" as soon as reasonably possible whenever the algorithm can make progress.

Note: Writing datagrams while the transport’s [[State]] is "connecting" is allowed. The datagrams are stored in [[OutgoingDatagramsExpirationDuration]], and they can be discarded in the same manner as the "connected" case. Once the transport’s [[State]] becomes "connected", it will start sending stored datagrams.

6. WebTransport Interface

WebTransport provides an API to the HTTP/3 transport functionality defined in [WEB-TRANSPORT-HTTP3].

[Exposed=(Window,Worker), SecureContext]
interface WebTransport {
  constructor(USVString url, optional WebTransportOptions options = {});

  Promise<WebTransportStats> getStats();
  readonly attribute Promise<undefined> ready;
  readonly attribute Promise<WebTransportCloseInfo> closed;
  undefined close(optional WebTransportCloseInfo closeInfo = {});

  readonly attribute WebTransportDatagramDuplexStream datagrams;

  Promise<WebTransportBidirectionalStream> createBidirectionalStream();
  /* a ReadableStream of WebTransportBidirectionalStream objects */
  readonly attribute ReadableStream incomingBidirectionalStreams;

  Promise<WritableStream> createUnidirectionalStream();
  /* a ReadableStream of ReceiveStreams */
  readonly attribute ReadableStream incomingUnidirectionalStreams;
};

6.1. Internal slots

A WebTransport object has the following internal slots.

Internal Slot Description (non-normative)
[[SendStreams]] An ordered set of SendStreams owned by this WebTransport.
[[ReceiveStreams]] An ordered set of ReceiveStreams owned by this WebTransport.
[[IncomingBidirectionalStreams]] A ReadableStream consisting of WebTransportBidirectionalStream objects.
[[IncomingUnidirectionalStreams]] A ReadableStream consisting of ReceiveStreams.
[[State]] An enum indicating the state of the transport. One of "connecting", "connected", "closed", and "failed".
[[Ready]] A promise fulfilled when the associated WebTransport session gets established, or rejected if the establishment process failed.
[[Closed]] A promise fulfilled when the associated WebTransport object is closed gracefully, or rejected when it is closed abruptly or failed on initialization.
[[Datagrams]] A WebTransportDatagramDuplexStream.
[[Session]] A WebTransport session for this WebTransport object, or null.

6.2. Constructor

When the WebTransport() constructor is invoked, the user agent MUST run the following steps:
  1. Let parsedURL be the URL record resulting from parsing url.

  2. If parsedURL is a failure, throw a SyntaxError exception.

  3. If parsedURL scheme is not https, throw a SyntaxError exception.

  4. If parsedURL fragment is not null, throw a SyntaxError exception.

  5. Let allowPooling be options's allowPooling if it exists, and false otherwise.

  6. Let dedicated be the negation of allowPooling.

  7. Let serverCertificateHashes be options's serverCertificateHashes if it exists, and null otherwise.

  8. If dedicated is false and serverCertificateHashes is non-null, then throw a TypeError.

  9. Let incomingDatagrams be a new ReadableStream.

  10. Let outgoingDatagrams be a new WritableStream.

  11. Let datagrams be the result of creating a WebTransportDatagramDuplexStream, its readable set to incomingDatagrams and its writable set to outgoingDatagrams.

  12. Let transport be a newly constructed WebTransport object, with:

    [[SendStreams]]

    an empty ordered set

    [[ReceiveStreams]]

    an empty ordered set

    [[IncomingBidirectionalStreams]]

    a new ReadableStream

    [[IncomingUnidirectionalStreams]]

    a new ReadableStream

    [[State]]

    "connecting"

    [[Ready]]

    a new promise

    [[Closed]]

    a new promise

    [[Datagrams]]

    datagrams

    [[Session]]

    null

  13. Let pullDatagramsAlgorithm be an action that runs pullDatagrams with transport.

  14. Let writeDatagramsAlgorithm be an action that runs writeDatagrams with transport.

  15. Set up incomingDatagrams with pullAlgorithm set to pullDatagramsAlgorithm, and highWaterMark set to 0.

  16. Set up outgoingDatagrams with writeAlgorithm set to writeDatagramsAlgorithm.

  17. Let pullBidirectionalStreamAlgorithm be an action that runs pullBidirectionalStream with transport.

  18. Set up transport’s [[IncomingBidirectionalStreams]] with pullAlgorithm set to pullBidirectionalStreamAlgorithm, and highWaterMark set to 0.

  19. Let pullUnidirectionalStreamAlgorithm be an action that runs pullUnidirectionalStream with transport.

  20. Set up transport’s [[IncomingUnidirectionalStreams]] with pullAlgorithm set to pullUnidirectionalStreamAlgorithm, and highWaterMark set to 0.

  21. Initialize WebTransport over HTTP with transport, parsedURL and dedicated.

  22. Return transport.

To initialize WebTransport over HTTP, given a WebTransport object transport, a URL record url, and a boolean dedicated, run these steps.
  1. Let client be transport’s relevant settings object.

  2. Let origin be client’s origin.

  3. Let networkPartitionKey be the result of determining the network partition key with transport’s relevant settings object.

  4. Run the remaining steps in parallel, but abort them whenever transport’s [[State]] becomes "closed" or "failed".

  5. Let newConnection be "no" if dedicated is false; otherwise "yes-and-dedicated".

  6. Let connection be the result of obtaining a connection with networkPartitionKey, url, false, newConnection, and with http3Only set to true.

  7. If connection is failure, then abort the remaining steps and queue a network task with transport to run these steps:

    1. If transport’s [[State]] is "closed" or "failed", then abort these steps.

    2. Let error be the result of creating a WebTransportError with "session".

    3. Cleanup transport with error, error and true.

  8. Wait for connection to receive the first SETTINGS frame, and let settings be a dictionary that represents the SETTINGS frame.

  9. If settings doesn’t contain SETTINGS_ENABLE_WEBTRANPORT with a value of 1, or it doesn’t contain H3_DATAGRAM with a value of 1, then abort the remaining steps and queue a network task with transport to run these steps:

    1. If transport’s [[State]] is "closed" or "failed", then abort these steps.

    2. Let error be the result of creating a WebTransportError with "session".

    3. Cleanup transport with error, error and true.

  10. Establish a WebTransport session with origin on connection.

    Note: This step also contains the transport parameter exchange specified in [QUIC-DATAGRAM].

  11. If the previous step fails, abort the remaining steps and queue a network task with transport to run these steps:

    1. If transport’s [[State]] is "closed" or "failed", then abort these steps.

    2. Let error be the result of creating a WebTransportError with "session".

    3. Cleanup transport with error, error and true.

  12. Let session be the established WebTransport session.

  13. Assert: maxDatagramSize is an integer.

  14. Queue a network task with transport to run these steps:

    1. If transport’s [[State]] is not "connecting":

      1. In parallel, terminate session.

      2. Abort these steps.

    2. Set transport’s [[State]] to "connected".

    3. Set transport’s [[Session]] to session.

    4. Resolve transport’s [[Ready]] with undefined.

To pullBidirectionalStream, given a WebTransport object transport, run these steps.
  1. If transport’s [[State]] is "connecting", then return the result of performing the following steps upon fulfillment of transport’s [[Ready]]:

    1. Return the result of pullBidirectionalStream with transport.

  2. Let session be transport’s [[Session]].

  3. Let p be a new promise.

  4. Return p and run the remaining steps in parallel.

  5. Wait until there is an available incoming bidirectional stream.

  6. Let internalStream be the result of receiving a bidirectional stream.

  7. Queue a network task with transport to run these steps:

    1. Let stream be the result of creating a WebTransportBidirectionalStream with internalStream and transport.

    2. Enqueue stream to transport’s [[IncomingBidirectionalStreams]].

    3. Resolve p with undefined.

To pullUnidirectionalStream, given a WebTransport object transport, run these steps.
  1. If transport’s [[State]] is "connecting", then return the result of performing the following steps upon fulfillment of transport’s [[Ready]]:

    1. Return the result of pullUnidirectionalStream with transport.

  2. Let session be transport’s [[Session]].

  3. Let p be a new promise.

  4. Return p and run the remaining steps in parallel.

  5. Wait until there is an available incoming unidirectional stream.

  6. Let internalStream be the result of receiving an incoming unidirectional stream.

  7. Queue a network task with transport to run these steps:

    1. Let stream be the result of creating a ReceiveStream with internalStream and transport.

    2. Enqueue stream to transport’s [[IncomingUnidirectionalStreams]].

    3. Resolve p with undefined.

6.3. Attributes

ready, of type Promise<undefined>, readonly

On getting, it MUST return this's [[Ready]].

closed, of type Promise<WebTransportCloseInfo>, readonly

On getting, it MUST return this's [[Closed]].

This promise MUST be resolved when the transport is closed; an implementation SHOULD include error information in the reason and closeCode fields of WebTransportCloseInfo.

datagrams, of type WebTransportDatagramDuplexStream, readonly

A single duplex stream for sending and receiving datagrams over this session. The getter steps for the datagrams attribute SHALL be:

  1. Return this's [[Datagrams]].

incomingBidirectionalStreams, of type ReadableStream, readonly

Returns a ReadableStream of WebTransportBidirectionalStreams that have been received from the server. The getter steps for the incomingBidirectionalStreams attribute SHALL be:

  1. Return this's [[IncomingBidirectionalStreams]].

incomingUnidirectionalStreams, of type ReadableStream, readonly

A ReadableStream of unidirectional streams, each represented by a ReceiveStream, that have been received from the server. The getter steps for incomingUnidirectionalStreams are:

  1. Return this's [[IncomingUnidirectionalStreams]].

6.4. Methods

close(closeInfo)

Terminates the WebTransport session associated with the WebTransport object.

When close is called, the user agent MUST run the following steps:

  1. Let transport be this.

  2. If transport’s [[State]] is "closed" or "failed", then abort these steps.

  3. If transport’s [[State]] is "connecting":

    1. Let error be the result of creating a WebTransportError with "session".

    2. Cleanup transport with error, error and true.

    3. Abort these steps.

  4. Let session be transport’s [[Session]].

  5. Let reason be an empty byte sequence.

  6. If closeInfo.reason exists, set reason be closeInfo.reason, UTF-8 encoded.

  7. If reason’s length exceeds 1024, then set reason to a prefix of reason whose length is 1024.

  8. If closeInfo.closeCode exists:

    1. In parallel, terminate session with closeInfo.closeCode and reason.

  9. Otherwise:

    1. In parallel, terminate session.

    Note: This also resets or sends STOP_SENDING WebTransport streams contained in transport’s [[SendStreams]] and [[ReceiveStreams]].

  10. Cleanup transport with closeInfo, an AbortError and false.

getStats()

Gathers stats for this WebTransport's HTTP/3 connection and reports the result asynchronously.

When getStats is called, the user agent MUST run the following steps:

  1. Let transport be this.

  2. Let p be a new promise.

  3. If the URL scheme associated with transport is not https, reject p with NotSupportedError and return p.

  4. Return p and continue the following steps in parallel.

    1. Gather the stats from the underlying QUIC connection.

    2. Wait for the stats to be ready.

    3. Queue a network task with transport to run the following steps:

      1. Let stats be a new WebTransportStats object representing the gathered stats.

      2. Resolve p with stats.

createBidirectionalStream()

Creates a WebTransportBidirectionalStream object for an outgoing bidirectional stream. Note that the mere creation of a stream is not immediately visible to the peer until it is used to send data.

When createBidirectionalStream is called, the user agent MUST run the following steps:

  1. Let transport be this.

  2. If transport’s [[State]] is "closed" or "failed", return a new rejected promise with an InvalidStateError.

  3. Let p be a new promise.

  4. Run the following steps in parallel, but abort them whenever transport’s [[State]] becomes "closed" or "failed", and instead queue a network task with transport to reject p with an InvalidStateError.

    1. Wait for transport’s [[State]] to be "connected".

    2. Let internalStream be the result of creating a bidirectional stream with transport’s [[Session]].

      Note: This operation may take time, for example when the stream ID is exhausted. [QUIC]

    3. Queue a network task with transport to run the following steps:

      1. If transport’s [[State]] is "closed" or "failed", reject p with an InvalidStateError and abort these steps.

      2. Let stream be the result of creating a WebTransportBidirectionalStream with internalStream and transport.

      3. Resolve p with stream.

  5. return p.

createUnidirectionalStream()

Creates a SendStream for an outgoing unidirectional stream. Note that the mere creation of a stream is not immediately visible to the server until it is used to send data.

When createUnidirectionalStream() method is called, the user agent MUST run the following steps:

  1. Let transport be this.

  2. If transport’s [[State]] is "closed" or "failed", return a new rejected promise with an InvalidStateError.

  3. Let p be a new promise.

  4. Run the following steps in parallel, but abort them whenever transport’s [[State]] becomes "closed" or "failed", and instead queue a network task with transport to reject p with an InvalidStateError.

    1. Wait for transport’s [[State]] to be "connected".

    2. Let internalStream be the result of creating an outgoing unidirectional stream with transport’s [[Session]].

      Note: This operation may take time, for example when the stream ID is exhausted. [QUIC]

    3. Queue a network task with transport to run the following steps:

      1. If transport’s [[State]] is "closed" or "failed", reject p with an InvalidStateError and abort these steps.

      2. Let stream be the result of creating a SendStream with internalStream and transport.

      3. Resolve p with stream.

  5. return p.

6.5. Procedures

To cleanup a WebTransport transport with reason, error and a boolean abruptly, run these steps:
  1. Let sendStreams be a copy of transport’s [[SendStreams]].

  2. Let receiveStreams be a copy of transport’s [[ReceiveStreams]].

  3. Let ready be transport’s [[Ready]].

  4. Let closed be transport’s [[Closed]].

  5. Let incomingBidirectionalStreams be transport’s [[IncomingBidirectionalStreams]].

  6. Let incomingUnidirectionalStreams be transport’s [[IncomingUnidirectionalStreams]].

  7. Set transport’s [[SendStreams]] to an empty set.

  8. Set transport’s [[ReceiveStreams]] to an empty set.

  9. If abruptly is true, then set transport’s [[State]] to "failed".

  10. Otherwise, set transport’s [[State]] to "closed".

  11. For each sendStream in sendStreams, error sendStream with error.

  12. For each receiveStream in receiveStreams, error receiveStream with error.

    Note: Script authors can inject code which runs in Promise resolution synchronously. Hence from here, do not touch transport as it may be mutated by scripts in an unpredictable way. This applies to logic calling this procedure, too.

  13. If abruptly is true, then:

    1. Reject closed with error.

    2. Reject ready with error.

    3. Error incomingBidirectionalStreams with error.

    4. Error incomingUnidirectionalStreams with error.

  14. Otherwise:

    1. Resolve closed with reason.

    2. Assert: ready is settled.

    3. Close incomingBidirectionalStreams.

    4. Close incomingUnidirectionalStreams.

To queue a network task with a WebTransport transport and a series of steps steps, run these steps:

  1. Queue a global task on the network task source with transport’s relevant global object to run steps.

6.6. Session termination not initiated by the client

Whenever a WebTransport session which is associated with a WebTransport transport is terminated with optionally code and reasonBytes, run these steps:
  1. Let cleanly be a boolean representing whether the HTTP/3 stream associated with the CONNECT request that initiated transport’s [[Session]] is in the "Data Recvd" state. [QUIC]

  2. Queue a network task with transport to run these steps:

    1. If transport’s [[State]] is "closed" or "failed", abort these steps.

    2. Let error be the result of creating a WebTransportError with "session".

    3. Let reason be error.

    4. If cleanly is true:

      1. Set reason to a new WebTransportCloseInfo.

      2. If code is given, set reason’s closeCode to code.

      3. If reasonBytes is given, set reason’s reason to reasonBytes, UTF-8 decoded.

    5. Cleanup transport with reason, error and the negation of cleanly.

Whenever a connection associated with a WebTransport transport gets a connection error, run these steps:
  1. Queue a network task with transport to run these steps:

    1. If transport’s [[State]] is "closed" or "failed", abort these steps.

    2. Let error be the result of creating a WebTransportError with "session".

    3. Cleanup transport with error, error and true.

6.7. Context cleanup steps

This specification defines unloading document cleanup steps as the following steps, given a Document document:

  1. Let window be document’s relevent global object.

  2. For each WebTransport transport whose relevent global object is window:

    1. If transport’s [[State]] is "connected", set transport’s [[State]] to "failed" and terminate transport’s [[Session]] in parallel.

    2. If transport’s [[State]] is "connecting", set transport’s [[State]] to "failed".

    This needs to be done in workers too. See #127 and whatwg/html#6731.

6.8. Garbage Collection

The user agent MUST NOT garbage collect a WebTransport object whose [[State]] is either "connecting" or "connected".

6.9. Configuration

dictionary WebTransportHash {
  DOMString algorithm;
  BufferSource value;
};

dictionary WebTransportOptions {
  boolean allowPooling;
  sequence<WebTransportHash> serverCertificateHashes;
};

WebTransportOptions is a dictionary of parameters that determine how WebTransport connection is established and used.

allowPooling, of type boolean

When set to true, the WebTransport connection can be pooled, that is, the network connection for the WebTransport session can be shared with other HTTP/3 sessions.

serverCertificateHashes, of type sequence<WebTransportHash>

This option is only supported for transports using dedicated connections. For transport protocols that do not support this feature, having this field non-empty SHALL result in a NotSupportedError exception being thrown.

If supported and non-empty, the user agent SHALL deem a server certificate trusted if and only if it can successfully verify a certificate hash against serverCertificateHashes and satisfies custom certificate requirements. The user agent SHALL ignore any hash that uses an unknown algorithm. If empty, the user agent SHALL use certificate verification procedures it would use for normal fetch operations.

This cannot be used with allowPooling.

To compute a certificate hash, do the following:
  1. Let cert be the input certificate, represented as a DER encoding of Certificate message defined in [RFC5280].

  2. Compute the SHA-256 hash of cert and return the computed value.

To verify a certificate hash, do the following:
  1. Let hashes be the input array of hashes.

  2. Let referenceHash be the computed hash of the input certificate.

  3. For every hash hash in hashes:

    1. If hash’s value is not null:

      1. Let hashValue be the byte sequence which hash’s value represents.

      2. If algorithm of hash is equal to "sha-256", and hashValue is equal to referenceHash, the certificate is valid. Return true.

  4. Return false.

The custom certificate requirements are as follows: the certificate MUST be an X.509v3 certificate as defined in [RFC5280], the current time MUST be within the validity period of the certificate as defined in Section 4.1.2.5 of [RFC5280] and the total length of the validity period MUST NOT exceed two weeks.

Reconsider the time period above. We want it to be sufficiently large that applications using this for ephemeral certificates can do so without having to fight the clock skew, but small enough to discourage long-term use without key rotation.

6.10. WebTransportCloseInfo Dictionary

The WebTransportCloseInfo dictionary includes information relating to the error code for closing a WebTransport. This information is used to set the error code and reason for a CONNECTION_CLOSE frame.

dictionary WebTransportCloseInfo {
  unsigned long closeCode;
  DOMString reason;
};

The dictionary SHALL have the following attributes:

closeCode, of type unsigned long

The error code communicated to the peer.

reason, of type DOMString

The reason for closing the WebTransport.

6.11. WebTransportStats Dictionary

The WebTransportStats dictionary includes information on HTTP/3 connection stats.

Now that quic-transport has been removed, this section needs to be revised. Some of those are safe to expose for HTTP/2 and HTTP/3 connections (like min-RTT), while most would either result in information disclosure or are impossible to define for pooled connections.

dictionary WebTransportStats {
  DOMHighResTimeStamp timestamp;
  unsigned long long bytesSent;
  unsigned long long packetsSent;
  unsigned long numOutgoingStreamsCreated;
  unsigned long numIncomingStreamsCreated;
  unsigned long long bytesReceived;
  unsigned long long packetsReceived;
  DOMHighResTimeStamp minRtt;
  unsigned long numReceivedDatagramsDropped;
};

The dictionary SHALL have the following attributes:

timestamp, of type DOMHighResTimeStamp

The timestamp for when the stats are gathered, relative to the UNIX epoch (Jan 1, 1970, UTC).

bytesSent, of type unsigned long long

The number of bytes sent on the QUIC connection, including retransmissions. Does not include UDP or any other outer framing.

packetsSent, of type unsigned long long

The number of packets sent on the QUIC connection, including retransmissions.

numOutgoingStreamsCreated, of type unsigned long

The number of outgoing QUIC streams created on the QUIC connection.

numIncomingStreamsCreated, of type unsigned long

The number of incoming QUIC streams created on the QUIC connection.

bytesReceived, of type unsigned long long

The number of total bytes received on the QUIC connection, including duplicate data for streams. Does not include UDP or any other outer framing.

packetsReceived, of type unsigned long long

The number of total packets received on the QUIC connection, including packets that were not processable.

minRtt, of type DOMHighResTimeStamp

The minimum RTT observed on the entire connection.

numReceivedDatagramsDropped, of type unsigned long

The number of datagrams that were dropped, due to too many datagrams buffered between calls to datagrams' readable.

7. SendStream

In this spec, we call a WritableStream providing outgoing streaming features with an outgoing unidirectional or bidirectional WebTransport stream a SendStream.

Note: SendStream is not a Web IDL type. A SendStream is always created by the create procedure.

7.1. Internal Slots

A SendStream has the following internal slots.

Internal Slot Description (non-normative)
[[InternalStream]] An outgoing unidirectional or bidirectional WebTransport stream.
[[PendingOperation]] A promise representing a pending write or close operation, or null.
[[Transport]] A WebTransport which owns this SendStream.

Note: These internal slots need to be attached to SendStream (which is a kind of WritableStream) conceptually, not physically. For example, an implementation could place them on an object which is referenced from writeAlgorithm, closeAlgorithm and abortAlgorithm in the create procedure.

7.2. Procedures

To create a SendStream, with an outgoing unidirectional or bidirectional WebTransport stream internalStream and a WebTransport transport, run these steps:

  1. Let stream be a new WritableStream, with:

    [[InternalStream]]

    internalStream

    [[PendingOperation]]

    null

    [[Transport]]

    transport

  2. Let writeAlgorithm be an action that writes chunk to stream, given chunk.

  3. Let closeAlgorithm be an action that closes stream.

  4. Let abortAlgorithm be an action that aborts stream with reason, given reason.

  5. Set up stream with writeAlgorithm set to writeAlgorithm, closeAlgorithm set to closeAlgorithm, abortAlgorithm set to abortAlgorithm.

  6. Add the following steps to stream’s [[controller]]'s [[signal]].

    1. If stream’s [[PendingOperation]] is null, then abort these steps.

    2. Let reason be stream’s [[controller]]'s [[abortReason]].

    3. Let abortPromise be the result of aborting stream with reason.

    4. Upon fulfillment of abortPromise, reject promise with reason.

    5. Let pendingOperation be stream’s [[PendingOperation]].

    6. Set stream’s [[PendingOperation]] to null.

    7. Resolve pendingOperation with promise.

  7. Append stream to transport’s [[SendStreams]].

  8. Return stream.

To write chunk to a SendStream stream, run these steps:
  1. Let transport be stream’s [[Transport]].

  2. If chunk is not a BufferSource, return a promise rejected with a TypeError.

  3. Let promise be a new promise.

  4. Let bytes be a copy of the byte sequence which chunk represents.

  5. Set stream’s [[PendingOperation]] to promise.

  6. Return promise and run the remaining steps in parallel.

  7. Wait for transport’s [[Datagrams]]'s [[OutgoingDatagramsQueue]] to be empty.

    Note: This means outgoing datagrams are prioritized over stream data.

  8. Send bytes on stream’s [[InternalStream]] and wait for the operation to complete.

  9. If the previous step failed, abort the remaining steps.

    Note: We don’t reject promise here because we handle network errors elsewhere, and those steps error stream and reject the result of this write operation.

  10. Queue a network task with transport to run these steps:

    1. Set stream’s [[PendingOperation]] to null.

    2. Resolve promise with undefined.

Note: The user-agent MAY have a buffer to improve the transfer performance. Such a buffer SHOULD have a fixed upper limit, to carry the backpressure information to the user of SendStream. This also means the fulfillment of the promise returned from this algorithm (or, WritableStreamDefaultWriter.write) does NOT necessarily mean that the chunk is acked by the server [QUIC]. It may just mean that the chunk is appended to the buffer. To make sure that the chunk arrives at the server, use an application-level protocol.

To close a SendStream stream, run these steps:
  1. Let transport be stream’s [[Transport]].

  2. Let promise be a new promise.

  3. Remove stream from transport’s [[SendStreams]].

  4. Set stream’s [[PendingOperation]] to promise.

  5. Return promise and run the remaining steps in parallel.

  6. Send FIN on stream’s [[InternalStream]] and wait for the operation to complete.

  7. Wait for stream’s [[InternalStream]] to enter the "Data Recvd" state. [QUIC]

  8. Queue a network task with transport to run these steps:

    1. Set stream’s [[PendingOperation]] to null.

    2. Resolve promise with undefined.

To abort a SendStream stream with reason, run these steps:
  1. Let transport be stream’s [[Transport]].

  2. Let promise be a new promise.

  3. Let code be 0.

  4. Remove stream from transport’s [[SendStreams]].

  5. If reason is a WebTransportError and reasons’s [[StreamErrorCode]] is not null, then set code to reason’s [[StreamErrorCode]].

  6. If code < 0, then set code to 0.

  7. If code > 255, then set code to 255.

    Note: Valid values of code are from 0 to 255 inclusive. The code will be encoded to a number in [0x52e4a40fa8db, 0x52e4a40fa9e2] as decribed in [WEB-TRANSPORT-HTTP3].

  8. Return promise and run the remaining steps in parallel.

  9. Reset stream’s [[InternalStream]] with code.

  10. Queue a network task with transport to resolve promise with undefined.

7.3. STOP_SENDING signal coming from the server

Whenever a WebTransport stream associated with a SendStream stream gets a STOP_SENDING signal from the server, run these steps:
  1. Let transport be stream’s [[Transport]].

  2. Let code be the application protocol error code attached to the STOP_SENDING frame. [QUIC]

    Note: Valid values of code are from 0 to 255 inclusive. The code has been decoded from a number in [0x52e4a40fa8db, 0x52e4a40fa9e2] as decribed in [WEB-TRANSPORT-HTTP3].

  3. Queue a network task with transport to run these steps:

    1. If transport’s [[State]] is "closed" or "failed", abort these steps.

    2. Remove stream from transport’s [[SendStreams]].

    3. Let error be the result of creating a WebTransportError with "stream".

    4. Set error’s [[StreamErrorCode]] to code.

    5. Error stream with error.

8. ReceiveStream

In this spec, we call a ReadableStream providing incoming streaming features with an incoming unidirectional or bidirectional WebTransport stream a ReceiveStream.

A ReceiveStream is a ReadableStream of Uint8Array that can be read from, to consume data received from the server. ReceiveStream is a readable byte stream, and hence it allows its consumers to use a BYOB reader as well as a default reader.

Note: ReceiveStream is not a Web IDL type. A ReceiveStream is always created by the create procedure.

8.1. Internal Slots

A ReceiveStream has the following internal slots.

Internal Slot Description (non-normative)
[[InternalStream]] An incoming unidirectional or bidirectional WebTransport stream.
[[Transport]] The WebTransport object owning this ReceiveStream.

Note: These internal slots need to be attached to ReceiveStream (which is a kind of ReadableStream) conceptually, not physically. For example, an implementation could place them on an object which is referenced from pullAlgorithm and cancelAlgorithm in the create procedure.

8.2. Procedures

To create a ReceiveStream, with an incoming unidirectional or bidirectional WebTransport stream internalStream and a WebTransport transport, run these steps:

  1. Let stream be a new ReadableStream, with:

    [[InternalStream]]

    internalStream

    [[Transport]]

    transport

  2. Let pullAlgorithm be an action that pulls bytes from stream.

  3. Let cancelAlgorithm be an action that cancels stream with reason, given reason.

  4. Set up with byte reading support stream with pullAlgorithm set to pullAlgorithm and cancelAlgorithm set to cancelAlgorithm.

  5. Add stream to transport’s [[ReceiveStreams]].

  6. Return stream.

To pull bytes from a ReceiveStream stream, run these steps.

  1. Let transport be stream’s [[Transport]].

  2. Let internalStream be stream’s [[InternalStream]].

  3. Let promise be a new promise.

  4. Let buffer, offset, and maxBytes be null.

  5. If stream’s current BYOB request view for stream is not null:

    1. Set offset to stream’s current BYOB request view.[[ByteOffset]].

    2. Set maxBytes to stream’s current BYOB request view's byte length.

    3. Set buffer to stream’s current BYOB request view's underlying buffer.

  6. Otherwise:

    1. Set offset to 0.

    2. Set maxBytes to an implementation-defined size.

    3. Set buffer be a new ArrayBuffer with maxBytes size. If allocating the ArrayBuffer fails, return a promise rejected with a RangeError.

  7. Return promise and run the remaining steps in parallel.

  8. Write the bytes that area read from internalStream into buffer with offset offset, up to maxBytes bytes. Wait until either at least one byte is read or FIN is received. Let read be the number of read bytes, and let hasReceivedFIN be whether FIN was accompanied. Note: The user-agent MAY have a buffer to improve the transfer performance. Such a buffer SHOULD have a fixed upper limit, to carry the backpressure information to the server.

    Note: This operation may return before filling up all of bytes.

  9. If the previous step failed, abort the remaining steps.

    Note: We don’t reject promise here because we handle network errors elsewhere, and those steps error stream and reject the result of this read operation.

  10. Queue a network task with transport to run these steps:

    Note: If the buffer described above is available in the event loop where this procedure is running, the following steps may run immediately.

    1. If read > 0:

      1. Set view to a new Uint8Array with buffer, offset and read.

      2. Enqueue view into stream.

    2. If hasReceivedFIN is true:

      1. Remove stream from transport’s [[ReceiveStreams]].

      2. Close stream.

    3. Resolve promise with undefined.

To cancel a ReceiveStream stream with reason, run these steps.

  1. Let transport be stream’s [[Transport]].

  2. Let internalStream be stream’s [[InternalStream]].

  3. Let promise be a new promise.

  4. Let code be 0.

  5. If reason is a WebTransportError and reasons’s [[StreamErrorCode]] is not null, then set code to reason’s [[StreamErrorCode]].

  6. If code < 0, then set code to 0.

  7. If code > 255, then set code to 255.

    Note: Valid values of code are from 0 to 255 inclusive. The code will be encoded to a number in [0x52e4a40fa8db, 0x52e4a40fa9e2] as decribed in [WEB-TRANSPORT-HTTP3].

  8. Remove stream from transport’s [[SendStreams]].

  9. Return promise and run the remaining steps in parallel.

  10. Send STOP_SENDING with internalStream and code.

  11. Queue a network task with transport to run these steps:

    Note: If the buffer described above is available in the event loop where this procedure is running, the following steps may run immediately.

    1. Remove stream from transport’s [[ReceiveStreams]].

    2. Resolve promise with undefined.

8.3. Reset signal coming from the server

Whenever a WebTransport stream associated with a ReceiveStream stream gets a reset signal from the server, run these steps:
  1. Let transport be stream’s [[Transport]].

  2. Let code be the application protocol error code attached to the RESET_STREAM frame. [QUIC]

    Note: Valid values of code are from 0 to 255 inclusive. The code has been decoded from a number in [0x52e4a40fa8db, 0x52e4a40fa9e2] as decribed in [WEB-TRANSPORT-HTTP3].

  3. Queue a network task with transport to run these steps:

    1. If transport’s [[State]] is "closed" or "failed", abort these steps.

    2. Remove stream from transport’s [[ReceiveStreams]].

    3. Let error be the result of creating a WebTransportError with "stream".

    4. Set error’s [[StreamErrorCode]] to code.

    5. Error stream with error.

9. Interface WebTransportBidirectionalStream

[Exposed=(Window,Worker), SecureContext]
interface WebTransportBidirectionalStream {
  readonly attribute ReadableStream readable;
  readonly attribute WritableStream writable;
};

9.1. Internal slots

A WebTransportBidirectionalStream has the following internal slots.

Internal Slot Description (non-normative)
[[Readable]] A ReceiveStream.
[[Writable]] A SendStream.
[[Transport]] The WebTransport object owning this WebTransportBidirectionalStream.

9.2. Attributes

readable, of type ReadableStream, readonly

The getter steps are to return this's [[Readable]].

writable, of type WritableStream, readonly

The getter steps are to return this's [[Writable]].

9.3. Procedures

To create a WebTransportBidirectionalStream with a bidirectional WebTransport stream internalStream and a WebTransport object transport, run these steps.
  1. Let readable be the result of creating a ReceiveStream with internalStream and transport.

  2. Let writable be the result of creating a SendStream with internalStream and transport.

  3. Let stream be a new WebTransportBidirectionalStream, with:

    [[Readable]]

    readable

    [[Writable]]

    writable

    [[Transport]]

    transport

  4. Return stream.

10. WebTransportError Interface

WebTransportError is a subclass of DOMException that represents

[Exposed=(Window,Worker), SecureContext]
interface WebTransportError : DOMException {
  constructor(optional WebTransportErrorInit init = {});

  readonly attribute WebTransportErrorSource source;
  readonly attribute octet? streamErrorCode;
};

dictionary WebTransportErrorInit {
  [Clamp] octet streamErrorCode;
  DOMString message;
};

enum WebTransportErrorSource {
  "stream",
  "session",
};

10.1. Internal slots

A WebTransportError has the following internal slots.

Internal Slot Description (non-normative)
[[Source]] A WebTransportErrorSource indicating the source of this error.
[[StreamErrorCode]] The application protocol error code for this error, or null.

10.2. Constructor

The new WebTransportError(init) constructor steps are:

  1. Let error be this.

  2. Let message be init’s message if it exists, and "" otherwise.

  3. Set up error with message and "stream".

  4. Set error’s [[StreamErrorCode]] to init’s streamErrorCode if it exists.

10.3. Attributes

source, of type WebTransportErrorSource, readonly

The getter steps are to return this's [[Source]].

streamErrorCode, of type octet, readonly, nullable

The getter steps are to return this's [[StreamErrorCode]].

10.4. Procedures

To create a WebTransportError error with a WebTransportErrorSource source, run these steps:

  1. Let message be an implementation-defined string.

  2. Let error be a new WebTransportError.

  3. Set up error with message and source.

To set up a WebTransportError error with a DOMString message and a WebTransportErrorSource source, run these steps:

  1. Set error’s internal slots as:

    [[StreamErrorCode]]

    null

    [[Source]]

    source

  2. Run new DOMException(message, name) constructor steps with error, message and "WebTransportError".

    Note: This name does not have a mapping to a legacy code, so this's code is 0.

11. Protocol Mappings

This section is non-normative.

This section describes the [QUIC] protocol behavior of methods defined in this specification, utilizing [WEB-TRANSPORT-HTTP3].

Method QUIC Protocol Action
writable.abort(errorCode) send RESET_STREAM with errorCode
writable.close() send STREAM_FINAL
writable.getWriter().write() send STREAM
writable.getWriter().close() send STREAM_FINAL
writable.getWriter().abort(errorCode) send RESET_STREAM with errorCode
readable.cancel(errorCode) send STOP_SENDING with errorCode
readable.getReader().read() receive STREAM or STREAM_FINAL
readable.getReader().cancel(errorCode) send STOP_SENDING with errorCode

12. Privacy and Security Considerations

This section is non-normative; it specifies no new behaviour, but instead summarizes information already present in other parts of the specification.

12.1. Confidentiality of Communications

The fact that communication is taking place cannot be hidden from adversaries that can observe the network, so this has to be regarded as public information.

All of the transport protocols described in this document use either TLS [RFC8446] or a semantically equivalent protocol, thus providing all of the security properties of TLS, including confidentiality and integrity of the traffic. WebTransport over HTTP uses the same certificate verification mechanism as outbound HTTP requests, thus relying on the same public key infrastructure for authentication of the remote server. In WebTransport, certificate verification errors are fatal; no interstitial allowing bypassing certificate validation is available.

12.2. State Persistence

WebTransport by itself does not create any new unique identifiers or new ways to persistently store state, nor does it automatically expose any of the existing persistent state to the server. For instance, none of the transports defined in this document automatically send cookies, support HTTP authentication or caching invalidation mechanisms. Since they use TLS, they may support TLS session tickets, which could be used by the server (though not by passive network observers) to correlate different connections from the same client. This is not specific to WebTransport by itself, but rather an inherent property of all TLS-based protocols; thus, this is out-of-scope for this specification.

12.3. Protocol Security

WebTransport imposes a set of requirements as described in [WEB-TRANSPORT-OVERVIEW], including:

  1. Ensuring that the remote server is aware that the connection in question originates from a Web application; this is required to prevent cross-protocol attacks. [WEB-TRANSPORT-HTTP3] uses a combination of ALPN [RFC7301], an HTTP/3 setting and a :protocol pseudo-header for this purpose.

  2. Allowing the server to filter connections based on the origin of the resource originating the transport session.

Protocol security considerations related are described in the Security Considerations sections of [WEB-TRANSPORT-HTTP3].

Networking APIs can be commonly used to scan the local network for available hosts, and thus be used for fingerprinting and other forms of attacks. WebTransport follows the WebSocket approach to this problem: the specific connection error is not returned until an endpoint is verified to be a WebTransport endpoint; thus, the Web application cannot distinguish between a non-existing endpoint and the endpoint that is not willing to accept connections from the Web.

13. Examples

13.1. Sending a buffer of datagrams

This section is non-normative.

Sending a buffer of datagrams can be achieved by using the datagrams' writable attribute. In the following example datagrams are only sent if the DatagramTransport is ready to send.

async function sendDatagrams(url, datagrams) {
  const wt = new WebTransport(url);
  const writer = wt.datagrams.writable.getWriter();
  for (const datagram of datagrams) {
    await writer.ready;
    writer.write(datagram).catch(() => {});
  }
}

13.2. Sending datagrams at a fixed rate

This section is non-normative.

Sending datagrams at a fixed rate regardless if the transport is ready to send can be achieved by simply using datagrams' writable and not using the ready attribute. More complex scenarios can utilize the ready attribute.

// Sends datagrams every 100 ms.
async function sendFixedRate(url, createDatagram, ms = 100) {
  const wt = new WebTransport(url);
  await wt.ready;
  const writer = wt.datagrams.writable.getWriter();
  const datagram = createDatagram();
  setInterval(() => writer.write(datagram).catch(() => {}), ms);
}

13.3. Receiving datagrams

This section is non-normative.

Datagrams can be received by reading from the transport.datagrams.datagrams.readable attribute. Null values may indicate that packets are not being processed quickly enough.

async function receiveDatagrams(url) {
  const wt = new WebTransport(url);
  for await (const datagram of wt.datagrams.readable) {
    // Process the datagram
  }
}

13.4. Sending a stream

This section is non-normative.

Sending data as a one-way stream can be achieved by using the createUnidirectionalStream function and the resulting stream’s writer.

async function sendData(url, data) {
  const wt = new WebTransport(url);
  const writable = await wt.createUnidirectionalStream();
  const writer = writable.getWriter();
  await writer.write(data);
  await writer.close();
}

Encoding can also be done through pipes from a ReadableStream, for example using TextEncoderStream.

async function sendText(url, readableStreamOfTextData) {
  const wt = new WebTransport(url);
  const writable = await wt.createUnidirectionalStream();
  await readableStreamOfTextData
    .pipeThrough(new TextEncoderStream("utf-8"))
    .pipeTo(writable);
}

13.5. Receiving incoming streams

This section is non-normative.

Reading incoming streams can be achieved by iterating over the incomingUnidirectionalStreams attribute, and then consuming each ReceiveStream by iterating over its chunks.

async function receiveData(url, processTheData) {
  const wt = new WebTransport(url);
  for await (const readable of wt.incomingUnidirectionalStreams) {
    // consume streams individually, reporting per-stream errors
    ((async () => {
      try {
        for await (const chunk of readable) {
          processTheData(chunk);
        }
      } catch (e) {
        console.error(e);
      }
    })());
  }
}

Decoding can also be done through pipes to new WritableStreams, for example using TextDecoderStream. This example assumes text output should not be interleaved, and therefore only reads one stream at a time.

async function receiveText(url, createWritableStreamForTextData) {
  const wt = new WebTransport(url);
  for await (const readable of wt.incomingUnidirectionalStreams) {
    // consume sequentially to not interleave output, reporting per-stream errors
    try {
      await readable
       .pipeThrough(new TextDecoderStream("utf-8"))
       .pipeTo(createWritableStreamForTextData());
    } catch (e) {
      console.error(e);
    }
  }
}

13.6. Complete example

This section is non-normative.

This example illustrates use of the closed and ready promises, opening of uni-directional and bi-directional streams by either the client or the server, and sending and receiving datagrams.

// Adds an entry to the event log on the page, optionally applying a specified
// CSS class.

let wt, streamNumber, datagramWriter;

connect.onclick = async () => {
  try {
    const url = document.getElementById('url').value;

    wt = new WebTransport(url);
    addToEventLog('Initiating connection...');
    await wt.ready;
    addToEventLog('Connection ready.');

    wt.closed
      .then(() => addToEventLog('Connection closed normally.'))
      .catch(() => addToEventLog('Connection closed abruptly.', 'error'));

    streamNumber = 1;
    datagramWriter = wt.datagrams.writable.getWriter();

    readDatagrams();
    acceptUnidirectionalStreams();
    document.forms.sending.elements.send.disabled = false;
    document.getElementById('connect').disabled = true;
  } catch (e) {
    addToEventLog(`Connection failed. ${e}`, 'error');
  }
}

sendData.onclick = async () => {
  const form = document.forms.sending.elements;
  const rawData = sending.data.value;
  const data = new TextEncoder('utf-8').encode(rawData);
  try {
    switch (form.sendtype.value) {
      case 'datagram': {
        await datagramWriter.write(data);
        addToEventLog(`Sent datagram: ${rawData}`);
        break;
      }
      case 'unidi': {
        const writable = await wt.createUnidirectionalStream();
        const writer = writable.getWriter();
        await writer.write(data);
        await writer.close();
        addToEventLog(`Sent a unidirectional stream with data: ${rawData}`);
        break;
      }
      case 'bidi': {
        const duplexStream = await wt.createBidirectionalStream();
        const n = streamNumber++;
        readFromIncomingStream(duplexStream.readable, n);

        const writer = duplexStream.writable.getWriter();
        await writer.write(data);
        await writer.close();
        addToEventLog(`Sent bidirectional stream #${n} with data: ${rawData}`);
        break;
      }
    }
  } catch (e) {
    addToEventLog(`Error while sending data: ${e}`, 'error');
  }
}

// Reads datagrams into the event log until EOF is reached.
async function readDatagrams() {
  try {
    const decoder = new TextDecoderStream('utf-8');

    for await (const data of wt.datagrams.readable.pipeThrough(decoder)) {
      addToEventLog(`Datagram received: ${data}`);
    }
    addToEventLog('Done reading datagrams!');
  } catch (e) {
    addToEventLog(`Error while reading datagrams: ${e}`, 'error');
  }
}

async function acceptUnidirectionalStreams() {
  try {
    for await (const readable of wt.incomingUnidirectionalStreams) {
      const number = streamNumber++;
      addToEventLog(`New incoming unidirectional stream #${number}`);
      readFromIncomingStream(readable, number);
    }
    addToEventLog('Done accepting unidirectional streams!');
  } catch (e) {
    addToEventLog(`Error while accepting streams ${e}`, 'error');
  }
}

async function readFromIncomingStream(readable, number) {
  try {
    const decoder = new TextDecoderStream('utf-8');
    for await (const chunk of readable.pipeThrough(decoder)) {
      addToEventLog(`Received data on stream #${number}: ${chunk}`);
    }
    addToEventLog(`Stream #${number} closed`);
  } catch (e) {
    addToEventLog(`Error while reading from stream #${number}: ${e}`, 'error');
    addToEventLog(`    ${e.message}`);
  }
}

function addToEventLog(text, severity = 'info') {
  const log = document.getElementById('event-log');
  const previous = log.lastElementChild;
  const entry = document.createElement('li');
  entry.innerText = text;
  entry.className = `log-${severity}`;
  log.appendChild(entry);

  // If the previous entry in the log was visible, scroll to the new element.
  if (previous &&
      previous.getBoundingClientRect().top < log.getBoundingClientRect().bottom) {
    entry.scrollIntoView();
  }
}

14. Acknowledgements

The editors wish to thank the Working Group chairs and Team Contact, Jan-Ivar Bruaroey, Will Law and Yves Lafon, for their support.

The WebTransport interface is based on the QuicTransport interface initially described in the W3C ORTC CG, and has been adapted for use in this specification.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT-6.0]
Allen Wirfs-Brock. ECMA-262 6th Edition, The ECMAScript 2015 Language Specification. June 2015. Standard. URL: http://www.ecma-international.org/ecma-262/6.0/index.html
[ENCODING]
Anne van Kesteren. Encoding Standard. Living Standard. URL: https://encoding.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[HR-TIME-2]
Ilya Grigorik. High Resolution Time Level 2. 21 November 2019. REC. URL: https://www.w3.org/TR/hr-time-2/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[QUIC]
Jana Iyengar; Martin Thomson. QUIC: A UDP-Based Multiplexed and Secure Transport. Internet-Draft. URL: https://tools.ietf.org/html/draft-ietf-quic-transport
[QUIC-DATAGRAM]
Tommy Pauly; Eric Kinnear; David Schinazi. An Unreliable Datagram Extension to QUIC. Internet-Draft. URL: https://datatracker.ietf.org/doc/html/draft-ietf-quic-datagram/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[RFC5280]
D. Cooper; et al. Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile. May 2008. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc5280
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WEB-TRANSPORT-HTTP3]
Victor Vasiliev. WebTransport over HTTP/3. Internet-Draft. URL: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/
[WEB-TRANSPORT-OVERVIEW]
Victor Vasiliev. WebTransport Protocol Framework. Internet-Draft. URL: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-overview
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/
[WHATWG-STREAMS]
Adam Rice; et al. Streams Standard. Living Standard. URL: https://streams.spec.whatwg.org/

Informative References

[RFC7301]
S. Friedl; et al. Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension. July 2014. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc7301
[RFC8446]
E. Rescorla. The Transport Layer Security (TLS) Protocol Version 1.3. August 2018. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc8446

IDL Index

[Exposed=(Window,Worker), SecureContext]
interface WebTransportDatagramDuplexStream {
  readonly attribute ReadableStream readable;
  readonly attribute WritableStream writable;

  readonly attribute unsigned long maxDatagramSize;
  attribute double? incomingMaxAge;
  attribute double? outgoingMaxAge;
  attribute long incomingHighWaterMark;
  attribute long outgoingHighWaterMark;
};

[Exposed=(Window,Worker), SecureContext]
interface WebTransport {
  constructor(USVString url, optional WebTransportOptions options = {});

  Promise<WebTransportStats> getStats();
  readonly attribute Promise<undefined> ready;
  readonly attribute Promise<WebTransportCloseInfo> closed;
  undefined close(optional WebTransportCloseInfo closeInfo = {});

  readonly attribute WebTransportDatagramDuplexStream datagrams;

  Promise<WebTransportBidirectionalStream> createBidirectionalStream();
  /* a ReadableStream of WebTransportBidirectionalStream objects */
  readonly attribute ReadableStream incomingBidirectionalStreams;

  Promise<WritableStream> createUnidirectionalStream();
  /* a ReadableStream of ReceiveStreams */
  readonly attribute ReadableStream incomingUnidirectionalStreams;
};


dictionary WebTransportHash {
  DOMString algorithm;
  BufferSource value;
};

dictionary WebTransportOptions {
  boolean allowPooling;
  sequence<WebTransportHash> serverCertificateHashes;
};

dictionary WebTransportCloseInfo {
  unsigned long closeCode;
  DOMString reason;
};

dictionary WebTransportStats {
  DOMHighResTimeStamp timestamp;
  unsigned long long bytesSent;
  unsigned long long packetsSent;
  unsigned long numOutgoingStreamsCreated;
  unsigned long numIncomingStreamsCreated;
  unsigned long long bytesReceived;
  unsigned long long packetsReceived;
  DOMHighResTimeStamp minRtt;
  unsigned long numReceivedDatagramsDropped;
};

[Exposed=(Window,Worker), SecureContext]
interface WebTransportBidirectionalStream {
  readonly attribute ReadableStream readable;
  readonly attribute WritableStream writable;
};

[Exposed=(Window,Worker), SecureContext]
interface WebTransportError : DOMException {
  constructor(optional WebTransportErrorInit init = {});

  readonly attribute WebTransportErrorSource source;
  readonly attribute octet? streamErrorCode;
};

dictionary WebTransportErrorInit {
  [Clamp] octet streamErrorCode;
  DOMString message;
};

enum WebTransportErrorSource {
  "stream",
  "session",
};

Issues Index

This needs to be done in workers too. See #127 and whatwg/html#6731.
Reconsider the time period above. We want it to be sufficiently large that applications using this for ephemeral certificates can do so without having to fight the clock skew, but small enough to discourage long-term use without key rotation.
Now that quic-transport has been removed, this section needs to be revised. Some of those are safe to expose for HTTP/2 and HTTP/3 connections (like min-RTT), while most would either result in information disclosure or are impossible to define for pooled connections.