Pico Web Transport

Implementation of the web transport protocol on top of picoquic and h3zero implementation of HTTP3.

The in-tree WebTransport CTest labels and strict versus compatibility policy are documented in webtransport_conformance.md. Browser smoke, browser E2E, and WPT adapter coverage is maintained in the separate h3browserconformance repository. Picoquic CI only gates the external WPT adapter smoke against the small in-tree pico_baton; full browser draft-compatibility testing stays in that separate repository. Picoquic does not advertise the draft WebTransport session-level flow-control SETTINGS or capsules; it relies on QUIC flow control and keeps a single WebTransport session on each QUIC connection.

Pico Web Transport architecture

The web transport implementation runs on top of the “h3zero” implementation of HTTP3, which itself runs on top of Quic/picoquic. The application interfaces with the web transport API, and receives data through a callback.

+-----------------------+                    +-----------------------+
| client app            |                    | server app            |
| H3zero API / Callback |                    | H3zero API / Callback |
|   |             ^     |                    |   |             ^     |
+---|-------------|-----+                    +---|-------------|-----+
    |             |                              |             |
+---|-------------|-----+                    +---|-------------|-----+
|   v             |     |                    |   v             |     |
|H3zero/WebTransport    |                    |H3zero/WebTransport    |
|Picoquic API / Callback|                    |Picoquic API / Callback|
|   |             ^     |                    |   |             ^     |
+---|-------------|-----+                    +---|-------------|-----+ 
    |             |                              |             |
+---|-------------|-----+                    +---|-------------|-----+
|   v             |     |                    |   v             |     |
|      Picoquic         |                    |      Picoquic         |
+-----------------------+                    +-----------------------+

The web transport server is actually an HTTP server, augmented with support for web transport primitives. Establishing a web transport connection requires first establishing an HTTP3 connection, handled by the H3zero server code, then using the “Connect Web transport” method on the client to “upgrade” the HTTP3 connection to a web transport connection. The connect request is sent over a QUIC/HTTP3 stream. That stream needs to remain open for the duration of the session – closing it closes the session.

Client and server can then open QUIC streams, over which they run the protocol of their choice. The QUIC streams opened by web transport start with a “stream prefix”, which differentiate them from regular HTTP3 streams. For web transport, the stream prefix is set to the identifier of the “control stream”, i.e., the stream over which the connect request was sent. The HTTP3 server processes the incoming streams. It will direct these streams to a web transport session if it recognizes their prefix, or treat them as regular HTTP traffic otherwise.

There is state attached to sessions, such as remembering open streams. In the picoquic Web Transport implementation, the session state is an object of type picowt_session_ctx-t, which is allocated by the application on the client side when starting the connect call, and created by the server on the server side when processing that call. The allocation is recorded in the table of stream prefixes in the H3Zero connection context. The table is keyed by the ID of the control stream for the session.

Preparing an H3 server to accept Web Transport sessions

To support web transport, a server must be ready to accept H3 connections, and then configured to accept web transport connections over that. There is an example of such code in the quic__server function of picoquic_demo.c. To simplify, the requirements are:

Setting Web Transport sessions on a client

To set up a web transport connection, the client needs to first create a connection to the target web server, then attach a web transport session to that connection. In picoquic, this requires:

The function wt_baton_client in baton_app_.c provides an example of a web transport client.

Web transport API

The web transport application will interact with the web transport and the QUIC stack in three ways:

Setting up a web transport session on the client

The web transport connection is set in five phases:

1- Create an h3zero stream context for the control stream, using the API picowt_set_control_stream.

2- Prepare the application state before the connection. This may include documenting the control stream context.

3- Call the picowt_connect API to prepare and queue the web transport connect message. The API takes the following parameters:

4- Start or continue the H3 connection so the deferred CONNECT can be sent after validation.

5- Make sure that the application is ready to process incoming streams.

The function wt_baton_connect in wt_baton.c provides an example of setting the web transport session on the client._

Web transport callback

The web transport callback API is defined as picohttp_post_data_cb_fn in h3zero_common.h. The enumeration picohttp_call_back_event_t defines the following callback events:

        picohttp_callback_get, /* Received a get command */
        picohttp_callback_post, /* Received a post command */
        picohttp_callback_connecting, /* Sending out a connect command */
        picohttp_callback_connect, /* Received a connect command */
        picohttp_callback_connect_refused, /* Connection request was refused by peer */
        picohttp_callback_connect_accepted, /* Connection request was accepted by peer */
        picohttp_callback_post_data, /* Data received from peer on stream N */
        picohttp_callback_post_fin, /* All posted data have been received on this stream */
        picohttp_callback_provide_data, /* Stack is ready to send chunk of data on stream N */
        picohttp_callback_post_datagram, /* Datagram received on this context */
        picohttp_callback_provide_datagram, /* Ready to send datagram in this context */
        picohttp_callback_reset, /* Stream has been abandoned by peer. */
        picohttp_callback_stop_sending, /* Peer asking to reset the stream. */
        picohttp_callback_deregister, /* Context has been deregistered */
        picohttp_callback_free,
        picohttp_callback_drain /* Peer initiated graceful WebTransport drain. */

The callback definition is generic – it is used for any kind of web server extension defined by connecting an URL path with a processor. Apart from web transport, it is currently used to process HTTP Post requests. That’s why the list includes the get and post events, which are not used by web transport.

An example of callback implementation is provided in wt_baton_callback in wt_baton.c._

WT Protocol negotiation

The web transport protocol is negotiated as part of the HTTP3 connection. The client proposes a list of available protocols as an argument to “picowt_connect”:

    int picowt_connect(picoquic_cnx_t* cnx, h3zero_callback_ctx_t* ctx,
    h3zero_stream_ctx_t* stream_ctx, const char* authority, const char* path, 
    picohttp_post_data_cb_fn wt_callback, void* wt_ctx,
    char const* wt_available_protocols);

The API argument is a comma-separated list of protocol identifiers, for example "protocol1, protocol2, protocol3". H3zero encodes this as a WT-Available-Protocols Structured Fields list of strings on the wire.

The server selects the protocol by calling the picowt_select_wt_protocol API:

int picowt_select_wt_protocol(h3zero_stream_ctx_t* stream_ctx, char const* supported);

The argument supported is the list of protocols supported by the server, encoded using the same format as the list of protocols proposed by the client, for example: “protocol0, protocol2, protocol3”. The code will select the first protocol in the client list that is also supported by the server, “protocol2” in our example. The selected value is copied in the stream context of the web transport session (look for the ps.session_state.wt_protocol entry in the stream context).

Creating streams

Once the session is created, client and server will be able to open “local” streams, i.e., client initiated streams on the client or server initiated on the servers.

These streams should be called by calling the function picowt_create_local_stream with parameters:

Once streams are created, data can be sent pretty much like for plain QUIC applications.

Sending datagrams

Once the session is created or accepted by both peers, an application may send and receive datagrams. An application signals its desire to send datagrams by calling the function h3zero_set_datagram_ready defined in h3zero_common.h with two parameters:

When the stack is ready to send a datagram, it will issue a callback that is relayed to the web transport user as picohttp_callback_provide_datagram. The process for sending datagrams is very similar to the process with raw QUIC, but to acquire a datagram buffer it uses the function h3zero_provide_datagram_buffer with parameters:

An example of sending datagrams can be found in the function wt_baton_provide_datagram in wt_baton.c.

When a datagram is ready, the application will receive a callback picohttp_callback_post_datagram in which the arguments bytes and length provide the value and length of the received datagram.

The raw QUIC callbacks picoquic_callback_datagram_acked, picoquic_callback_datagram_lost, and picoquic_callback_datagram_spurious are not propagated to the Web Transport application. (Not impossible, but nobody has asked for them yet.)

Running a web transport and a raw QUIC server in the same process

It is possible to create a server that handles both “raw” QUIC connections and “web transport” connections. All these connections will share a single QUIC context and a single UDP port. The requirements are:

There is an example of this process in demoserver.c, with the ALPN selection function picoquic_demo_server_callback_select_alpn and the redirection callback picoquic_demo_server_callback.