mirror of
				https://gitea.invidious.io/iv-org/litespeed-quic.git
				synced 2024-08-15 00:53:43 +00:00 
			
		
		
		
	Release 2.30.0
- [FEATURE] Added support for sending/receiving multiple headers to address the case related to "100 continue" header handling. - [BUGFIX] Addressed high CPU usage for a GOAWAY connection before sending CONNECTION_CLOSE. - [BUGFIX] Addressed SIGFPE due to zero pacing rate. (ISSUE #254). - [BUGFIX] Fixed a minor issue related to multi-paths.
This commit is contained in:
		
							parent
							
								
									ab69788e51
								
							
						
					
					
						commit
						293df8d66b
					
				
					 16 changed files with 815 additions and 125 deletions
				
			
		| 
						 | 
				
			
			@ -1,3 +1,12 @@
 | 
			
		|||
2021-04-12
 | 
			
		||||
    - 2.30.0
 | 
			
		||||
    - Added support for sending/receiving multiple headers to address the
 | 
			
		||||
      case related to "100 continue" header handling.
 | 
			
		||||
    - Addressed high CPU usage for a GOAWAY connection before sending
 | 
			
		||||
      CONNECTION_CLOSE.
 | 
			
		||||
    - Addressed SIGFPE due to zero pacing rate. (ISSUE #254).
 | 
			
		||||
    - Fixed a minor issue related to multi-paths.
 | 
			
		||||
 | 
			
		||||
2021-03-31
 | 
			
		||||
    - 2.29.6
 | 
			
		||||
    - Documentation: describe lsquic internals ("guts").
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -585,13 +585,14 @@ send_headers (lsquic_stream_ctx_t *st_h)
 | 
			
		|||
    if (!hostname)
 | 
			
		||||
        hostname = st_h->client_ctx->prog->prog_hostname;
 | 
			
		||||
    hbuf.off = 0;
 | 
			
		||||
    struct lsxpack_header headers_arr[9];
 | 
			
		||||
    struct lsxpack_header headers_arr[10];
 | 
			
		||||
#define V(v) (v), strlen(v)
 | 
			
		||||
    header_set_ptr(&headers_arr[h_idx++], &hbuf, V(":method"), V(st_h->client_ctx->method));
 | 
			
		||||
    header_set_ptr(&headers_arr[h_idx++], &hbuf, V(":scheme"), V("https"));
 | 
			
		||||
    header_set_ptr(&headers_arr[h_idx++], &hbuf, V(":path"), V(st_h->path));
 | 
			
		||||
    header_set_ptr(&headers_arr[h_idx++], &hbuf, V(":authority"), V(hostname));
 | 
			
		||||
    header_set_ptr(&headers_arr[h_idx++], &hbuf, V("user-agent"), V(st_h->client_ctx->prog->prog_settings.es_ua));
 | 
			
		||||
    //header_set_ptr(&headers_arr[h_idx++], &hbuf, V("expect"), V("100-continue"));
 | 
			
		||||
    if (randomly_reprioritize_streams)
 | 
			
		||||
    {
 | 
			
		||||
        char pfv[10];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								docs/conf.py
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								docs/conf.py
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -24,9 +24,9 @@ copyright = u'2021, LiteSpeed Technologies'
 | 
			
		|||
author = u'LiteSpeed Technologies'
 | 
			
		||||
 | 
			
		||||
# The short X.Y version
 | 
			
		||||
version = u'2.29'
 | 
			
		||||
version = u'2.30'
 | 
			
		||||
# The full version, including alpha/beta/rc tags
 | 
			
		||||
release = u'2.29.6'
 | 
			
		||||
release = u'2.30.0'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- General configuration ---------------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +39,12 @@ release = u'2.29.6'
 | 
			
		|||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 | 
			
		||||
# ones.
 | 
			
		||||
extensions = [
 | 
			
		||||
# To make ours look like readthedocs.io, change theme to "sphinx_rtd_theme",
 | 
			
		||||
# pip install sphinx_rtd_theme, and uncomment extensions:
 | 
			
		||||
#    "sphinx.ext.intersphinx",
 | 
			
		||||
#    "sphinx.ext.autodoc",
 | 
			
		||||
#    "sphinx.ext.mathjax",
 | 
			
		||||
#    "sphinx.ext.viewcode",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# Add any paths that contain templates here, relative to this directory.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,7 +38,7 @@ Fetch Google home page:
 | 
			
		|||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    ./http_client -s www.google.com -p / -o version=Q050
 | 
			
		||||
    ./http_client -s www.google.com -p / -o version=h3-29
 | 
			
		||||
 | 
			
		||||
Run your own server (it does not touch the filesystem, don't worry):
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,16 +22,6 @@ Code Version
 | 
			
		|||
 | 
			
		||||
The code version under discussion is v2.29.6.
 | 
			
		||||
 | 
			
		||||
High-Level Structure
 | 
			
		||||
********************
 | 
			
		||||
 | 
			
		||||
At a high level, the lsquic library can be used to instantiate an engine
 | 
			
		||||
(or several engines). An engine manages connections; each connection has
 | 
			
		||||
streams. Engine, connection, and stream objects are exposed to the user
 | 
			
		||||
who interacts with them using the API (see :doc:`apiref`). All other data
 | 
			
		||||
structures are internal and are hanging off, in one way or another, from
 | 
			
		||||
the engine, connection, or stream objects.
 | 
			
		||||
 | 
			
		||||
Coding Style
 | 
			
		||||
************
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -97,6 +87,21 @@ List of Common Terms
 | 
			
		|||
-  **iQUIC** This stands for IETF QUIC. To differentiate between gQUIC
 | 
			
		||||
   and IETF QUIC, we use ``iquic`` in some names and types.
 | 
			
		||||
 | 
			
		||||
-  **Public Reset** In the IETF QUIC parlance, this is called the *stateless*
 | 
			
		||||
   reset.  Because gQUIC was first to be implemented, this name is still
 | 
			
		||||
   used in the code, even when the IETF QUIC stateless reset is meant.
 | 
			
		||||
   You will see names that contain strings like "prst" and "pubres".
 | 
			
		||||
 | 
			
		||||
High-Level Structure
 | 
			
		||||
********************
 | 
			
		||||
 | 
			
		||||
At a high level, the lsquic library can be used to instantiate an engine
 | 
			
		||||
(or several engines). An engine manages connections; each connection has
 | 
			
		||||
streams. Engine, connection, and stream objects are exposed to the user
 | 
			
		||||
who interacts with them using the API (see :doc:`apiref`). All other data
 | 
			
		||||
structures are internal and are hanging off, in one way or another, from
 | 
			
		||||
the engine, connection, or stream objects.
 | 
			
		||||
 | 
			
		||||
Engine
 | 
			
		||||
******
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -573,11 +578,11 @@ dedicated chapters elsewhere in this document:
 | 
			
		|||
 | 
			
		||||
-  `Mini gQUIC Connection <#mini-gquic-connection>`__
 | 
			
		||||
 | 
			
		||||
-  `Full gQUIC Connection <#connection-public-interface>`__
 | 
			
		||||
-  `Full gQUIC Connection <#full-gquic-connection>`__
 | 
			
		||||
 | 
			
		||||
-  `Mini IETF QUIC Connection <#mini-ietf-connection>`__
 | 
			
		||||
 | 
			
		||||
-  `Full IETF QUIC Connection <#mini-ietf-connection>`__
 | 
			
		||||
-  `Full IETF QUIC Connection <#full-ietf-connection>`__
 | 
			
		||||
 | 
			
		||||
-  `Evanescent Connection <#evanescent-connection>`__
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -619,7 +624,7 @@ Various list and heap connectors
 | 
			
		|||
 | 
			
		||||
A connection may be pointed to by one or several queues and heaps (see
 | 
			
		||||
"\ `Connection Management <#connection-management>`__\ "). There are
 | 
			
		||||
several struct members that make it possible: all the \*TAILQ_ENTRYs,
 | 
			
		||||
several struct members that make it possible: \*TAILQ_ENTRYs,
 | 
			
		||||
``cn_attq_elem``, and ``cn_cert_susp_head``.
 | 
			
		||||
 | 
			
		||||
Version
 | 
			
		||||
| 
						 | 
				
			
			@ -1135,12 +1140,156 @@ size will be written by a different function.
 | 
			
		|||
Parsing
 | 
			
		||||
*******
 | 
			
		||||
 | 
			
		||||
*Files: lsquic_parse.h, lsquic_parse_ietf_v1.c, lsquic_parse_Q050.c, lsquic_parse_Q046.c,
 | 
			
		||||
lsquic_parse_gquic_be.c, lsquic_parse_common.c, and others*
 | 
			
		||||
 | 
			
		||||
Overview
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
The two types of QUIC -- gQUIC and IETF QUIC -- have different packet and
 | 
			
		||||
frame formats.  In addition, different gQUIC version are different among
 | 
			
		||||
themselves.  Functions to parse and generate packets and frames of each
 | 
			
		||||
type are abstracted out behind the rather large ``struct parse_funcs``.
 | 
			
		||||
When a connection is created, its ``cn_pf`` member is set to point to
 | 
			
		||||
the correct set of function pointers via the ``select_pf_by_ver()`` macro.
 | 
			
		||||
 | 
			
		||||
Parsing Packets
 | 
			
		||||
===============
 | 
			
		||||
 | 
			
		||||
Before settling on a particular set of parsing function for a connection,
 | 
			
		||||
the server needs to determine the connection's version.  It does so using
 | 
			
		||||
the function ``lsquic_parse_packet_in_server_begin()``.
 | 
			
		||||
 | 
			
		||||
This function figures out whether the packet has a long or a short header,
 | 
			
		||||
and which QUIC version it is.  Because the server deals with fewer packet
 | 
			
		||||
types than the client (no version negotiation or stateless retry packets),
 | 
			
		||||
it can determine the necessary parsing function from the first byte of the
 | 
			
		||||
incoming packet.
 | 
			
		||||
 | 
			
		||||
The "begin" in the name of the function refers to the fact that packet
 | 
			
		||||
parsing is a two-step process [3]_.  In the first step, the packet version,
 | 
			
		||||
CID, and some other parameters are parsed out; in the second step,
 | 
			
		||||
version-specific ``pf_parse_packet_in_finish()`` is called to parse out
 | 
			
		||||
the packet number.  Between the two calls, the state is saved in
 | 
			
		||||
``struct packin_parse_state``.
 | 
			
		||||
 | 
			
		||||
Generating Packets
 | 
			
		||||
==================
 | 
			
		||||
 | 
			
		||||
Packets are generated during encryption using the ``pf_gen_reg_pkt_header()``
 | 
			
		||||
function.  The generated header is encrypted together with the `packet payload`_
 | 
			
		||||
and this becomes the QUIC packet that is sent out.  (Most of the time, the
 | 
			
		||||
QUIC packet corresponds to the UDP datagram, but sometimes packets are
 | 
			
		||||
`coalesced <#packet-coalescing>`__.
 | 
			
		||||
 | 
			
		||||
Parsing Frames
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
There is a parsing function for each frame type.  These function generally
 | 
			
		||||
have names that begin with "pf_parse\_" and follow a similar pattern:
 | 
			
		||||
 | 
			
		||||
-   The first argument is the buffer to be parsed;
 | 
			
		||||
 | 
			
		||||
-   The second argument is its size;
 | 
			
		||||
 | 
			
		||||
-   Any additional arguments are outputs: the parsed out values from the frame;
 | 
			
		||||
 | 
			
		||||
-   Number of bytes consumed is returned or a negative value is returned
 | 
			
		||||
    if a parsing error occurred.
 | 
			
		||||
 | 
			
		||||
For example:
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    int
 | 
			
		||||
    (*pf_parse_stream_frame) (const unsigned char *buf, size_t rem_packet_sz,
 | 
			
		||||
                                                    struct stream_frame *);
 | 
			
		||||
 | 
			
		||||
    int
 | 
			
		||||
    (*pf_parse_max_data) (const unsigned char *, size_t, uint64_t *);
 | 
			
		||||
 | 
			
		||||
Generating Frames
 | 
			
		||||
=================
 | 
			
		||||
 | 
			
		||||
Functions that generate frames begin with "pf_gen\_" and also follow a
 | 
			
		||||
pattern:
 | 
			
		||||
 | 
			
		||||
-   First argument is the buffer to be written to;
 | 
			
		||||
 | 
			
		||||
-   The second argument is the buffer size;
 | 
			
		||||
 | 
			
		||||
-   Any additional arguments specify the values to include in the frame;
 | 
			
		||||
 | 
			
		||||
-   The size of the resulting frame is returned or a negative value if
 | 
			
		||||
    an error occurred.
 | 
			
		||||
 | 
			
		||||
For example:
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    int
 | 
			
		||||
    (*pf_gen_path_chal_frame) (unsigned char *, size_t, uint64_t chal);
 | 
			
		||||
 | 
			
		||||
    int
 | 
			
		||||
    (*pf_gen_stream_frame) (unsigned char *buf, size_t bufsz,
 | 
			
		||||
                            lsquic_stream_id_t stream_id, uint64_t offset,
 | 
			
		||||
                            int fin, size_t size, gsf_read_f, void *stream);
 | 
			
		||||
 | 
			
		||||
Frame Types
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
Frame types are listed in ``enum quic_frame_type``.  When frames are parsed,
 | 
			
		||||
the on-the-wire frame type is translated to the enum value; when frames are
 | 
			
		||||
generated, the enum is converted to the on-the-wire format.  This indirection
 | 
			
		||||
is convenient, as it limits the range of possible QUIC frame values, making
 | 
			
		||||
it possible to store a list of frame types as a bitmask.  Examples include
 | 
			
		||||
``po_frame_types`` and ``sc_retx_frames``.
 | 
			
		||||
 | 
			
		||||
Some frame types, such as ACK and STREAM, are common to both Google and IETF
 | 
			
		||||
QUIC.  Others, such as STOP_WAITING and RETIRE_CONNECTION_ID, are only used
 | 
			
		||||
in one of the protocols.  The third type is frames that are used by IETF
 | 
			
		||||
QUIC extensions, such as TIMESTAMP and ACK_FREQUENCY.
 | 
			
		||||
 | 
			
		||||
Parsing IETF QUIC Frame Types
 | 
			
		||||
-----------------------------
 | 
			
		||||
 | 
			
		||||
Most IETF frame types are encoded as a single by on the wire (and all Google
 | 
			
		||||
QUIC frames are).  Some of them are encoded using multiple bytes.  This is
 | 
			
		||||
because, like the vast majority of all integral values in IETF QUIC, the frame
 | 
			
		||||
type is encoded as a varint.  Unlike the other integral values, however, the
 | 
			
		||||
frame type has the unique property is that it must be encoded using the
 | 
			
		||||
*minimal representation*: that is, the encoding must use the minimum number
 | 
			
		||||
of bytes possible.  For example, encoding the value 200 must use the two-byte
 | 
			
		||||
varint, not four- or eight-byte version.  This makes it possible to parse
 | 
			
		||||
frame types once without having to reparse the frame type again in individual
 | 
			
		||||
frame-parsing routines.
 | 
			
		||||
 | 
			
		||||
Frame type is parsed out in ``ietf_v1_parse_frame_type()``.  Because of the
 | 
			
		||||
minimal encoding requirement, the corresponding frame-parsing functions know
 | 
			
		||||
the number of bytes to skip for type, for example:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    static int
 | 
			
		||||
    ietf_v1_parse_frame_with_varints (const unsigned char *buf, size_t len,
 | 
			
		||||
                const uint64_t frame_type, unsigned count, uint64_t *vals[])
 | 
			
		||||
    {
 | 
			
		||||
        /* --- 8< --- code removed */
 | 
			
		||||
        vbits = vint_val2bits(frame_type);
 | 
			
		||||
        p += 1 << vbits;                    // <=== SKIP FRAME TYPE
 | 
			
		||||
        /* --- 8< --- code removed */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static int
 | 
			
		||||
    ietf_v1_parse_timestamp_frame (const unsigned char *buf,
 | 
			
		||||
                                    size_t buf_len, uint64_t *timestamp)
 | 
			
		||||
    {
 | 
			
		||||
        return ietf_v1_parse_frame_with_varints(buf, buf_len,
 | 
			
		||||
                FRAME_TYPE_TIMESTAMP, 1, (uint64_t *[]) { timestamp });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Mini vs Full Connections
 | 
			
		||||
************************
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1772,13 +1921,6 @@ The following steps are performed:
 | 
			
		|||
 | 
			
		||||
-  Streams are serviced (closed, freed, created)
 | 
			
		||||
 | 
			
		||||
.. _notable-code-4:
 | 
			
		||||
 | 
			
		||||
Notable Code
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
Full IETF Connection
 | 
			
		||||
********************
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2240,8 +2382,22 @@ Last time ``ea_live_scids()`` was called.
 | 
			
		|||
ifc_paths
 | 
			
		||||
---------
 | 
			
		||||
 | 
			
		||||
Array of network paths.  Most of the time, only one path is used when the
 | 
			
		||||
peer migrates.  The array has four elements as a safe upper limit.
 | 
			
		||||
Array of connection paths.  Most of the time, only one path is used; more
 | 
			
		||||
are used during `migration <#path-migration>`__.  The array has four
 | 
			
		||||
elements as a safe upper limit.
 | 
			
		||||
 | 
			
		||||
The elements are of type ``struct conn_path``.  Besides the network path,
 | 
			
		||||
which stores socket addresses and is associated with each outgoing packet
 | 
			
		||||
(via ``po_path``), the connection path keeps track of the following
 | 
			
		||||
information:
 | 
			
		||||
 | 
			
		||||
-   Outgoing path challenges.  See `Sending Path Challenges`_.
 | 
			
		||||
 | 
			
		||||
-   Incoming path challenge.
 | 
			
		||||
 | 
			
		||||
-   Spin bit (``cop_max_packno``, ``cop_spin_bit``, and ``COP_SPIN_BIT``).
 | 
			
		||||
 | 
			
		||||
-   DPLPMTUD state.
 | 
			
		||||
 | 
			
		||||
ifc_u.cli
 | 
			
		||||
---------
 | 
			
		||||
| 
						 | 
				
			
			@ -2348,6 +2504,140 @@ be found).
 | 
			
		|||
Path Migration
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
What follows assumes familiarity with `Section 9
 | 
			
		||||
<https://tools.ietf.org/html/draft-ietf-quic-transport-34#section-9>`__
 | 
			
		||||
of the Transport I-D.
 | 
			
		||||
 | 
			
		||||
Server
 | 
			
		||||
------
 | 
			
		||||
 | 
			
		||||
The server handles two types of path migration.  In the first type, the
 | 
			
		||||
client performs probing by sending path challenges; in the second type,
 | 
			
		||||
the migration is due to a NAT rebinding.
 | 
			
		||||
 | 
			
		||||
The connection keeps track of different paths in `ifc_paths`_.  Path
 | 
			
		||||
objects are allocated out of the ``ifc_paths`` array.  They are of type
 | 
			
		||||
``struct conn_path``; one of the members is ``cop_path``, which is the
 | 
			
		||||
network path object used to send packets (via ``po_path``).
 | 
			
		||||
 | 
			
		||||
Each incoming packet is fed to the engine using the
 | 
			
		||||
``lsquic_engine_packet_in()`` function.  Along with the UDP datagram,
 | 
			
		||||
the local and peer socket addresses are passed to it.  These addresses are
 | 
			
		||||
eventually passed to the connection via the ``ci_record_addrs()`` call.
 | 
			
		||||
The first of these calls -- for the first incoming packet -- determines the
 | 
			
		||||
*current path*.  When the address pair, which is a four-tuple of local
 | 
			
		||||
and remote IP addresses and port numbers, does not match that of the
 | 
			
		||||
current path, a new path object is created, triggering migration logic.
 | 
			
		||||
 | 
			
		||||
``ci_record_addrs()`` returns a *path ID*, which is simply the index of
 | 
			
		||||
the corresponding element in the ``ifc_paths`` array.  The current path
 | 
			
		||||
ID is stored in ``ifc_cur_path_id``.  The engine assigns this value to
 | 
			
		||||
the newly created incoming packet (in ``pi_path_id``).  The packet is
 | 
			
		||||
then passed to ``ci_packet_in()``.
 | 
			
		||||
 | 
			
		||||
The first part of the path-switching logic is in ``process_regular_packet()``:
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    case REC_ST_OK:
 | 
			
		||||
        /* --- 8< --- some code elided... */
 | 
			
		||||
        saved_path_id = conn->ifc_cur_path_id;
 | 
			
		||||
        parse_regular_packet(conn, packet_in);
 | 
			
		||||
        if (saved_path_id == conn->ifc_cur_path_id)
 | 
			
		||||
        {
 | 
			
		||||
            if (conn->ifc_cur_path_id != packet_in->pi_path_id)
 | 
			
		||||
            {
 | 
			
		||||
                if (0 != on_new_or_unconfirmed_path(conn, packet_in))
 | 
			
		||||
                {
 | 
			
		||||
                    LSQ_DEBUG("path %hhu invalid, cancel any path response "
 | 
			
		||||
                        "on it", packet_in->pi_path_id);
 | 
			
		||||
                    conn->ifc_send_flags &= ~(SF_SEND_PATH_RESP
 | 
			
		||||
                                                    << packet_in->pi_path_id);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
The above means: if the current path has not changed after the packet
 | 
			
		||||
was processed, but the packet came in on a different path, then invoke
 | 
			
		||||
the "on new or unconfirmed path" logic.  This is done this way because
 | 
			
		||||
the current path may be have been already changed if the packet contained
 | 
			
		||||
a PATH_RESPONSE frame.
 | 
			
		||||
 | 
			
		||||
First time a packet is received on a new path, a PATH_CHALLENGE frame is
 | 
			
		||||
scheduled.
 | 
			
		||||
 | 
			
		||||
If more than one packet received on the new path contain non-probing frames,
 | 
			
		||||
the current path is switched: it is assumed that the path change is due to
 | 
			
		||||
NAT rebinding.
 | 
			
		||||
 | 
			
		||||
Client
 | 
			
		||||
------
 | 
			
		||||
 | 
			
		||||
Path migration is controlled by the client.  When the client receives
 | 
			
		||||
a packet from an unknown server address, it drops the packet on the
 | 
			
		||||
floor (per spec).  This code is in ``process_regular_packet()``.
 | 
			
		||||
 | 
			
		||||
The client can migrate if ``es_allow_migration`` is on (it is in the default
 | 
			
		||||
configuration) and the server provides the "preferred_address" transport
 | 
			
		||||
parameter.  The migration process begins once the handshake is confirmed;
 | 
			
		||||
see the ``maybe_start_migration()`` function.  The SCID provided by the
 | 
			
		||||
server as part of the "preferred_address" transport parameter is used as the
 | 
			
		||||
destination CID and path #1 is picked:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    copath = &conn->ifc_paths[1];
 | 
			
		||||
    migra_begin(conn, copath, dce, (struct sockaddr *) &sockaddr, params);
 | 
			
		||||
    return BM_MIGRATING;
 | 
			
		||||
 | 
			
		||||
In ``migra_begin``, migration state is initiated and sending of a
 | 
			
		||||
PATH_CHALLENGE frame is scheduled:
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    conn->ifc_mig_path_id = copath - conn->ifc_paths;
 | 
			
		||||
    conn->ifc_used_paths |= 1 << conn->ifc_mig_path_id;
 | 
			
		||||
    conn->ifc_send_flags |= SF_SEND_PATH_CHAL << conn->ifc_mig_path_id;
 | 
			
		||||
    LSQ_DEBUG("Schedule migration to path %hhu: will send PATH_CHALLENGE",
 | 
			
		||||
        conn->ifc_mig_path_id);
 | 
			
		||||
 | 
			
		||||
Sending Path Challenges
 | 
			
		||||
-----------------------
 | 
			
		||||
 | 
			
		||||
To send a path challenge, a packet is allocated to be sent on that path,
 | 
			
		||||
a new challenge is generated, the PATH_CHALLENGE is written to the
 | 
			
		||||
packet, and the packet is scheduled.  All this happens in the
 | 
			
		||||
``generate_path_chal_frame()`` function.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    need = conn->ifc_conn.cn_pf->pf_path_chal_frame_size();
 | 
			
		||||
    packet_out = get_writeable_packet_on_path(conn, need, &copath->cop_path, 1);
 | 
			
		||||
    /* --- 8< --- some code elided... */
 | 
			
		||||
    w = conn->ifc_conn.cn_pf->pf_gen_path_chal_frame(
 | 
			
		||||
            packet_out->po_data + packet_out->po_data_sz,
 | 
			
		||||
            lsquic_packet_out_avail(packet_out),
 | 
			
		||||
            copath->cop_path_chals[copath->cop_n_chals]);
 | 
			
		||||
    /* --- 8< --- some code elided... */
 | 
			
		||||
    lsquic_alarmset_set(&conn->ifc_alset, AL_PATH_CHAL + path_id,
 | 
			
		||||
                    now + (INITIAL_CHAL_TIMEOUT << (copath->cop_n_chals - 1)));
 | 
			
		||||
 | 
			
		||||
If the path response is not received before a timeout, another path challenge
 | 
			
		||||
is sent, up to the number of elements in ``cop_path_chals``.  The timeout
 | 
			
		||||
uses exponential back-off; it is not based on RTT, because the RTT of the
 | 
			
		||||
new path is unknown.
 | 
			
		||||
 | 
			
		||||
Receiving Path Responses
 | 
			
		||||
------------------------
 | 
			
		||||
 | 
			
		||||
When a PATH_RESPONSE frame is received, the path on which the corresponding
 | 
			
		||||
challenge was sent may become the new current path.  See
 | 
			
		||||
``process_path_response_frame()``.
 | 
			
		||||
 | 
			
		||||
Note that the path ID of the incoming packet with the PATH_RESPONSE frame is
 | 
			
		||||
not taken into account.  This is by design: see
 | 
			
		||||
`Section 8.2.2 of the Transport I-D
 | 
			
		||||
<https://tools.ietf.org/html/draft-ietf-quic-transport-34#section-8.2.2>`__.
 | 
			
		||||
 | 
			
		||||
Stream Priority Iterators
 | 
			
		||||
=========================
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2385,9 +2675,182 @@ some limited interop testing.
 | 
			
		|||
Anatomy of Outgoing Packet
 | 
			
		||||
**************************
 | 
			
		||||
 | 
			
		||||
Overview
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
The outgoing packet is represented by ``struct lsquic_packet_out``.  An
 | 
			
		||||
outgoing packet always lives on one -- and only one -- of the
 | 
			
		||||
`Send Controller`_'s `Packet Queues`_.  For that, ``po_next`` is used.
 | 
			
		||||
 | 
			
		||||
Beyond the packet number, stored in ``po_packno``, the packet has several
 | 
			
		||||
properties: sent time (``po_sent``), frame information, encryption
 | 
			
		||||
level, network path, and others.  Several properties are encoded into
 | 
			
		||||
one or more bits in the bitmasks ``po_flags`` and ``po_lflags``.
 | 
			
		||||
Multibit properties are usually accessed and modified by a special
 | 
			
		||||
macro.
 | 
			
		||||
 | 
			
		||||
The packet has a pointer to the packetized data in ``po_data``.
 | 
			
		||||
If the packet has been encrypted but not yet sent, the encrypted
 | 
			
		||||
buffer is pointed to ``po_enc_data``.
 | 
			
		||||
 | 
			
		||||
Packet Payload
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
The payload consists of the various frames -- STREAM, ACK, and others --
 | 
			
		||||
written, one after another, to ``po_data``.  The header, consisting of
 | 
			
		||||
the type byte, (optional) connection ID, and the packet number is constructed
 | 
			
		||||
when the packet is just about to be sent, during encryption.  This
 | 
			
		||||
buffer -- header and the encrypted payload are stored in a buffer
 | 
			
		||||
pointed to by ``po_enc_data``.
 | 
			
		||||
 | 
			
		||||
Because stream data is written directly to the outgoing packet, the
 | 
			
		||||
packet is not destroyed when it is declared lost by the `loss detection
 | 
			
		||||
logic <#loss-detection-and-retransmission>`__.  Instead, it is repackaged
 | 
			
		||||
and sent out again as a new packet.  Besides assigning the packet a
 | 
			
		||||
new number, packet retransmission involves removing non-retransmittable
 | 
			
		||||
frames from the packet.  (See ``lsquic_packet_out_chop_regen()``.)
 | 
			
		||||
 | 
			
		||||
Historically, some places in the code assumed that the frames to be
 | 
			
		||||
dropped are always located at the beginning of the ``po_data`` buffer.
 | 
			
		||||
(This was before a `frame record <#frame-records>`__ was created for
 | 
			
		||||
each frame).  The cumulative size of the frames to be removed is in
 | 
			
		||||
``po_regen_sz``; this size can be zero.  Code that generates
 | 
			
		||||
non-retransmittable frames still writes them only to the beginning
 | 
			
		||||
of the packet.
 | 
			
		||||
 | 
			
		||||
The goal is to drop ``po_regen_sz`` and to begin to write ACK and
 | 
			
		||||
other non-retransmittable frames anywhere.  This should be possible
 | 
			
		||||
to do now (see ``lsquic_packet_out_chop_regen()``, which can support
 | 
			
		||||
such use after removing the assertion), but we haven't pulled the
 | 
			
		||||
trigger on it yet.  Making this change will allow other code to become
 | 
			
		||||
simpler: for example, the opportunistic ACKs logic.
 | 
			
		||||
 | 
			
		||||
Frame Records
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
Each frame written to ``po_data`` has an associated *frame record* stored
 | 
			
		||||
in ``po_frecs``:
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    struct frame_rec {
 | 
			
		||||
        union {
 | 
			
		||||
            struct lsquic_stream   *stream;
 | 
			
		||||
            uintptr_t               data;
 | 
			
		||||
        }                        fe_u;
 | 
			
		||||
        unsigned short           fe_off,
 | 
			
		||||
                                 fe_len;
 | 
			
		||||
        enum quic_frame_type     fe_frame_type;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
Frame records are primarily used to keep track of the number of unacknowledged
 | 
			
		||||
stream frames for a stream.  When a packet is acknowledged, the frame records
 | 
			
		||||
are iterated over and ``lsquic_stream_acked()`` is called.  The second purpose
 | 
			
		||||
is to speed up packet resizing, as frame records record the type, position,
 | 
			
		||||
and size of a frame.
 | 
			
		||||
 | 
			
		||||
Most of the time, a packet will contain a single frame: STREAM on the sender
 | 
			
		||||
of data and ACK on the receiver.  This use case is optimized: ``po_frecs`` is
 | 
			
		||||
a union and when there is only one frame per packets, the frame record is
 | 
			
		||||
stored in the packet struct directly.
 | 
			
		||||
 | 
			
		||||
Evanescent Connection
 | 
			
		||||
*********************
 | 
			
		||||
 | 
			
		||||
*Files: lsquic_pr_queue.h, lsquic_pr_queue.c*
 | 
			
		||||
 | 
			
		||||
"PR Queue" stands for "Packet Request Queue."  This and the Evanescent
 | 
			
		||||
Connection object types are explaned below in this section.
 | 
			
		||||
 | 
			
		||||
Overview
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
Some packets need to be replied to outside of context of existing
 | 
			
		||||
mini or full connections:
 | 
			
		||||
 | 
			
		||||
1. A version negotiation packet needs to be sent when a packet
 | 
			
		||||
   arrives that specifies QUIC version that we do not support.
 | 
			
		||||
 | 
			
		||||
2. A stateless reset packet needs to be sent when we receive a
 | 
			
		||||
   packet that does not belong to a known QUIC connection.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
The replies cannot be sent immediately.  They share outgoing
 | 
			
		||||
socket with existing connections and must be scheduled according
 | 
			
		||||
to prioritization rules.
 | 
			
		||||
 | 
			
		||||
The information needed to generate reply packet  -- connection ID,
 | 
			
		||||
connection context, and the peer address -- is saved in the Packet
 | 
			
		||||
Request Queue.
 | 
			
		||||
 | 
			
		||||
When it is time to send packets, the connection iterator knows to
 | 
			
		||||
call prq_next_conn() when appropriate.  What is returned is an
 | 
			
		||||
evanescent connection object that disappears as soon as the reply
 | 
			
		||||
packet is successfully sent out.
 | 
			
		||||
 | 
			
		||||
There are two limits associated with Packet Request Queue:
 | 
			
		||||
 | 
			
		||||
1. Maximum number of packet requests that are allowed to be
 | 
			
		||||
   pending at any one time.  This is simply to prevent memory
 | 
			
		||||
   blowout.
 | 
			
		||||
 | 
			
		||||
2. Maximum verneg connection objects to be allocated at any one
 | 
			
		||||
   time.  This number is the same as the maximum batch size in
 | 
			
		||||
   the engine, because the packet (and, therefore, the connection)
 | 
			
		||||
   is returned to the Packet Request Queue when it could not be
 | 
			
		||||
   sent.
 | 
			
		||||
 | 
			
		||||
We call this a "request" queue because it describes what we do with
 | 
			
		||||
QUIC packets whose version we do not support or those packets that
 | 
			
		||||
do not belong to an existing connection: we send a reply for each of
 | 
			
		||||
these packets, which effectively makes them "requests."
 | 
			
		||||
 | 
			
		||||
Packet Requests
 | 
			
		||||
===============
 | 
			
		||||
 | 
			
		||||
When an incoming packet requires a non-connection response, it is added
 | 
			
		||||
to the Packet Request Queue.  There is a single ``struct pr_queue`` per
 | 
			
		||||
engine -- it is instantiated if the engine is in the server mode.
 | 
			
		||||
 | 
			
		||||
The packet request is recorded in ``struct packet_req``, which are kept
 | 
			
		||||
inside a hash in the PR Queue.  The reason for keeping the requests in
 | 
			
		||||
a hash is to minimize duplicate responses:  If a client hello message
 | 
			
		||||
is spread over several incoming packets, only one response carrying the
 | 
			
		||||
version negotiation packet (for example) will be sent.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    struct packet_req
 | 
			
		||||
    {
 | 
			
		||||
        struct lsquic_hash_elem     pr_hash_el;
 | 
			
		||||
        lsquic_cid_t                pr_scid;
 | 
			
		||||
        lsquic_cid_t                pr_dcid;
 | 
			
		||||
        enum packet_req_type        pr_type;
 | 
			
		||||
        enum pr_flags {
 | 
			
		||||
            PR_GQUIC    = 1 << 0,
 | 
			
		||||
        }                           pr_flags;
 | 
			
		||||
        enum lsquic_version         pr_version;
 | 
			
		||||
        unsigned                    pr_rst_sz;
 | 
			
		||||
        struct network_path         pr_path;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
Responses are created on demand.  Until that time, everything that is
 | 
			
		||||
necessary to generate the response is stored in ``packet_req``.
 | 
			
		||||
 | 
			
		||||
Sending Responses
 | 
			
		||||
=================
 | 
			
		||||
 | 
			
		||||
To make these packets fit into the usual packet-sending loop,
 | 
			
		||||
each response is made to resemble a packet
 | 
			
		||||
sent by a connecteion.  For that, the PR Queue creates a connection
 | 
			
		||||
object that only lives for the duration of batching of the packet.
 | 
			
		||||
(Hence the connection's name: *evanescent* connection.)  This connection
 | 
			
		||||
is returned by the ``lsquic_prq_next_conn()`` by the connection iterator
 | 
			
		||||
during the `batching process <#batching-packets>`__
 | 
			
		||||
 | 
			
		||||
For simplicity, the response packet is generated in this function as well.
 | 
			
		||||
The call to ``ci_next_packet_to_send()`` only returns the pointer to it.
 | 
			
		||||
 | 
			
		||||
Send Controller
 | 
			
		||||
***************
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2692,7 +3155,16 @@ Alarm Set
 | 
			
		|||
 | 
			
		||||
*Files: lsquic_alarmset.h, lsquic_alarmset.c, test_alarmset.c*
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
The alarm set, ``struct lsquic_alarmset``, is an array of callbacks and
 | 
			
		||||
expiry times.  To speed up operations, setting and unsetting alarms is
 | 
			
		||||
done via macros.
 | 
			
		||||
 | 
			
		||||
The functions to ring [4]_ the alarms and to calculate the next alarm
 | 
			
		||||
time use a loop.  It would be possible to maintain a different data
 | 
			
		||||
structure, such as a min-heap, to keep the alarm, and that would obviate
 | 
			
		||||
the need to loop in ``lsquic_alarmset_mintime()``.  It is not worth it:
 | 
			
		||||
the function is not called often and a speed win here would be offset
 | 
			
		||||
by the necessity to maintain the min-heap ordering.
 | 
			
		||||
 | 
			
		||||
Tickable Queue
 | 
			
		||||
**************
 | 
			
		||||
| 
						 | 
				
			
			@ -2743,12 +3215,169 @@ memory that stores ``attq_elem`` stays put. This is why there are both
 | 
			
		|||
CID Purgatory
 | 
			
		||||
*************
 | 
			
		||||
 | 
			
		||||
*Files: lsquic_purga.h, lsquic_purga.c*
 | 
			
		||||
 | 
			
		||||
Overview
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
This module keeps a set of CIDs that should be ignored for a period
 | 
			
		||||
of time.  It is used when a connection is closed: this way, late
 | 
			
		||||
packets will not create a new connection.
 | 
			
		||||
 | 
			
		||||
A connection may have been deleted, retired, or closed.  In the latter
 | 
			
		||||
case, it enters the `Draining State <https://tools.ietf.org/html/draft-ietf-quic-transport-34#section-10.2.2>`__.
 | 
			
		||||
In this state, the connection is to ignore incoming packets.
 | 
			
		||||
 | 
			
		||||
Structure
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
The purgatory keeps a list of 16-KB pages.  A page looks like this:
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    #define PURGA_ELS_PER_PAGE 273
 | 
			
		||||
 | 
			
		||||
    struct purga_page
 | 
			
		||||
    {
 | 
			
		||||
        TAILQ_ENTRY(purga_page)     pupa_next;
 | 
			
		||||
        lsquic_time_t               pupa_last;
 | 
			
		||||
        unsigned                    pupa_count;
 | 
			
		||||
        bloom_mask_el_t             pupa_mask[BLOOM_N_MASK_ELS];
 | 
			
		||||
        lsquic_cid_t                pupa_cids[PURGA_ELS_PER_PAGE];
 | 
			
		||||
        void *                      pupa_peer_ctx[PURGA_ELS_PER_PAGE];
 | 
			
		||||
        struct purga_el             pupa_els[PURGA_ELS_PER_PAGE];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
The reason for having CIDs and peer contexts in separate arrays is to be
 | 
			
		||||
able to call the ``ea_old_scids()`` callback when a page expires.  A page
 | 
			
		||||
is expired when it is full and the last added element is more than
 | 
			
		||||
``pur_min_life`` microseconds ago.  The minimum CID life is hardcoded as
 | 
			
		||||
30 seconds in lsquic_engine.c (see the ``lsquic_purga_new()`` call).
 | 
			
		||||
 | 
			
		||||
To avoid scannig the whole array of CIDs in ``lsquic_purga_contains()``,
 | 
			
		||||
we use a Bloom filter.
 | 
			
		||||
 | 
			
		||||
The Bloom filter is constructed using a 8192-bit bit field and 6 hash
 | 
			
		||||
functions.  With 273 elements per page, this gives us 0.004% possibility
 | 
			
		||||
of a false positive.  In other words, when we do have to search a page
 | 
			
		||||
for a particular CID, the chance of finding the CID is 99.99%.
 | 
			
		||||
 | 
			
		||||
Quick calculation:
 | 
			
		||||
 | 
			
		||||
.. code-block:: text
 | 
			
		||||
 | 
			
		||||
    perl -E '$k=6;$m=1<<13;$n=273;printf("%f\n", (1-exp(1)**-($k*$n/$m))**$k)'
 | 
			
		||||
 | 
			
		||||
To extract 6 13-bit values from a 64-bit integer, they are overlapped:
 | 
			
		||||
 | 
			
		||||
.. code-block:: text
 | 
			
		||||
 | 
			
		||||
     0         10        20        30        40        50        60
 | 
			
		||||
    +----------------------------------------------------------------+
 | 
			
		||||
    |                                                                |
 | 
			
		||||
    +----------------------------------------------------------------+
 | 
			
		||||
     1111111111111
 | 
			
		||||
               2222222222222
 | 
			
		||||
                         3333333333333
 | 
			
		||||
                                   4444444444444
 | 
			
		||||
                                             5555555555555
 | 
			
		||||
                                                       6666666666666
 | 
			
		||||
 | 
			
		||||
This is not 100% kosher, but having 6 functions gives a better guarantee
 | 
			
		||||
and it happens to work in practice.
 | 
			
		||||
 | 
			
		||||
Memory Manager
 | 
			
		||||
**************
 | 
			
		||||
 | 
			
		||||
*Files: lsquic_mm.h, lsquic_mm.c*
 | 
			
		||||
 | 
			
		||||
The memory manager allocates several types of objects that are used by
 | 
			
		||||
different parts of the library:
 | 
			
		||||
 | 
			
		||||
-   Incoming packet objects and associated buffers
 | 
			
		||||
 | 
			
		||||
-   Outgoing packet objects and associated buffers
 | 
			
		||||
 | 
			
		||||
-   Stream frames
 | 
			
		||||
 | 
			
		||||
-   Frame records
 | 
			
		||||
 | 
			
		||||
-   Mini connections, both Google and IETF QUIC
 | 
			
		||||
 | 
			
		||||
-   DCID elements
 | 
			
		||||
 | 
			
		||||
-   HTTP/3 (a.k.a. "HQ") frames
 | 
			
		||||
 | 
			
		||||
-   Four- and sixteen-kilobyte pages
 | 
			
		||||
 | 
			
		||||
These objects are either stored on linked list or in `malo <#malo-allocator>`__
 | 
			
		||||
pools and are shared among all connections.  (Full connections allocate outgoing
 | 
			
		||||
packets from per-connection malo allocators: this is done to speed up `ACK
 | 
			
		||||
processing <#handling-acks>`__.)
 | 
			
		||||
 | 
			
		||||
The list of cached outgoing packet buffers is shrunk once in a while (see
 | 
			
		||||
the "poolst\_*" functions).  Other object types are kept in the cache
 | 
			
		||||
until the engine is destroyed.  One Memory Manager object is allocated per
 | 
			
		||||
engine instance.
 | 
			
		||||
 | 
			
		||||
Malo Allocator
 | 
			
		||||
**************
 | 
			
		||||
 | 
			
		||||
*Files: lsquic_malo.h, lsquic_malo.c*
 | 
			
		||||
 | 
			
		||||
Overview
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
The malo allocator is a pool of objects of fixed size.  It tries to
 | 
			
		||||
allocate and deallocate objects as fast as possible.  To do so, it
 | 
			
		||||
does the following:
 | 
			
		||||
 | 
			
		||||
1. Allocations occur 4 KB at a time.
 | 
			
		||||
 | 
			
		||||
2. No division or multiplication operations are performed for
 | 
			
		||||
   appropriately sized objects.  (More on this below.)
 | 
			
		||||
 | 
			
		||||
(In recent testing, malo was about 2.7 times faster than malloc for
 | 
			
		||||
64-byte objects.)
 | 
			
		||||
 | 
			
		||||
Besides speed, the allocator provides a convenient API:
 | 
			
		||||
To free (put) an object, one does not need a pointer to the malo
 | 
			
		||||
object.
 | 
			
		||||
 | 
			
		||||
To gain all these advantages, there are trade-offs:
 | 
			
		||||
 | 
			
		||||
1. There are two memory penalties:
 | 
			
		||||
 | 
			
		||||
   a. Per object overhead.  If an object is at least ROUNDUP_THRESH in
 | 
			
		||||
      size as the next power of two, the allocator uses that power of
 | 
			
		||||
      two value as the object size.  This is done to avoid using
 | 
			
		||||
      division and multiplication.  For example, a 104-byte object
 | 
			
		||||
      will have a 24-byte overhead.
 | 
			
		||||
 | 
			
		||||
   b. Per page overhead.  Page links occupy some bytes in the
 | 
			
		||||
      page.  To keep things fast, at least one slot per page is
 | 
			
		||||
      always occupied, independent of object size.  Thus, for a
 | 
			
		||||
      1 KB object size, 25% of the page is used for the page
 | 
			
		||||
      header.
 | 
			
		||||
 | 
			
		||||
2. 4 KB pages are not freed until the malo allocator is destroyed.
 | 
			
		||||
   This is something to keep in mind.
 | 
			
		||||
 | 
			
		||||
Internal Structure
 | 
			
		||||
==================
 | 
			
		||||
 | 
			
		||||
The malo allocator allocates objects out of 4 KB pages.  Each page is
 | 
			
		||||
aligned on a 4-KB memory boundary.  This makes it possible for the
 | 
			
		||||
``lsquic_malo_put()`` function only to take on argument -- the object
 | 
			
		||||
to free -- and to find the malo allocator object itself.
 | 
			
		||||
 | 
			
		||||
Each page begins with a header followed by a number of slots -- up to
 | 
			
		||||
the 4-KB limit.  Two lists of pages are maintained: all pages and free
 | 
			
		||||
pages.  A "free" page is a page with at least one free slot in it.
 | 
			
		||||
 | 
			
		||||
The malo allocator (``struct malo``) stores itself in the first page,
 | 
			
		||||
occupying some slots.
 | 
			
		||||
 | 
			
		||||
Receive History
 | 
			
		||||
***************
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2987,6 +3616,20 @@ connection.
 | 
			
		|||
Set64
 | 
			
		||||
*****
 | 
			
		||||
 | 
			
		||||
*Files: lsquic_set.h, lsquic_set.h, test_set.c*
 | 
			
		||||
 | 
			
		||||
This data structure (along with *Set32*, which is not currently used
 | 
			
		||||
anywhere in the code) is meant to keep track of a set of numbers that
 | 
			
		||||
are always increasing and are not expected to contain many gaps.
 | 
			
		||||
Stream IDs fit that description, and ``lsquic_set64`` is used in both
 | 
			
		||||
gQUIC and IETF QUIC full connections.
 | 
			
		||||
 | 
			
		||||
Because one or two low bits in stream IDs contain stream type, the
 | 
			
		||||
stream IDs of different types are stored in different set structures;
 | 
			
		||||
otherwise, there would be gaps.  For example, see the
 | 
			
		||||
``conn_is_stream_closed()`` functions (there is one in each gQUIC and
 | 
			
		||||
IETF QUIC full connection code).
 | 
			
		||||
 | 
			
		||||
Appendix A: List of Data Structures
 | 
			
		||||
***********************************
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3050,3 +3693,14 @@ QLOG
 | 
			
		|||
   Allocator <#malo-allocator>`__, which used to be limited to objects
 | 
			
		||||
   whose size is a power of two, so it was either fitting it into 128
 | 
			
		||||
   bytes or effectively doubling the mini conn size.
 | 
			
		||||
 | 
			
		||||
.. [3]
 | 
			
		||||
   This two-step packet parsing mechanism is left over from the
 | 
			
		||||
   little-endian to big-endian switch in gQUIC several years ago:
 | 
			
		||||
   Before parsing out the packet number, it was necessary to know
 | 
			
		||||
   whether it is little- or big-endian.  It should be possible to
 | 
			
		||||
   do away with this, especially once gQUIC is gone.
 | 
			
		||||
 | 
			
		||||
.. [4]
 | 
			
		||||
   This term was picked consciously: alarms *ring*, while timers do
 | 
			
		||||
   other things, such as "fire" and so on.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,8 +24,8 @@ extern "C" {
 | 
			
		|||
#endif
 | 
			
		||||
 | 
			
		||||
#define LSQUIC_MAJOR_VERSION 2
 | 
			
		||||
#define LSQUIC_MINOR_VERSION 29
 | 
			
		||||
#define LSQUIC_PATCH_VERSION 6
 | 
			
		||||
#define LSQUIC_MINOR_VERSION 30
 | 
			
		||||
#define LSQUIC_PATCH_VERSION 0
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Engine flags:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ struct adaptive_cc
 | 
			
		|||
    struct lsquic_cubic     acc_cubic;
 | 
			
		||||
    struct lsquic_bbr       acc_bbr;
 | 
			
		||||
    enum {
 | 
			
		||||
        ACC_CUBIC,  /* If set, use Cubic; otherwise, use BBR */
 | 
			
		||||
        ACC_CUBIC = 1,  /* If set, use Cubic; otherwise, use BBR */
 | 
			
		||||
    }                       acc_flags;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,6 +45,8 @@ const char *const lsquic_alid2str[] =
 | 
			
		|||
    [AL_CID_THROT]  =  "CID_THROT",
 | 
			
		||||
    [AL_PATH_CHAL_0] = "PATH_CHAL_0",
 | 
			
		||||
    [AL_PATH_CHAL_1] = "PATH_CHAL_1",
 | 
			
		||||
    [AL_PATH_CHAL_2] = "PATH_CHAL_2",
 | 
			
		||||
    [AL_PATH_CHAL_3] = "PATH_CHAL_3",
 | 
			
		||||
    [AL_SESS_TICKET] = "SESS_TICKET",
 | 
			
		||||
    [AL_BLOCKED_KA] = "BLOCKED_KA",
 | 
			
		||||
    [AL_MTU_PROBE]  = "MTU_PROBE",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -704,6 +704,7 @@ wipe_path (struct ietf_full_conn *conn, unsigned path_id)
 | 
			
		|||
    memset(&conn->ifc_paths[path_id], 0, sizeof(conn->ifc_paths[0]));
 | 
			
		||||
    conn->ifc_paths[path_id].cop_path.np_path_id = path_id;
 | 
			
		||||
    conn->ifc_paths[path_id].cop_path.np_peer_ctx = peer_ctx;
 | 
			
		||||
    conn->ifc_used_paths &= ~(1 << path_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2755,6 +2756,20 @@ have_bidi_streams (const struct ietf_full_conn *conn)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
conn_ok_to_close (const struct ietf_full_conn *conn)
 | 
			
		||||
{
 | 
			
		||||
    assert(conn->ifc_flags & IFC_CLOSING);
 | 
			
		||||
    return !(conn->ifc_flags & IFC_SERVER)
 | 
			
		||||
        || (conn->ifc_flags & IFC_RECV_CLOSE)
 | 
			
		||||
        || (
 | 
			
		||||
               !lsquic_send_ctl_have_outgoing_stream_frames(&conn->ifc_send_ctl)
 | 
			
		||||
            && !have_bidi_streams(conn)
 | 
			
		||||
            && lsquic_send_ctl_have_unacked_stream_frames(
 | 
			
		||||
                                                    &conn->ifc_send_ctl) == 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
maybe_close_conn (struct ietf_full_conn *conn)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -2763,8 +2778,13 @@ maybe_close_conn (struct ietf_full_conn *conn)
 | 
			
		|||
        && !have_bidi_streams(conn))
 | 
			
		||||
    {
 | 
			
		||||
        conn->ifc_flags |= IFC_CLOSING|IFC_GOAWAY_CLOSE;
 | 
			
		||||
        LSQ_DEBUG("maybe_close_conn: GOAWAY sent and no responses remain");
 | 
			
		||||
        if (conn_ok_to_close(conn))
 | 
			
		||||
        {
 | 
			
		||||
            conn->ifc_send_flags |= SF_SEND_CONN_CLOSE;
 | 
			
		||||
        LSQ_DEBUG("closing connection: GOAWAY sent and no responses remain");
 | 
			
		||||
            LSQ_DEBUG("maybe_close_conn: ok to close: "
 | 
			
		||||
                      "schedule to send CONNECTION_CLOSE");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2920,7 +2940,12 @@ ietf_full_conn_ci_close (struct lsquic_conn *lconn)
 | 
			
		|||
                lsquic_stream_maybe_reset(stream, 0, 1);
 | 
			
		||||
        }
 | 
			
		||||
        conn->ifc_flags |= IFC_CLOSING;
 | 
			
		||||
        if (conn_ok_to_close(conn))
 | 
			
		||||
        {
 | 
			
		||||
            conn->ifc_send_flags |= SF_SEND_CONN_CLOSE;
 | 
			
		||||
            LSQ_DEBUG("ietf_full_conn_ci_close: ok to close: "
 | 
			
		||||
                      "schedule to send CONNECTION_CLOSE");
 | 
			
		||||
        }
 | 
			
		||||
        lsquic_engine_add_conn_to_tickable(conn->ifc_enpub, lconn);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3204,7 +3229,8 @@ ietf_full_conn_ci_going_away (struct lsquic_conn *lconn)
 | 
			
		|||
    {
 | 
			
		||||
        if (!(conn->ifc_flags & (IFC_CLOSING|IFC_GOING_AWAY)))
 | 
			
		||||
        {
 | 
			
		||||
            LSQ_INFO("connection marked as going away");
 | 
			
		||||
            LSQ_INFO("connection marked as going away, last stream: %ld",
 | 
			
		||||
                     conn->ifc_max_req_id);
 | 
			
		||||
            conn->ifc_flags |= IFC_GOING_AWAY;
 | 
			
		||||
            const lsquic_stream_id_t stream_id = conn->ifc_max_req_id + N_SITS;
 | 
			
		||||
            if (valid_stream_id(stream_id))
 | 
			
		||||
| 
						 | 
				
			
			@ -4340,20 +4366,6 @@ process_streams_write_events (struct ietf_full_conn *conn, int high_prio)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
conn_ok_to_close (const struct ietf_full_conn *conn)
 | 
			
		||||
{
 | 
			
		||||
    assert(conn->ifc_flags & IFC_CLOSING);
 | 
			
		||||
    return !(conn->ifc_flags & IFC_SERVER)
 | 
			
		||||
        || (conn->ifc_flags & IFC_RECV_CLOSE)
 | 
			
		||||
        || (
 | 
			
		||||
               !lsquic_send_ctl_have_outgoing_stream_frames(&conn->ifc_send_ctl)
 | 
			
		||||
            && !have_bidi_streams(conn)
 | 
			
		||||
            && lsquic_send_ctl_have_unacked_stream_frames(
 | 
			
		||||
                                                    &conn->ifc_send_ctl) == 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
generate_connection_close_packet (struct ietf_full_conn *conn)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -8325,7 +8337,9 @@ ietf_full_conn_ci_tick (struct lsquic_conn *lconn, lsquic_time_t now)
 | 
			
		|||
    lsquic_send_ctl_maybe_app_limited(&conn->ifc_send_ctl, CUR_NPATH(conn));
 | 
			
		||||
 | 
			
		||||
  end_write:
 | 
			
		||||
    if ((conn->ifc_flags & IFC_CLOSING) && conn_ok_to_close(conn))
 | 
			
		||||
    if ((conn->ifc_flags & IFC_CLOSING)
 | 
			
		||||
        && ((conn->ifc_send_flags & SF_SEND_CONN_CLOSE)
 | 
			
		||||
            || conn_ok_to_close(conn)))
 | 
			
		||||
    {
 | 
			
		||||
        LSQ_DEBUG("connection is OK to close");
 | 
			
		||||
        conn->ifc_flags |= IFC_TICK_CLOSE;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -133,6 +133,7 @@ typedef struct hs_ctx_st
 | 
			
		|||
        HSET_SCID     =   (1 << 2),
 | 
			
		||||
        HSET_IRTT     =   (1 << 3),
 | 
			
		||||
        HSET_SRST     =   (1 << 4),
 | 
			
		||||
        HSET_XLCT     =   (1 << 5),     /* xlct is set */
 | 
			
		||||
    }           set;
 | 
			
		||||
    enum {
 | 
			
		||||
        HOPT_NSTP     =   (1 << 0),     /* NSTP option present in COPT */
 | 
			
		||||
| 
						 | 
				
			
			@ -283,6 +284,7 @@ struct lsquic_enc_session
 | 
			
		|||
    SSL_CTX *  ssl_ctx;
 | 
			
		||||
    struct lsquic_engine_public *enpub;
 | 
			
		||||
    struct lsquic_str * cert_ptr; /* pointer to the leaf cert of the server, not real copy */
 | 
			
		||||
    uint64_t            cert_hash;
 | 
			
		||||
    struct lsquic_str   chlo; /* real copy of CHLO message */
 | 
			
		||||
    struct lsquic_str   sstk;
 | 
			
		||||
    struct lsquic_str   ssno;
 | 
			
		||||
| 
						 | 
				
			
			@ -310,6 +312,7 @@ typedef struct compress_cert_hash_item_st
 | 
			
		|||
typedef struct cert_item_st
 | 
			
		||||
{
 | 
			
		||||
    struct lsquic_str*      crt;
 | 
			
		||||
    uint64_t                hash;   /* Hash of `crt' */
 | 
			
		||||
    struct lsquic_hash_elem hash_el;
 | 
			
		||||
    unsigned char           key[0];
 | 
			
		||||
} cert_item_t;
 | 
			
		||||
| 
						 | 
				
			
			@ -535,6 +538,8 @@ insert_cert (struct lsquic_engine_public *enpub, const unsigned char *key,
 | 
			
		|||
 | 
			
		||||
    item->crt = crt_copy;
 | 
			
		||||
    memcpy(item->key, key, key_sz);
 | 
			
		||||
    item->hash = lsquic_fnv1a_64((const uint8_t *)lsquic_str_buf(crt),
 | 
			
		||||
                                                        lsquic_str_len(crt));
 | 
			
		||||
    el = lsquic_hash_insert(enpub->enp_server_certs, item->key, key_sz,
 | 
			
		||||
                                                        item, &item->hash_el);
 | 
			
		||||
    if (el)
 | 
			
		||||
| 
						 | 
				
			
			@ -1267,6 +1272,13 @@ static int parse_hs_data (struct lsquic_enc_session *enc_session, uint32_t tag,
 | 
			
		|||
        break;
 | 
			
		||||
 | 
			
		||||
    case QTAG_XLCT:
 | 
			
		||||
        if (len != sizeof(hs_ctx->xlct))
 | 
			
		||||
        {
 | 
			
		||||
            LSQ_INFO("Unexpected size of XLCT: %u instead of %zu bytes",
 | 
			
		||||
                len, sizeof(hs_ctx->xlct));
 | 
			
		||||
            return -1;
 | 
			
		||||
        }
 | 
			
		||||
        hs_ctx->set |= HSET_XLCT;
 | 
			
		||||
        hs_ctx->xlct = get_tag_value_i64(val, len);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1664,7 +1676,6 @@ determine_rtts (struct lsquic_enc_session *enc_session,
 | 
			
		|||
{
 | 
			
		||||
    hs_ctx_t *const hs_ctx = &enc_session->hs_ctx;
 | 
			
		||||
    enum hsk_failure_reason hfr;
 | 
			
		||||
    uint64_t hash = 0;
 | 
			
		||||
 | 
			
		||||
    if (!(hs_ctx->set & HSET_SCID))
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -1701,12 +1712,9 @@ determine_rtts (struct lsquic_enc_session *enc_session,
 | 
			
		|||
        goto fail_1rtt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (hs_ctx->xlct)
 | 
			
		||||
    if (hs_ctx->set & HSET_XLCT)
 | 
			
		||||
    {
 | 
			
		||||
        hash = lsquic_fnv1a_64((const uint8_t *)lsquic_str_buf(enc_session->cert_ptr),
 | 
			
		||||
                    lsquic_str_len(enc_session->cert_ptr));
 | 
			
		||||
 | 
			
		||||
        if (hash != hs_ctx->xlct)
 | 
			
		||||
        if (enc_session->cert_hash != hs_ctx->xlct)
 | 
			
		||||
        {
 | 
			
		||||
            /* The expected leaf certificate hash could not be validated. */
 | 
			
		||||
            hs_ctx->rrej = HFR_INVALID_EXPECTED_LEAF_CERTIFICATE;
 | 
			
		||||
| 
						 | 
				
			
			@ -2297,6 +2305,7 @@ get_sni_SSL_CTX(struct lsquic_enc_session *enc_session, lsquic_lookup_cert_f cb,
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
            enc_session->cert_ptr = item->crt;
 | 
			
		||||
            enc_session->cert_hash = item->hash;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -2310,6 +2319,9 @@ get_sni_SSL_CTX(struct lsquic_enc_session *enc_session, lsquic_lookup_cert_f cb,
 | 
			
		|||
            if (!enc_session->cert_ptr)
 | 
			
		||||
                return GET_SNI_ERR;
 | 
			
		||||
            enc_session->es_flags |= ES_FREE_CERT_PTR;
 | 
			
		||||
            enc_session->cert_hash = lsquic_fnv1a_64(
 | 
			
		||||
                (const uint8_t *) lsquic_str_buf(enc_session->cert_ptr),
 | 
			
		||||
                lsquic_str_len(enc_session->cert_ptr));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return GET_SNI_OK;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,8 @@ struct uncompressed_headers
 | 
			
		|||
                           UH_H1H  = (1 << 2),  /* uh_hset points to http1x_headers */
 | 
			
		||||
    }                      uh_flags:8;
 | 
			
		||||
    void                  *uh_hset;
 | 
			
		||||
    struct uncompressed_headers
 | 
			
		||||
                          *uh_next;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,6 @@ struct lsquic_mm {
 | 
			
		|||
        struct malo     *frame_rec_arr; /* For struct frame_rec_arr */
 | 
			
		||||
        struct malo     *mini_conn;     /* For struct mini_conn */
 | 
			
		||||
        struct malo     *mini_conn_ietf;/* For struct ietf_mini_conn */
 | 
			
		||||
        struct malo     *retry_conn;    /* For struct retry_conn */
 | 
			
		||||
        struct malo     *packet_in;     /* For struct lsquic_packet_in */
 | 
			
		||||
        struct malo     *packet_out;    /* For struct lsquic_packet_out */
 | 
			
		||||
        struct malo     *dcid_elem;     /* For struct dcid_elem */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -641,7 +641,7 @@ qdh_header_read_results (struct qpack_dec_hdl *qdh,
 | 
			
		|||
 | 
			
		||||
    if (rhs == LQRHS_DONE)
 | 
			
		||||
    {
 | 
			
		||||
        if (!lsquic_stream_header_is_trailer(stream))
 | 
			
		||||
        if (1)    //!lsquic_stream_header_is_trailer(stream))
 | 
			
		||||
        {
 | 
			
		||||
            if (stream->sm_hblock_ctx->ctx.ppc_flags
 | 
			
		||||
                                                & (PPC_INC_SET|PPC_URG_SET))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -546,6 +546,8 @@ send_ctl_transfer_time (void *ctx)
 | 
			
		|||
 | 
			
		||||
    in_recovery = send_ctl_in_recovery(ctl);
 | 
			
		||||
    pacing_rate = ctl->sc_ci->cci_pacing_rate(CGP(ctl), in_recovery);
 | 
			
		||||
    if (!pacing_rate)
 | 
			
		||||
        pacing_rate = 1;
 | 
			
		||||
    tx_time = (uint64_t) SC_PACK_SIZE(ctl) * 1000000 / pacing_rate;
 | 
			
		||||
    return tx_time;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3788,6 +3790,8 @@ lsquic_send_ctl_can_send_probe (const struct lsquic_send_ctl *ctl,
 | 
			
		|||
        if (n_out + path->np_pack_size >= cwnd)
 | 
			
		||||
            return 0;
 | 
			
		||||
        pacing_rate = ctl->sc_ci->cci_pacing_rate(CGP(ctl), 0);
 | 
			
		||||
        if (!pacing_rate)
 | 
			
		||||
            pacing_rate = 1;
 | 
			
		||||
        tx_time = (uint64_t) path->np_pack_size * 1000000 / pacing_rate;
 | 
			
		||||
        return lsquic_pacer_can_schedule_probe(&ctl->sc_pacer,
 | 
			
		||||
                   ctl->sc_n_scheduled + ctl->sc_n_in_flight_all, tx_time);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -623,15 +623,13 @@ lsquic_stream_drop_hset_ref (struct lsquic_stream *stream)
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
destroy_uh (struct lsquic_stream *stream)
 | 
			
		||||
destroy_uh (struct uncompressed_headers *uh, const struct lsquic_hset_if *hsi_if)
 | 
			
		||||
{
 | 
			
		||||
    if (stream->uh)
 | 
			
		||||
    if (uh)
 | 
			
		||||
    {
 | 
			
		||||
        if (stream->uh->uh_hset)
 | 
			
		||||
            stream->conn_pub->enpub->enp_hsi_if
 | 
			
		||||
                            ->hsi_discard_header_set(stream->uh->uh_hset);
 | 
			
		||||
        free(stream->uh);
 | 
			
		||||
        stream->uh = NULL;
 | 
			
		||||
        if (uh->uh_hset)
 | 
			
		||||
            hsi_if->hsi_discard_header_set(uh->uh_hset);
 | 
			
		||||
        free(uh);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -641,6 +639,7 @@ lsquic_stream_destroy (lsquic_stream_t *stream)
 | 
			
		|||
{
 | 
			
		||||
    struct push_promise *promise;
 | 
			
		||||
    struct stream_hq_frame *shf;
 | 
			
		||||
    struct uncompressed_headers *uh;
 | 
			
		||||
 | 
			
		||||
    stream->stream_flags |= STREAM_U_WRITE_DONE|STREAM_U_READ_DONE;
 | 
			
		||||
    if ((stream->stream_flags & (STREAM_ONNEW_DONE|STREAM_ONCLOSE_DONE)) ==
 | 
			
		||||
| 
						 | 
				
			
			@ -687,7 +686,12 @@ lsquic_stream_destroy (lsquic_stream_t *stream)
 | 
			
		|||
    }
 | 
			
		||||
    while ((shf = STAILQ_FIRST(&stream->sm_hq_frames)))
 | 
			
		||||
        stream_hq_frame_put(stream, shf);
 | 
			
		||||
    destroy_uh(stream);
 | 
			
		||||
    while(stream->uh)
 | 
			
		||||
    {
 | 
			
		||||
        uh = stream->uh;
 | 
			
		||||
        stream->uh = uh->uh_next;
 | 
			
		||||
        destroy_uh(uh, stream->conn_pub->enpub->enp_hsi_if);
 | 
			
		||||
    }
 | 
			
		||||
    free(stream->sm_buf);
 | 
			
		||||
    free(stream->sm_header_block);
 | 
			
		||||
    LSQ_DEBUG("destroyed stream");
 | 
			
		||||
| 
						 | 
				
			
			@ -1443,7 +1447,8 @@ static size_t
 | 
			
		|||
read_uh (struct lsquic_stream *stream,
 | 
			
		||||
        size_t (*readf)(void *, const unsigned char *, size_t, int), void *ctx)
 | 
			
		||||
{
 | 
			
		||||
    struct http1x_headers *const h1h = stream->uh->uh_hset;
 | 
			
		||||
    struct uncompressed_headers *uh = stream->uh;
 | 
			
		||||
    struct http1x_headers *const h1h = uh->uh_hset;
 | 
			
		||||
    size_t nread;
 | 
			
		||||
 | 
			
		||||
    nread = readf(ctx, (unsigned char *) h1h->h1h_buf + h1h->h1h_off,
 | 
			
		||||
| 
						 | 
				
			
			@ -1452,8 +1457,10 @@ read_uh (struct lsquic_stream *stream,
 | 
			
		|||
    h1h->h1h_off += nread;
 | 
			
		||||
    if (h1h->h1h_off == h1h->h1h_size)
 | 
			
		||||
    {
 | 
			
		||||
        LSQ_DEBUG("read all uncompressed headers");
 | 
			
		||||
        destroy_uh(stream);
 | 
			
		||||
        stream->uh = uh->uh_next;
 | 
			
		||||
        LSQ_DEBUG("read all uncompressed headers from uh: %p, next uh: %p",
 | 
			
		||||
                    uh, stream->uh);
 | 
			
		||||
        destroy_uh(uh, stream->conn_pub->enpub->enp_hsi_if);
 | 
			
		||||
        if (stream->stream_flags & STREAM_HEAD_IN_FIN)
 | 
			
		||||
        {
 | 
			
		||||
            stream->stream_flags |= STREAM_FIN_REACHED;
 | 
			
		||||
| 
						 | 
				
			
			@ -4186,7 +4193,7 @@ lsquic_stream_send_headers (lsquic_stream_t *stream,
 | 
			
		|||
                            const lsquic_http_headers_t *headers, int eos)
 | 
			
		||||
{
 | 
			
		||||
    if ((stream->sm_bflags & SMBF_USE_HEADERS)
 | 
			
		||||
            && !(stream->stream_flags & (STREAM_HEADERS_SENT|STREAM_U_WRITE_DONE)))
 | 
			
		||||
            && !(stream->stream_flags & (STREAM_U_WRITE_DONE)))
 | 
			
		||||
    {
 | 
			
		||||
        if (stream->sm_bflags & SMBF_IETF)
 | 
			
		||||
            return send_headers_ietf(stream, headers, eos);
 | 
			
		||||
| 
						 | 
				
			
			@ -4411,15 +4418,19 @@ static int
 | 
			
		|||
stream_uh_in_gquic (struct lsquic_stream *stream,
 | 
			
		||||
                                            struct uncompressed_headers *uh)
 | 
			
		||||
{
 | 
			
		||||
    if ((stream->sm_bflags & SMBF_USE_HEADERS)
 | 
			
		||||
                                    && !(stream->stream_flags & STREAM_HAVE_UH))
 | 
			
		||||
    struct uncompressed_headers **next;
 | 
			
		||||
    if ((stream->sm_bflags & SMBF_USE_HEADERS))
 | 
			
		||||
    {
 | 
			
		||||
        SM_HISTORY_APPEND(stream, SHE_HEADERS_IN);
 | 
			
		||||
        LSQ_DEBUG("received uncompressed headers");
 | 
			
		||||
        stream->stream_flags |= STREAM_HAVE_UH;
 | 
			
		||||
        if (uh->uh_flags & UH_FIN)
 | 
			
		||||
            stream->stream_flags |= STREAM_FIN_RECVD|STREAM_HEAD_IN_FIN;
 | 
			
		||||
        stream->uh = uh;
 | 
			
		||||
        next = &stream->uh;
 | 
			
		||||
        while(*next)
 | 
			
		||||
            next = &(*next)->uh_next;
 | 
			
		||||
        *next = uh;
 | 
			
		||||
        assert(uh->uh_next == NULL);
 | 
			
		||||
        if (uh->uh_oth_stream_id == 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (uh->uh_weight)
 | 
			
		||||
| 
						 | 
				
			
			@ -4443,9 +4454,10 @@ stream_uh_in_ietf (struct lsquic_stream *stream,
 | 
			
		|||
                                            struct uncompressed_headers *uh)
 | 
			
		||||
{
 | 
			
		||||
    int push_promise;
 | 
			
		||||
    struct uncompressed_headers **next;
 | 
			
		||||
 | 
			
		||||
    push_promise = lsquic_stream_header_is_pp(stream);
 | 
			
		||||
    if (!(stream->stream_flags & STREAM_HAVE_UH) && !push_promise)
 | 
			
		||||
    if (!push_promise)
 | 
			
		||||
    {
 | 
			
		||||
        SM_HISTORY_APPEND(stream, SHE_HEADERS_IN);
 | 
			
		||||
        LSQ_DEBUG("received uncompressed headers");
 | 
			
		||||
| 
						 | 
				
			
			@ -4460,7 +4472,11 @@ stream_uh_in_ietf (struct lsquic_stream *stream,
 | 
			
		|||
                                            && lsquic_stream_is_pushed(stream));
 | 
			
		||||
            stream->stream_flags |= STREAM_FIN_RECVD|STREAM_HEAD_IN_FIN;
 | 
			
		||||
        }
 | 
			
		||||
        stream->uh = uh;
 | 
			
		||||
        next = &stream->uh;
 | 
			
		||||
        while(*next)
 | 
			
		||||
            next = &(*next)->uh_next;
 | 
			
		||||
        *next = uh;
 | 
			
		||||
        assert(uh->uh_next == NULL);
 | 
			
		||||
        if (uh->uh_oth_stream_id == 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (uh->uh_weight)
 | 
			
		||||
| 
						 | 
				
			
			@ -4652,6 +4668,7 @@ void *
 | 
			
		|||
lsquic_stream_get_hset (struct lsquic_stream *stream)
 | 
			
		||||
{
 | 
			
		||||
    void *hset;
 | 
			
		||||
    struct uncompressed_headers *uh;
 | 
			
		||||
 | 
			
		||||
    if (stream_is_read_reset(stream))
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -4676,9 +4693,14 @@ lsquic_stream_get_hset (struct lsquic_stream *stream)
 | 
			
		|||
 | 
			
		||||
    hset = stream->uh->uh_hset;
 | 
			
		||||
    stream->uh->uh_hset = NULL;
 | 
			
		||||
    destroy_uh(stream);
 | 
			
		||||
 | 
			
		||||
    uh = stream->uh;
 | 
			
		||||
    stream->uh = uh->uh_next;
 | 
			
		||||
    free(uh);
 | 
			
		||||
 | 
			
		||||
    if (stream->stream_flags & STREAM_HEAD_IN_FIN)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        stream->stream_flags |= STREAM_FIN_REACHED;
 | 
			
		||||
        SM_HISTORY_APPEND(stream, SHE_REACH_FIN);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -4706,31 +4728,21 @@ static int
 | 
			
		|||
update_type_hist_and_check (const struct lsquic_stream *stream,
 | 
			
		||||
                                                    struct hq_filter *filter)
 | 
			
		||||
{
 | 
			
		||||
    /* 3-bit codes: */
 | 
			
		||||
    enum {
 | 
			
		||||
        CODE_UNSET,
 | 
			
		||||
        CODE_HEADER,    /* H    Header  */
 | 
			
		||||
        CODE_DATA,      /* D    Data    */
 | 
			
		||||
        CODE_PLUS,      /* +    Plus: meaning previous frame repeats */
 | 
			
		||||
    };
 | 
			
		||||
    static const unsigned valid_seqs[] = {
 | 
			
		||||
        /* Ordered by expected frequency */
 | 
			
		||||
        0123,   /* HD+  */
 | 
			
		||||
        012,    /* HD   */
 | 
			
		||||
        01,     /* H    */
 | 
			
		||||
        013,    /* H+   */ /* Really HH, but we don't record it like this */
 | 
			
		||||
        01231,  /* HD+H */
 | 
			
		||||
        0121,   /* HDH  */
 | 
			
		||||
    };
 | 
			
		||||
    unsigned code, i;
 | 
			
		||||
 | 
			
		||||
    switch (filter->hqfi_type)
 | 
			
		||||
    {
 | 
			
		||||
    case HQFT_HEADERS:
 | 
			
		||||
        code = CODE_HEADER;
 | 
			
		||||
        if (filter->hqfi_flags & HQFI_FLAG_TRAILER)
 | 
			
		||||
            return -1;
 | 
			
		||||
        if (filter->hqfi_flags & HQFI_FLAG_DATA)
 | 
			
		||||
            filter->hqfi_flags |= HQFI_FLAG_TRAILER;
 | 
			
		||||
        else
 | 
			
		||||
            filter->hqfi_flags |= HQFI_FLAG_HEADER;
 | 
			
		||||
        break;
 | 
			
		||||
    case HQFT_DATA:
 | 
			
		||||
        code = CODE_DATA;
 | 
			
		||||
        if ((filter->hqfi_flags & (HQFI_FLAG_HEADER
 | 
			
		||||
              | HQFI_FLAG_TRAILER)) != HQFI_FLAG_HEADER)
 | 
			
		||||
            return -1;
 | 
			
		||||
        filter->hqfi_flags |= HQFI_FLAG_DATA;
 | 
			
		||||
        break;
 | 
			
		||||
    case HQFT_PUSH_PROMISE:
 | 
			
		||||
        /* [draft-ietf-quic-http-24], Section 7 */
 | 
			
		||||
| 
						 | 
				
			
			@ -4768,31 +4780,7 @@ update_type_hist_and_check (const struct lsquic_stream *stream,
 | 
			
		|||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (filter->hqfi_hist_idx >= MAX_HQFI_ENTRIES)
 | 
			
		||||
        return -1;
 | 
			
		||||
 | 
			
		||||
    if (filter->hqfi_hist_idx && (filter->hqfi_hist_buf & 7) == code)
 | 
			
		||||
    {
 | 
			
		||||
        filter->hqfi_hist_buf <<= 3;
 | 
			
		||||
        filter->hqfi_hist_buf |= CODE_PLUS;
 | 
			
		||||
        filter->hqfi_hist_idx++;
 | 
			
		||||
    }
 | 
			
		||||
    else if (filter->hqfi_hist_idx > 1
 | 
			
		||||
            && ((filter->hqfi_hist_buf >> 3) & 7) == code
 | 
			
		||||
            && (filter->hqfi_hist_buf & 7) == CODE_PLUS)
 | 
			
		||||
        /* Keep it at plus, do nothing */;
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        filter->hqfi_hist_buf <<= 3;
 | 
			
		||||
        filter->hqfi_hist_buf |= code;
 | 
			
		||||
        filter->hqfi_hist_idx++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (i = 0; i < sizeof(valid_seqs) / sizeof(valid_seqs[0]); ++i)
 | 
			
		||||
        if (filter->hqfi_hist_buf == valid_seqs[i])
 | 
			
		||||
    return 0;
 | 
			
		||||
 | 
			
		||||
    return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4860,8 +4848,7 @@ hq_read (void *ctx, const unsigned char *buf, size_t sz, int fin)
 | 
			
		|||
            {
 | 
			
		||||
                lconn = stream->conn_pub->lconn;
 | 
			
		||||
                filter->hqfi_flags |= HQFI_FLAG_ERROR;
 | 
			
		||||
                LSQ_INFO("unexpected HTTP/3 frame sequence: %o",
 | 
			
		||||
                    filter->hqfi_hist_buf);
 | 
			
		||||
                LSQ_INFO("unexpected HTTP/3 frame sequence");
 | 
			
		||||
                lconn->cn_if->ci_abort_error(lconn, 1, HEC_FRAME_UNEXPECTED,
 | 
			
		||||
                    "unexpected HTTP/3 frame sequence on stream %"PRIu64,
 | 
			
		||||
                    stream->id);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -109,6 +109,9 @@ struct hq_filter
 | 
			
		|||
        HQFI_FLAG_ERROR         = 1 << 1,
 | 
			
		||||
        HQFI_FLAG_BEGIN         = 1 << 2,
 | 
			
		||||
        HQFI_FLAG_BLOCKED       = 1 << 3,
 | 
			
		||||
        HQFI_FLAG_HEADER        = 1 << 4,
 | 
			
		||||
        HQFI_FLAG_DATA          = 1 << 5,
 | 
			
		||||
        HQFI_FLAG_TRAILER       = 1 << 6,
 | 
			
		||||
    }                           hqfi_flags:8;
 | 
			
		||||
    enum {
 | 
			
		||||
        HQFI_STATE_FRAME_HEADER_BEGIN,
 | 
			
		||||
| 
						 | 
				
			
			@ -117,9 +120,6 @@ struct hq_filter
 | 
			
		|||
        HQFI_STATE_PUSH_ID_BEGIN,
 | 
			
		||||
        HQFI_STATE_PUSH_ID_CONTINUE,
 | 
			
		||||
    }                           hqfi_state:8;
 | 
			
		||||
    unsigned char               hqfi_hist_idx;
 | 
			
		||||
#define MAX_HQFI_ENTRIES (sizeof(unsigned) * 8 / 3)
 | 
			
		||||
    unsigned                    hqfi_hist_buf;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue