Add tutorial.rst -- forgotten in the previous commit

This commit is contained in:
Dmitri Tikhonov 2020-03-02 08:57:22 -05:00
parent afe3d36359
commit aedecb458e

221
docs/tutorial.rst Normal file
View file

@ -0,0 +1,221 @@
********
Tutorial
********
.. highlight:: c
Introduction
============
The LSQUIC library provides facilities for operating a QUIC (Google QUIC
or IETF QUIC) server or client with optional HTTP (or HTTP/3) functionality.
To do that, it specifies an application programming interface (API) and
exposes several basic object types to operate upon:
- engine;
- connection; and
- stream.
An engine manages connections, processes incoming packets, and schedules
outgoing packets. An engine operates in one of two modes: client or server.
The LSQUIC library does not use sockets to receive and send packets; that is
handled by the user-supplied callbacks. The library also does not mandate
the use of any particular event loop. Instead, it has functions to help the
user schedule events. (Thus, using an event loop is not even strictly
necessary.) The various callbacks and settings are supplied to the engine
constructor.
A connection carries one or more streams, ensures reliable data delivery,
and handles the protocol details.
A stream usually corresponds to a request/response pair: a client sends
its request over a single stream and a server sends its response back
using the same stream. This is the Google QUIC and HTTP/3 use case.
Nevertheless, the library does not limit one to this scenario. Any
application protocol can be implemented using LSQUIC -- as long as it
can be implemented using the QUIC transport protocol. The library provides
hooks for stream events: when a stream is created or closed, when it has
data to read or when it can be written to, and so on.
In the following sections, we will describe how to:
- initialize the library;
- configure and instantiate an engine object;
- send and receive packets; and
- work with connections and streams.
Include Files
-------------
A single include file, :file:`lsquic.h`, contains all the necessary
LSQUIC declarations:
::
#include <lsquic.h>
Library Initialization
======================
Before the first engine object is instantiate, the library must be
initialized using :func:`lsquic_global_init()`:
::
if (0 != lsquic_global_init(LSQUIC_GLOBAL_CLIENT|LSQUIC_GLOBAL_SERVER))
{
exit(EXIT_FAILURE);
}
/* OK, do something useful */
If you plan to instantiate engines only in a single mode, client or server,
you can omit the appropriate flag.
After all engines have been destroyed and the LSQUIC library is no longer
going to be used, the global initialization can be undone:
::
lsquic_global_cleanup();
exit(EXIT_SUCCESS);
Engine Instantiation
====================
Engine instantiation is performed by :func:`lsquic_engine_new()`:
::
/* Create an engine in server mode with HTTP behavior: */
lsquic_engine_t *engine
= lsquic_engine_new(LSENG_SERVER|LSENG_HTTP, &engine_api);
The engine mode is selected by using the ``LSENG_SERVER`` flag. If
present, the engine will be in server mode; if not, the engine will
be in client mode.
Using the ``LSENG_HTTP`` flag enables the HTTP behavior: The library
hides the interaction between the HTTP application layer and the QUIC
transport layer and presents a simple, unified (between Google QUIC and
HTTP/3) way of sending and receiving HTTP messages. Behind the scenes,
the library will compress and uncompress HTTP headers, add and remove
HTTP/3 stream framing, and operate the necessary control streams.
Engine Configuration
--------------------
The second argument to :func:`lsquic_engine_new()` is a pointer to
a struct of type :type:`lsquic_engine_api`. This structure lists
several user-specified function pointers that the engine is to use
to perform various functions. Mandatory among these are:
- function to set packets out, :member:`lsquic_engine_api.ea_packets_out`;
- functions linked to connection and stream events,
:member:`lsquic_engine_api.ea_stream_if`;
- function to look up certificate to use, :member:`lsquic_engine_api.ea_lookup_cert` (in server mode); and
- function to fetch SSL context, :member:`lsquic_engine_api.ea_get_ssl_ctx` (in server mode).
The minimal structure for a client will look like this:
::
lsquic_engine_api engine_api = {
.ea_packets_out = send_packets_out,
.ea_packets_out_ctx = (void *) sockfd, /* For example */
.ea_stream_if = &stream_callbacks,
.ea_stream_if_ctx = &some_context,
};
Engine Settings
---------------
Engine settings can be changed by specifying
:member:`lsquic_engine_api.ea_settings`. There are **many** parameters
to tweak: supported QUIC versions, amount of memory dedicated to connections
and streams, various timeout values, and so on. See
:ref:`apiref-engine-settings` for full details. If ``ea_settings`` is set
to ``NULL``, the engine will use the defaults, which should be OK.
Sending Packets
===============
The :member:`lsquic_engine_api.ea_packets_out` is the function that gets
called when an engine instance has packets to send. It could look like
this:
::
/* Return number of packets sent or -1 on error */
static int
send_packets_out (void *ctx, const struct lsquic_out_spec *specs,
unsigned n_specs)
{
struct msghdr msg;
int sockfd;
unsigned n;
memset(&msg, 0, sizeof(msg));
sockfd = (int) (uintptr_t) ctx;
for (n = 0; n < n_specs; ++n)
{
msg.msg_name = (void *) specs[n].dest_sa;
msg.msg_namelen = sizeof(struct sockaddr_in);
msg.msg_iov = specs[n].iov;
msg.msg_iovlen = specs[n].iovlen;
if (sendmsg(sockfd, &msg, 0) < 0)
break;
}
return (int) n;
}
Note that the version above is very simple. :type:`lsquic_out_spec`
also specifies local address as well as ECN value. These are set
using ancillary data in a platform-dependent way.
Receiving Packets
=================
The user reads packets and provides them to an engine instance using
:func:`lsquic_engine_packet_in()`.
*TODO*
Running Connections
===================
A connection needs to be processed once in a while. It needs to be
processed when one of the following is true:
- There are incoming packets;
- A stream is both readable by the user code and the user code wants
to read from it;
- A stream is both writeable by the user code and the user code wants
to write to it;
- User has written to stream outside of on_write() callbacks (that is
allowed) and now there are packets ready to be sent;
- A timer (pacer, retransmission, idle, etc) has expired;
- A control frame needs to be sent out;
- A stream needs to be serviced or created.
Each of these use cases is handled by a single function,
:func:`lsquic_engine_process_conns()`.
The connections to which the conditions above apply are processed (or
"ticked") in the least recently ticked order. After calling this function,
you can see when is the next time a connection needs to be processed using
:func:`lsquic_engine_earliest_adv_tick()`.
Based on this value, next event can be scheduled (in the event loop of
your choice).
::
Stream Reading and Writing
==========================
Reading from (or writing to) a stream is best down when that stream is
readable (or writeable). To register an interest in an event,