blob: 6e4774d4437a137aadfd541199ad297ae611204f [file] [log] [blame]
/*
* include/haproxy/quic_conn.h
*
* Copyright 2020 HAProxy Technologies, Frederic Lecaille <flecaille@haproxy.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, version 2.1
* exclusively.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _HAPROXY_QUIC_CONN_H
#define _HAPROXY_QUIC_CONN_H
#ifdef USE_QUIC
#ifndef USE_OPENSSL
#error "Must define USE_OPENSSL"
#endif
#include <inttypes.h>
#include <import/eb64tree.h>
#include <import/ebmbtree.h>
#include <haproxy/buf.h>
#include <haproxy/chunk.h>
#include <haproxy/ncbuf-t.h>
#include <haproxy/net_helper.h>
#include <haproxy/openssl-compat.h>
#include <haproxy/ticks.h>
#include <haproxy/listener.h>
#include <haproxy/quic_cc.h>
#include <haproxy/quic_conn-t.h>
#include <haproxy/quic_enc.h>
#include <haproxy/quic_frame.h>
#include <haproxy/quic_loss.h>
#include <haproxy/mux_quic.h>
#include <openssl/rand.h>
extern struct pool_head *pool_head_quic_connection_id;
int qc_conn_finalize(struct quic_conn *qc, int server);
int ssl_quic_initial_ctx(struct bind_conf *bind_conf);
/* Return the long packet type matching with <qv> version and <type> */
static inline int quic_pkt_type(int type, uint32_t version)
{
if (version != QUIC_PROTOCOL_VERSION_2_DRAFT)
return type;
switch (type) {
case QUIC_PACKET_TYPE_INITIAL:
return 1;
case QUIC_PACKET_TYPE_0RTT:
return 2;
case QUIC_PACKET_TYPE_HANDSHAKE:
return 3;
case QUIC_PACKET_TYPE_RETRY:
return 0;
}
return -1;
}
static inline int qc_is_listener(struct quic_conn *qc)
{
return qc->flags & QUIC_FL_CONN_LISTENER;
}
/* Copy <src> QUIC CID to <dst>.
* This is the responsibility of the caller to check there is enough room in
* <dst> to copy <src>.
* Always succeeds.
*/
static inline void quic_cid_cpy(struct quic_cid *dst, const struct quic_cid *src)
{
memcpy(dst->data, src->data, src->len);
dst->len = src->len;
}
/* Copy <saddr> socket address data into <buf> buffer.
* This is the responsibility of the caller to check the output buffer is big
* enough to contain these socket address data.
* Return the number of bytes copied.
*/
static inline size_t quic_saddr_cpy(unsigned char *buf,
const struct sockaddr_storage *saddr)
{
void *port, *addr;
unsigned char *p;
size_t port_len, addr_len;
p = buf;
if (saddr->ss_family == AF_INET6) {
port = &((struct sockaddr_in6 *)saddr)->sin6_port;
addr = &((struct sockaddr_in6 *)saddr)->sin6_addr;
port_len = sizeof ((struct sockaddr_in6 *)saddr)->sin6_port;
addr_len = sizeof ((struct sockaddr_in6 *)saddr)->sin6_addr;
}
else {
port = &((struct sockaddr_in *)saddr)->sin_port;
addr = &((struct sockaddr_in *)saddr)->sin_addr;
port_len = sizeof ((struct sockaddr_in *)saddr)->sin_port;
addr_len = sizeof ((struct sockaddr_in *)saddr)->sin_addr;
}
memcpy(p, port, port_len);
p += port_len;
memcpy(p, addr, addr_len);
p += addr_len;
return p - buf;
}
/* Concatenate the port and address of <saddr> to <cid> QUIC connection ID. The
* <addrlen> field of <cid> will be updated with the size of the concatenated
* address.
*
* Returns the number of bytes concatenated to <cid>.
*/
static inline size_t quic_cid_saddr_cat(struct quic_cid *cid,
struct sockaddr_storage *saddr)
{
void *port, *addr;
size_t port_len, addr_len;
cid->addrlen = 0;
if (saddr->ss_family == AF_INET6) {
port = &((struct sockaddr_in6 *)saddr)->sin6_port;
addr = &((struct sockaddr_in6 *)saddr)->sin6_addr;
port_len = sizeof ((struct sockaddr_in6 *)saddr)->sin6_port;
addr_len = sizeof ((struct sockaddr_in6 *)saddr)->sin6_addr;
}
else {
port = &((struct sockaddr_in *)saddr)->sin_port;
addr = &((struct sockaddr_in *)saddr)->sin_addr;
port_len = sizeof ((struct sockaddr_in *)saddr)->sin_port;
addr_len = sizeof ((struct sockaddr_in *)saddr)->sin_addr;
}
memcpy(cid->data + cid->len, port, port_len);
cid->addrlen += port_len;
memcpy(cid->data + cid->len + port_len, addr, addr_len);
cid->addrlen += addr_len;
return port_len + addr_len;
}
/* Dump the QUIC connection ID value if present (non null length). Used only for
* debugging purposes.
* Always succeeds.
*/
static inline void quic_cid_dump(struct buffer *buf,
const struct quic_cid *cid)
{
int i;
chunk_appendf(buf, "(%d", cid->len);
if (cid->len)
chunk_appendf(buf, ",");
for (i = 0; i < cid->len; i++)
chunk_appendf(buf, "%02x", cid->data[i]);
chunk_appendf(buf, ")");
}
/* Free the CIDs attached to <conn> QUIC connection. This must be called under
* the CID lock.
*/
static inline void free_quic_conn_cids(struct quic_conn *conn)
{
struct eb64_node *node;
node = eb64_first(&conn->cids);
while (node) {
struct quic_connection_id *cid;
cid = eb64_entry(node, struct quic_connection_id, seq_num);
/* remove the CID from the receiver tree */
ebmb_delete(&cid->node);
/* remove the CID from the quic_conn tree */
node = eb64_next(node);
eb64_delete(&cid->seq_num);
pool_free(pool_head_quic_connection_id, cid);
}
}
/* Copy <src> new connection ID information to <to> NEW_CONNECTION_ID frame data.
* Always succeeds.
*/
static inline void quic_connection_id_to_frm_cpy(struct quic_frame *dst,
struct quic_connection_id *src)
{
struct quic_new_connection_id *to = &dst->new_connection_id;
dst->type = QUIC_FT_NEW_CONNECTION_ID;
to->seq_num = src->seq_num.key;
to->retire_prior_to = src->retire_prior_to;
to->cid.len = src->cid.len;
to->cid.data = src->cid.data;
to->stateless_reset_token = src->stateless_reset_token;
}
/* extract a TID from a CID for bind_conf <bc>, from 0 to global.nbthread-1 and
* in any case no more than 4095. It takes into account the bind_conf's thread
* group and the bind_conf's thread mask. The algorithm is the following: most
* packets contain a valid thread ID for the bind_conf, which means that the
* retrieved ID directly maps to a bound thread ID. If that's not the case,
* then we have to remap it. The resulting thread ID will then differ but will
* be correctly encoded and decoded.
*/
static inline uint quic_get_cid_tid(const unsigned char *cid, const struct bind_conf *bc)
{
uint id, grp;
uint base, count;
id = read_n16(cid) & 4095;
grp = bc->bind_tgroup;
base = ha_tgroup_info[grp - 1].base;
count = ha_tgroup_info[grp - 1].count;
if (base <= id && id < base + count &&
bc->bind_thread & ha_thread_info[id].ltid_bit)
return id; // part of the group and bound: valid
/* The thread number isn't valid, it doesn't map to a thread bound on
* this receiver. Let's reduce it to one of the thread(s) valid for
* that receiver.
*/
count = my_popcountl(bc->bind_thread);
id = count - 1 - id % count;
id = mask_find_rank_bit(id, bc->bind_thread);
id += base;
return id;
}
/* Modify <cid> to have a CID linked to the thread ID <target_tid> that
* quic_get_cid_tid() will be able to extract return.
*/
static inline void quic_pin_cid_to_tid(unsigned char *cid, uint target_tid)
{
uint16_t prev_id;
prev_id = read_n16(cid);
write_n16(cid, (prev_id & ~4095) | target_tid);
}
/* Return a 32-bits integer in <val> from QUIC packet with <buf> as address.
* Makes <buf> point to the data after this 32-bits value if succeeded.
* Note that these 32-bits integers are network bytes ordered.
* Returns 0 if failed (not enough data in the buffer), 1 if succeeded.
*/
static inline int quic_read_uint32(uint32_t *val,
const unsigned char **buf,
const unsigned char *end)
{
if (end - *buf < sizeof *val)
return 0;
*val = ntohl(*(uint32_t *)*buf);
*buf += sizeof *val;
return 1;
}
/* Write a 32-bits integer to a buffer with <buf> as address.
* Make <buf> point to the data after this 32-buts value if succeeded.
* Note that these 32-bits integers are networkg bytes ordered.
* Returns 0 if failed (not enough room in the buffer), 1 if succeeded.
*/
static inline int quic_write_uint32(unsigned char **buf,
const unsigned char *end, uint32_t val)
{
if (end - *buf < sizeof val)
return 0;
*(uint32_t *)*buf = htonl(val);
*buf += sizeof val;
return 1;
}
/* Return the maximum number of bytes we must use to completely fill a
* buffer with <sz> as size for a data field of bytes prefixed by its QUIC
* variable-length (may be 0).
* Also put in <*len_sz> the size of this QUIC variable-length.
* So after returning from this function we have : <*len_sz> + <ret> <= <sz>
* (<*len_sz> = { max(i), i + ret <= <sz> }) .
*/
static inline size_t max_available_room(size_t sz, size_t *len_sz)
{
size_t sz_sz, ret;
size_t diff;
sz_sz = quic_int_getsize(sz);
if (sz <= sz_sz)
return 0;
ret = sz - sz_sz;
*len_sz = quic_int_getsize(ret);
/* Difference between the two sizes. Note that <sz_sz> >= <*len_sz>. */
diff = sz_sz - *len_sz;
if (unlikely(diff > 0)) {
/* Let's try to take into an account remaining bytes.
*
* <----------------> <sz_sz>
* <--------------><--------> +----> <max_int>
* <ret> <len_sz> |
* +---------------------------+-----------....
* <--------------------------------> <sz>
*/
size_t max_int = quic_max_int(*len_sz);
if (max_int + *len_sz <= sz)
ret = max_int;
else
ret = sz - diff;
}
return ret;
}
/* This function computes the maximum data we can put into a buffer with <sz> as
* size prefixed with a variable-length field "Length" whose value is the
* remaining data length, already filled of <ilen> bytes which must be taken
* into an account by "Length" field, and finally followed by the data we want
* to put in this buffer prefixed again by a variable-length field.
* <sz> is the size of the buffer to fill.
* <ilen> the number of bytes already put after the "Length" field.
* <dlen> the number of bytes we want to at most put in the buffer.
* Also set <*dlen_sz> to the size of the data variable-length we want to put in
* the buffer. This is typically this function which must be used to fill as
* much as possible a QUIC packet made of only one CRYPTO or STREAM frames.
* Returns this computed size if there is enough room in the buffer, 0 if not.
*/
static inline size_t max_stream_data_size(size_t sz, size_t ilen, size_t dlen)
{
size_t ret, len_sz, dlen_sz;
/*
* The length of variable-length QUIC integers are powers of two.
* Look for the first 3length" field value <len_sz> which match our need.
* As we must put <ilen> bytes in our buffer, the minimum value for
* <len_sz> is the number of bytes required to encode <ilen>.
*/
for (len_sz = quic_int_getsize(ilen);
len_sz <= QUIC_VARINT_MAX_SIZE;
len_sz <<= 1) {
if (sz < len_sz + ilen)
return 0;
ret = max_available_room(sz - len_sz - ilen, &dlen_sz);
if (!ret)
return 0;
/* Check that <*len_sz> matches <ret> value */
if (len_sz + ilen + dlen_sz + ret <= quic_max_int(len_sz))
return ret < dlen ? ret : dlen;
}
return 0;
}
/* Return the length in bytes of <pn> packet number depending on
* <largest_acked_pn> the largest ackownledged packet number.
*/
static inline size_t quic_packet_number_length(int64_t pn,
int64_t largest_acked_pn)
{
int64_t max_nack_pkts;
/* About packet number encoding, the RFC says:
* The sender MUST use a packet number size able to represent more than
* twice as large a range than the difference between the largest
* acknowledged packet and packet number being sent.
*/
max_nack_pkts = 2 * (pn - largest_acked_pn) + 1;
if (max_nack_pkts > 0xffffff)
return 4;
if (max_nack_pkts > 0xffff)
return 3;
if (max_nack_pkts > 0xff)
return 2;
return 1;
}
/* Encode <pn> packet number with <pn_len> as length in byte into a buffer with
* <buf> as current copy address and <end> as pointer to one past the end of
* this buffer. This is the responsibility of the caller to check there is
* enough room in the buffer to copy <pn_len> bytes.
* Never fails.
*/
static inline int quic_packet_number_encode(unsigned char **buf,
const unsigned char *end,
uint64_t pn, size_t pn_len)
{
if (end - *buf < pn_len)
return 0;
/* Encode the packet number. */
switch (pn_len) {
case 1:
**buf = pn;
break;
case 2:
write_n16(*buf, pn);
break;
case 3:
(*buf)[0] = pn >> 16;
(*buf)[1] = pn >> 8;
(*buf)[2] = pn;
break;
case 4:
write_n32(*buf, pn);
break;
}
*buf += pn_len;
return 1;
}
/* Returns the <ack_delay> field value in milliseconds from <ack_frm> ACK frame for
* <conn> QUIC connection. Note that the value of <ack_delay> coming from
* ACK frame is in microseconds.
*/
static inline unsigned int quic_ack_delay_ms(struct quic_ack *ack_frm,
struct quic_conn *conn)
{
return (ack_frm->ack_delay << conn->tx.params.ack_delay_exponent) / 1000;
}
/* Returns the <ack_delay> field value in microsecond to be set in an ACK frame
* depending on the time the packet with a new largest packet number was received.
*/
static inline uint64_t quic_compute_ack_delay_us(unsigned int time_received,
struct quic_conn *conn)
{
return ((now_ms - time_received) * 1000) >> conn->tx.params.ack_delay_exponent;
}
/* Initialize a QUIC packet number space.
* Never fails.
*/
static inline void quic_pktns_init(struct quic_pktns *pktns)
{
LIST_INIT(&pktns->tx.frms);
pktns->tx.next_pn = -1;
pktns->tx.pkts = EB_ROOT_UNIQUE;
pktns->tx.time_of_last_eliciting = 0;
pktns->tx.loss_time = TICK_ETERNITY;
pktns->tx.in_flight = 0;
pktns->tx.ack_delay = 0;
pktns->rx.largest_pn = -1;
pktns->rx.largest_acked_pn = -1;
pktns->rx.arngs.root = EB_ROOT_UNIQUE;
pktns->rx.arngs.sz = 0;
pktns->rx.arngs.enc_sz = 0;
pktns->rx.nb_aepkts_since_last_ack = 0;
pktns->rx.largest_time_received = 0;
pktns->flags = 0;
}
/* Returns the current largest acknowledged packet number if exists, -1 if not */
static inline int64_t quic_pktns_get_largest_acked_pn(struct quic_pktns *pktns)
{
struct eb64_node *ar = eb64_last(&pktns->rx.arngs.root);
if (!ar)
return -1;
return eb64_entry(ar, struct quic_arng_node, first)->last;
}
/* The TX packets sent in the same datagram are linked to each others in
* the order they are built. This function detach a packet from its successor
* and predecessor in the same datagram.
*/
static inline void quic_tx_packet_dgram_detach(struct quic_tx_packet *pkt)
{
if (pkt->prev)
pkt->prev->next = pkt->next;
if (pkt->next)
pkt->next->prev = pkt->prev;
}
/* Increment the reference counter of <pkt> */
static inline void quic_tx_packet_refinc(struct quic_tx_packet *pkt)
{
HA_ATOMIC_ADD(&pkt->refcnt, 1);
}
/* Decrement the reference counter of <pkt> */
static inline void quic_tx_packet_refdec(struct quic_tx_packet *pkt)
{
if (!HA_ATOMIC_SUB_FETCH(&pkt->refcnt, 1)) {
BUG_ON(!LIST_ISEMPTY(&pkt->frms));
/* If there are others packet in the same datagram <pkt> is attached to,
* detach the previous one and the next one from <pkt>.
*/
quic_tx_packet_dgram_detach(pkt);
pool_free(pool_head_quic_tx_packet, pkt);
}
}
static inline void quic_pktns_tx_pkts_release(struct quic_pktns *pktns, struct quic_conn *qc)
{
struct eb64_node *node;
node = eb64_first(&pktns->tx.pkts);
while (node) {
struct quic_tx_packet *pkt;
struct quic_frame *frm, *frmbak;
pkt = eb64_entry(node, struct quic_tx_packet, pn_node);
node = eb64_next(node);
if (pkt->flags & QUIC_FL_TX_PACKET_ACK_ELICITING)
qc->path->ifae_pkts--;
list_for_each_entry_safe(frm, frmbak, &pkt->frms, list) {
LIST_DELETE(&frm->list);
quic_tx_packet_refdec(frm->pkt);
pool_free(pool_head_quic_frame, frm);
}
eb64_delete(&pkt->pn_node);
quic_tx_packet_refdec(pkt);
}
}
/* Discard <pktns> packet number space attached to <qc> QUIC connection.
* Its loss information are reset. Deduce the outstanding bytes for this
* packet number space from the outstanding bytes for the path of this
* connection.
* Note that all the non acknowledged TX packets and their frames are freed.
* Always succeeds.
*/
static inline void quic_pktns_discard(struct quic_pktns *pktns,
struct quic_conn *qc)
{
qc->path->in_flight -= pktns->tx.in_flight;
qc->path->prep_in_flight -= pktns->tx.in_flight;
qc->path->loss.pto_count = 0;
pktns->tx.time_of_last_eliciting = 0;
pktns->tx.loss_time = TICK_ETERNITY;
pktns->tx.pto_probe = 0;
pktns->tx.in_flight = 0;
quic_pktns_tx_pkts_release(pktns, qc);
}
/* Initialize <p> QUIC network path depending on <ipv4> boolean
* which is true for an IPv4 path, if not false for an IPv6 path.
*/
static inline void quic_path_init(struct quic_path *path, int ipv4,
struct quic_cc_algo *algo, struct quic_conn *qc)
{
unsigned int max_dgram_sz;
max_dgram_sz = ipv4 ? QUIC_INITIAL_IPV4_MTU : QUIC_INITIAL_IPV6_MTU;
quic_loss_init(&path->loss);
path->mtu = max_dgram_sz;
path->cwnd = QUIC_MIN(10 * max_dgram_sz, QUIC_MAX(max_dgram_sz << 1, 14720U));
path->min_cwnd = max_dgram_sz << 1;
path->prep_in_flight = 0;
path->in_flight = 0;
path->ifae_pkts = 0;
quic_cc_init(&path->cc, algo, qc);
}
/* Return the remaining <room> available on <path> QUIC path. In fact this this
*the remaining number of bytes available in the congestion controller window.
*/
static inline size_t quic_path_room(struct quic_path *path)
{
if (path->in_flight > path->cwnd)
return 0;
return path->cwnd - path->in_flight;
}
/* Return the remaining <room> available on <path> QUIC path for prepared data
* (before being sent). Almost the same that for the QUIC path room, except that
* here this is the data which have been prepared which are taken into an account.
*/
static inline size_t quic_path_prep_data(struct quic_path *path)
{
if (path->prep_in_flight > path->cwnd)
return 0;
return path->cwnd - path->prep_in_flight;
}
/* Return 1 if <pktns> matches with the Application packet number space of
* <conn> connection which is common to the 0-RTT and 1-RTT encryption levels, 0
* if not (handshake packets).
*/
static inline int quic_application_pktns(struct quic_pktns *pktns, struct quic_conn *conn)
{
return pktns == &conn->pktns[QUIC_TLS_PKTNS_01RTT];
}
/* CRYPTO data buffer handling functions. */
static inline unsigned char *c_buf_getpos(struct quic_enc_level *qel, uint64_t offset)
{
int idx;
unsigned char *data;
idx = offset >> QUIC_CRYPTO_BUF_SHIFT;
data = qel->tx.crypto.bufs[idx]->data;
return data + (offset & QUIC_CRYPTO_BUF_MASK);
}
/* Returns 1 if the CRYPTO buffer at <qel> encryption level has been
* consumed (sent to the peer), 0 if not.
*/
static inline int c_buf_consumed(struct quic_enc_level *qel)
{
return qel->tx.crypto.offset == qel->tx.crypto.sz;
}
/* Return 1 if <pkt> header form is long, 0 if not. */
static inline int qc_pkt_long(const struct quic_rx_packet *pkt)
{
return pkt->type != QUIC_PACKET_TYPE_SHORT;
}
/* Return 1 if there is RX packets for <qel> QUIC encryption level, 0 if not */
static inline int qc_el_rx_pkts(struct quic_enc_level *qel)
{
int ret;
ret = !eb_is_empty(&qel->rx.pkts);
return ret;
}
/* Release the memory for the RX packets which are no more referenced
* and consume their payloads which have been copied to the RX buffer
* for the connection.
* Always succeeds.
*/
static inline void quic_rx_pkts_del(struct quic_conn *qc)
{
struct quic_rx_packet *pkt, *pktback;
list_for_each_entry_safe(pkt, pktback, &qc->rx.pkt_list, qc_rx_pkt_list) {
if (pkt->data != (unsigned char *)b_head(&qc->rx.buf)) {
size_t cdata;
cdata = b_contig_data(&qc->rx.buf, 0);
if (cdata && !*b_head(&qc->rx.buf)) {
/* Consume the remaining data */
b_del(&qc->rx.buf, cdata);
}
break;
}
if (HA_ATOMIC_LOAD(&pkt->refcnt))
break;
b_del(&qc->rx.buf, pkt->raw_len);
LIST_DELETE(&pkt->qc_rx_pkt_list);
pool_free(pool_head_quic_rx_packet, pkt);
}
/* In frequent cases the buffer will be emptied at this stage. */
b_realign_if_empty(&qc->rx.buf);
}
/* Increment the reference counter of <pkt> */
static inline void quic_rx_packet_refinc(struct quic_rx_packet *pkt)
{
HA_ATOMIC_ADD(&pkt->refcnt, 1);
}
/* Decrement the reference counter of <pkt> while remaining positive */
static inline void quic_rx_packet_refdec(struct quic_rx_packet *pkt)
{
unsigned int refcnt;
do {
refcnt = HA_ATOMIC_LOAD(&pkt->refcnt);
} while (refcnt && !HA_ATOMIC_CAS(&pkt->refcnt, &refcnt, refcnt - 1));
}
/* Delete all RX packets for <qel> QUIC encryption level */
static inline void qc_el_rx_pkts_del(struct quic_enc_level *qel)
{
struct eb64_node *node;
node = eb64_first(&qel->rx.pkts);
while (node) {
struct quic_rx_packet *pkt =
eb64_entry(node, struct quic_rx_packet, pn_node);
node = eb64_next(node);
eb64_delete(&pkt->pn_node);
quic_rx_packet_refdec(pkt);
}
}
static inline void qc_list_qel_rx_pkts(struct quic_enc_level *qel)
{
struct eb64_node *node;
node = eb64_first(&qel->rx.pkts);
while (node) {
struct quic_rx_packet *pkt;
pkt = eb64_entry(node, struct quic_rx_packet, pn_node);
fprintf(stderr, "pkt@%p type=%d pn=%llu\n",
pkt, pkt->type, (ull)pkt->pn_node.key);
node = eb64_next(node);
}
}
static inline void qc_list_all_rx_pkts(struct quic_conn *qc)
{
fprintf(stderr, "REMAINING QEL RX PKTS:\n");
qc_list_qel_rx_pkts(&qc->els[QUIC_TLS_ENC_LEVEL_INITIAL]);
qc_list_qel_rx_pkts(&qc->els[QUIC_TLS_ENC_LEVEL_EARLY_DATA]);
qc_list_qel_rx_pkts(&qc->els[QUIC_TLS_ENC_LEVEL_HANDSHAKE]);
qc_list_qel_rx_pkts(&qc->els[QUIC_TLS_ENC_LEVEL_APP]);
}
void chunk_frm_appendf(struct buffer *buf, const struct quic_frame *frm);
void quic_set_connection_close(struct quic_conn *qc, const struct quic_err err);
void quic_set_tls_alert(struct quic_conn *qc, int alert);
int quic_set_app_ops(struct quic_conn *qc, const unsigned char *alpn, size_t alpn_len);
int qc_check_dcid(struct quic_conn *qc, unsigned char *dcid, size_t dcid_len);
int quic_get_dgram_dcid(unsigned char *buf, const unsigned char *end,
unsigned char **dcid, size_t *dcid_len);
int qc_send_mux(struct quic_conn *qc, struct list *frms);
void qc_notify_close(struct quic_conn *qc);
void qc_release_frm(struct quic_conn *qc, struct quic_frame *frm);
void qc_check_close_on_released_mux(struct quic_conn *qc);
void quic_conn_release(struct quic_conn *qc);
int quic_dgram_parse(struct quic_dgram *dgram, struct quic_conn *qc,
struct listener *li);
#endif /* USE_QUIC */
#endif /* _HAPROXY_QUIC_CONN_H */