Skip to content

Commit

Permalink
Merge pull request #1549 from private-octopus/web-transport-doc
Browse files Browse the repository at this point in the history
Web transport doc
  • Loading branch information
huitema authored Sep 23, 2023
2 parents 2be9a4c + a184379 commit 9b319be
Show file tree
Hide file tree
Showing 22 changed files with 564 additions and 268 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ else()
endif()

project(picoquic
VERSION 1.1.11.2
VERSION 1.1.12.0
DESCRIPTION "picoquic library"
LANGUAGES C CXX)

Expand Down
22 changes: 9 additions & 13 deletions baton_app/baton_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ int baton_client_loop_cb(picoquic_quic_t* quic, picoquic_packet_loop_cb_enum cb_
static void usage(char const * sample_name)
{
fprintf(stderr, "Usage:\n");
fprintf(stderr, " %s server_name port path [nb_rounds]\n", sample_name);
fprintf(stderr, " %s server_name port path\n", sample_name);
fprintf(stderr, "The path argument may include parameters:\n");
fprintf(stderr, " - version: baton protocol version,\n");
fprintf(stderr, " - baton: initial version value,\n");
fprintf(stderr, " - count: number of rounds,\n");
fprintf(stderr, " - inject: inject error for testing\n");
fprintf(stderr, "For example, set a path like /baton?count=17 to have 17 rounds of baton exchange.");
exit(1);
}

Expand All @@ -67,23 +73,13 @@ int main(int argc, char** argv)
(void)WSA_START(MAKEWORD(2, 2), &wsaData);
#endif

if (argc < 4 || argc > 5) {
if (argc != 4) {
usage(argv[0]);
}
else {
char const* server_name = argv[1];
int server_port = get_port(argv[0], argv[2]);
char const * path = argv[3];
int nb_rounds = 15;
if (argc == 5) {
char* end_of_int = NULL;
nb_rounds = (int)strtol(argv[4], &end_of_int, 10);
if (nb_rounds < 0 || end_of_int == NULL || *end_of_int != 0 ||
(end_of_int - argv[4]) > 3) {
fprintf(stderr, "Invalid number of rounds: %s\n", argv[4]);
usage(argv[0]);
}
}

ret = wt_baton_client(server_name, server_port, path);

Expand Down Expand Up @@ -242,7 +238,7 @@ int wt_baton_client(char const * server_name, int server_port, char const * path
printf("Final baton state: %d\n", baton_ctx.baton_state);
printf("Nb turns: %d\n", baton_ctx.nb_turns);
/* print statistics per lane */
for (size_t i = 0; i < baton_ctx.count; i++) {
for (size_t i = 0; i < baton_ctx.nb_lanes; i++) {
printf("Lane %zu, first baton: 0x%02x, last sent: 0x%02x, last received: 0x%02x\n", i,
baton_ctx.lanes[i].first_baton, baton_ctx.lanes[i].baton, baton_ctx.lanes[i].baton_received);
}
Expand Down
237 changes: 224 additions & 13 deletions doc/pico_webtransport.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,235 @@

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

# Life time of sessions
## Pico Web Transport architecture

A session starts when the client issues a "connect" request, or when a server receives and accepts it.
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.

The session state is an object of type `picowt_session_ctx-t`, which is allocated by the application
on the client side, and automatically by the server on the server side. 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.
~~~
+-----------------------+ +-----------------------+
| 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 session state is deleted when the entry for the control stream is removed from the table of stream
prefixes. The stack or the application can do that at anytime.
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.

If the session state is deleted, all corresponding streams will be closed, or reset.
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.

# Per stream context
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.

The application can attach a per stream context to the H3 stream context. This can be done at any time.
The application will be informed of stream closures, and must manage the memory allocated for the context.
### 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:

* Prepare a picoquic context ready to accept HTTP3 requests. In `picoquicdemo`,
this is done by setting the callback to `picoquic_demo_server_callback`,
defined in `demoserver.c`. That callback provides support for 4 different
protocols: HTTP3, HTTP/0.9, SIDUCK and QUICPERF. If you only want to support
HTTP3, you can set the callback function to `h3zero_callback`, defined in `

* Configure the HTTP3 server to accept and process Web Transport requests by
configuring a "path table". Each path specifies a local URL, a path callback
function, and a path callback context, per `picohttp_server_path_item_t`
in `h3zero_common.h`.

* Run the server socket loop connected to the picoquic context.

### 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:

* Creating a client side quic context

* Creating a connection with the ALPN set to "H3" and the callback
set to `h3zero_callback` defined in `h3zero_common.h`.

* Create a stream in the connection.

* Call the API `picowt_connect` defined in `pico_webtransport.h`

* Run the server socket loop connected to the picoquic context.

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 the Web Transport context by calling `picowt_connect` on the
client,

* Opening and closing streams,

* Responding to "path" callbacks from the web stack.

### Setting up a web transport session on the client

The web transport connection is set in four 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:

- `cnx`: QUIC connection context
- `stream_ctx`: the stream context returned by `picowt_set_control_stream`
- `path`: the path parameter for the connect request
- `wt_callback`: the path callback used for the application
- `wt_ctx`: the web transport application context associated with the path callback

4- 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_deregister, /* Context has been deregistered */
picohttp_callback_free
~~~

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`._

### 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:

- `cnx`: QUIC connection context
- `h3_ctx`: the h3zero context for the connection
- `control_stream_id`: the stream_id of the control stream for the web transport session.

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:

- `cnx`: QUIC connection context,
- `stream_id`: Stream ID of the control stream for the web transport session.

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:

- `context`: must be set to the value of the argument `bytes` of the
`picohttp_callback_provide_datagram` callback.
- `length`: the length of the datagram prepared by the application, which must be
lower than or equal to the value of the argument `length` of the
`picohttp_callback_provide_datagram` callback.
- `ready_to_send`: whether the application is ready to send more datagrams.

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:

- develop an ALPN selection function of type `picoquic_alpn_select_fn`,
as specified in `picoquic.h`, then call `picoquic_set_alpn_select_fn`
to attach the ALPN selection function to the QUIC context.
- develop two callback functions, one of type `picoquic_stream_data_cb_fn`
for the "raw" connections, and another of type `picohttp_post_data_cb_fn`
for the "web transport" sessions.
- develop a third callback function that will only be set as default callback
for the QUIC context. This function will only be used for the first
callback for an incoming connection. The function should retrieve the
ALPN of the incoming connection using the API `picoquic_tls_get_negotiated_alpn`,
and then relay the call to the appropriate callback, `h3zero_callback` if this
is an HTTP3 connection, or the raw callback of the application if this
is a call to the ALPN of the application protocol.

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`.

# Key functions

45 changes: 1 addition & 44 deletions picohttp/democlient.c
Original file line number Diff line number Diff line change
Expand Up @@ -162,49 +162,6 @@ static picoquic_demo_client_stream_ctx_t* picoquic_demo_client_find_stream(
return stream_ctx;
}


int demo_client_prepare_to_send(void * context, size_t space, uint64_t echo_length, uint64_t * echo_sent, FILE * F)
{
int ret = 0;

if (*echo_sent < echo_length) {
uint8_t * buffer;
uint64_t available = echo_length - *echo_sent;
int is_fin = 1;

if (available > space) {
available = space;
is_fin = 0;
}

buffer = picoquic_provide_stream_data_buffer(context, (size_t)available, is_fin, !is_fin);
if (buffer != NULL) {
if (F) {
size_t nb_read = fread(buffer, 1, (size_t)available, F);

if (nb_read != available) {
ret = -1;
}
else {
*echo_sent += available;
ret = 0;
}
}
else {
/* TODO: fill buffer with some text */
memset(buffer, 0x5A, (size_t)available);
*echo_sent += available;
ret = 0;
}
}
else {
ret = -1;
}
}

return ret;
}

/* HTTP 0.9 client.
* This is the client that was used for QUIC interop testing prior
* to availability of HTTP 3.0. It allows for testing transport
Expand Down Expand Up @@ -640,7 +597,7 @@ int picoquic_demo_client_callback(picoquic_cnx_t* cnx,
return 0;
}
else {
return demo_client_prepare_to_send((void*)bytes, length, stream_ctx->post_size, &stream_ctx->post_sent, NULL);
return h3zero_prepare_and_send_data((void*)bytes, length, stream_ctx->post_size, &stream_ctx->post_sent, NULL);
}
case picoquic_callback_almost_ready:
case picoquic_callback_ready:
Expand Down
2 changes: 0 additions & 2 deletions picohttp/democlient.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@ int picoquic_demo_client_get_alpn_and_version_from_tickets(picoquic_quic_t* quic
char const* sni, char const* alpn, uint32_t proposed_version, uint64_t current_time,
char const** ticket_alpn, uint32_t* ticket_version);

int demo_client_prepare_to_send(void * context, size_t space, uint64_t echo_length, uint64_t * echo_sent, FILE * F);

int h09_demo_client_prepare_stream_open_command(
uint8_t * command, size_t max_size, uint8_t const* path, size_t path_len, uint64_t post_size, const char * host, size_t * consumed);

Expand Down
Loading

0 comments on commit 9b319be

Please sign in to comment.