litespeed-quic/src/liblsquic/lsquic_frame_writer.c

675 lines
19 KiB
C

/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc. See LICENSE. */
/*
* lsquic_frame_writer.c -- write frames to HEADERS stream.
*
* The frame is first written to list of frame_buf's (frabs) and then
* out to the stream. This is done because frame's size is written out
* to the stream and we may not have enough room in the stream to fit
* the whole frame.
*/
#ifndef WIN32
#include <arpa/inet.h>
#endif
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include "lshpack.h"
#include "lsquic_mm.h"
#include "lsquic.h"
#include "lsquic_int_types.h"
#include "lsquic_hash.h"
#include "lsquic_conn.h"
#include "lsquic_frame_writer.h"
#include "lsquic_frame_common.h"
#include "lsquic_frab_list.h"
#include "lsquic_ev_log.h"
#include "fiu-local.h"
#define LSQUIC_LOGGER_MODULE LSQLM_FRAME_WRITER
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(\
lsquic_stream_conn(fw->fw_stream))
#include "lsquic_logger.h"
/* Size of the buffer passed to lshpack_enc_encode() -- this limits the size
* of a single compressed header field.
*/
#define MAX_COMP_HEADER_FIELD_SIZE (64 * 1024)
struct lsquic_frame_writer
{
struct lsquic_stream *fw_stream;
fw_writef_f fw_writef;
struct lsquic_mm *fw_mm;
struct lshpack_enc *fw_henc;
#if LSQUIC_CONN_STATS
struct conn_stats *fw_conn_stats;
#endif
struct frab_list fw_fral;
unsigned fw_max_frame_sz;
uint32_t fw_max_header_list_sz; /* 0 means unlimited */
enum {
FW_SERVER = (1 << 0),
} fw_flags;
};
/* RFC 7540, Section 4.2 */
#define MIN_MAX_FRAME_SIZE (1 << 14)
#define MAX_MAX_FRAME_SIZE ((1 << 24) - 1)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SETTINGS_FRAME_SZ 6
#define ABS_MIN_FRAME_SIZE MAX(SETTINGS_FRAME_SZ, \
sizeof(struct http_prio_frame))
static void *
fw_alloc (void *ctx, size_t size)
{
return lsquic_mm_get_4k(ctx);
}
struct lsquic_frame_writer *
lsquic_frame_writer_new (struct lsquic_mm *mm, struct lsquic_stream *stream,
unsigned max_frame_sz, struct lshpack_enc *henc, fw_writef_f writef,
#if LSQUIC_CONN_STATS
struct conn_stats *conn_stats,
#endif
int is_server)
{
struct lsquic_frame_writer *fw;
/* When frame writer is instantiated, limit the maximum size to
* MIN_MAX_FRAME_SIZE. The reference implementation has this value
* hardcoded and QUIC does not provide a mechanism to advertise a
* different value.
*/
if (0 == max_frame_sz)
max_frame_sz = MIN_MAX_FRAME_SIZE;
else
LSQ_LOG1(LSQ_LOG_WARN, "max frame size specified to be %u bytes "
"-- this better be test code!", max_frame_sz);
if (!is_server && max_frame_sz < ABS_MIN_FRAME_SIZE)
{
LSQ_LOG1(LSQ_LOG_ERROR, "max frame size must be at least %zd bytes, "
"which is the size of priority information that client always "
"writes", ABS_MIN_FRAME_SIZE);
return NULL;
}
fw = calloc(1, sizeof(*fw));
if (!fw)
return NULL;
fw->fw_mm = mm;
fw->fw_henc = henc;
fw->fw_stream = stream;
fw->fw_writef = writef;
fw->fw_max_frame_sz = max_frame_sz;
fw->fw_max_header_list_sz = 0;
if (is_server)
fw->fw_flags = FW_SERVER;
else
fw->fw_flags = 0;
#if LSQUIC_CONN_STATS
fw->fw_conn_stats = conn_stats;
#endif
lsquic_frab_list_init(&fw->fw_fral, 0x1000, fw_alloc,
(void (*)(void *, void *)) lsquic_mm_put_4k, mm);
return fw;
}
void
lsquic_frame_writer_destroy (struct lsquic_frame_writer *fw)
{
lsquic_frab_list_cleanup(&fw->fw_fral);
free(fw);
}
int
lsquic_frame_writer_have_leftovers (const struct lsquic_frame_writer *fw)
{
return !lsquic_frab_list_empty(&fw->fw_fral);
}
int
lsquic_frame_writer_flush (struct lsquic_frame_writer *fw)
{
struct lsquic_reader reader = {
.lsqr_read = lsquic_frab_list_read,
.lsqr_size = lsquic_frab_list_size,
.lsqr_ctx = &fw->fw_fral,
};
ssize_t nw;
nw = fw->fw_writef(fw->fw_stream, &reader);
if (nw >= 0)
return 0;
else
return -1;
}
struct header_framer_ctx
{
struct lsquic_frame_writer
*hfc_fw;
struct {
struct frame_buf *frab;
unsigned short off;
} hfc_header_ptr; /* Points to byte *after* current frame header */
unsigned hfc_max_frame_sz; /* Maximum frame size. We always fill it. */
unsigned hfc_cur_sz; /* Number of bytes in the current frame. */
unsigned hfc_n_frames; /* Number of frames written. */
lsquic_stream_id_t
hfc_stream_id; /* Stream ID */
enum http_frame_header_flags
hfc_first_flags;
enum http_frame_type
hfc_frame_type;
};
static void
hfc_init (struct header_framer_ctx *hfc, struct lsquic_frame_writer *fw,
unsigned max_frame_sz, enum http_frame_type frame_type,
lsquic_stream_id_t stream_id, enum http_frame_header_flags first_flags)
{
memset(hfc, 0, sizeof(*hfc));
hfc->hfc_fw = fw;
hfc->hfc_frame_type = frame_type;
hfc->hfc_stream_id = stream_id;
hfc->hfc_first_flags = first_flags;
hfc->hfc_max_frame_sz = max_frame_sz;
hfc->hfc_cur_sz = max_frame_sz;
}
static void
hfc_save_ptr (struct header_framer_ctx *hfc)
{
hfc->hfc_header_ptr.frab = TAILQ_LAST(&hfc->hfc_fw->fw_fral.fl_frabs, frame_buf_head);
hfc->hfc_header_ptr.off = hfc->hfc_header_ptr.frab->frab_size;
}
static void
hfc_terminate_frame (struct header_framer_ctx *hfc,
enum http_frame_header_flags flags)
{
union {
struct http_frame_header fh;
unsigned char buf[ sizeof(struct http_frame_header) ];
} u;
uint32_t stream_id;
struct frame_buf *frab;
/* Construct the frame */
u.fh.hfh_length[0] = hfc->hfc_cur_sz >> 16;
u.fh.hfh_length[1] = hfc->hfc_cur_sz >> 8;
u.fh.hfh_length[2] = hfc->hfc_cur_sz;
u.fh.hfh_flags = flags;
if (1 == hfc->hfc_n_frames)
{
u.fh.hfh_type = hfc->hfc_frame_type;
u.fh.hfh_flags |= hfc->hfc_first_flags;
}
else
u.fh.hfh_type = HTTP_FRAME_CONTINUATION;
stream_id = htonl(hfc->hfc_stream_id);
memcpy(u.fh.hfh_stream_id, &stream_id, sizeof(stream_id));
if (hfc->hfc_header_ptr.off >= sizeof(u.fh))
{ /* Write in a single chunk */
assert(0 == memcmp("123456789", hfc->hfc_header_ptr.frab->frab_buf +
hfc->hfc_header_ptr.off - sizeof(u.buf), sizeof(u.buf)));
memcpy(hfc->hfc_header_ptr.frab->frab_buf + hfc->hfc_header_ptr.off -
sizeof(u.buf), u.buf, sizeof(u.buf));
}
else
{ /* Write across frab boundary */
memcpy(hfc->hfc_header_ptr.frab->frab_buf,
u.buf + sizeof(u.buf) - hfc->hfc_header_ptr.off,
hfc->hfc_header_ptr.off);
frab = TAILQ_PREV(hfc->hfc_header_ptr.frab, frame_buf_head, frab_next);
memcpy(frab->frab_buf + frab->frab_size - sizeof(u.buf) +
hfc->hfc_header_ptr.off, u.buf,
sizeof(u.buf) - hfc->hfc_header_ptr.off);
}
}
static int
hfc_write (struct header_framer_ctx *hfc, const void *buf, size_t sz)
{
const unsigned char *p = buf;
unsigned avail;
int s;
while (sz > 0)
{
if (hfc->hfc_max_frame_sz == hfc->hfc_cur_sz)
{
if (hfc->hfc_n_frames > 0)
hfc_terminate_frame(hfc, 0);
s = lsquic_frab_list_write(&hfc->hfc_fw->fw_fral, "123456789",
sizeof(struct http_frame_header));
if (s < 0)
return s;
++hfc->hfc_n_frames;
hfc_save_ptr(hfc);
hfc->hfc_cur_sz = 0;
}
avail = hfc->hfc_max_frame_sz - hfc->hfc_cur_sz;
if (sz < avail)
avail = sz;
if (avail)
{
s = lsquic_frab_list_write(&hfc->hfc_fw->fw_fral, p, avail);
if (s < 0)
return s;
hfc->hfc_cur_sz += avail;
sz -= avail;
p += avail;
}
}
return 0;
}
static unsigned
count_uppercase (const unsigned char *buf, size_t sz)
{
static const unsigned char uppercase[0x100] = {
['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1, ['E'] = 1, ['F'] = 1,
['G'] = 1, ['H'] = 1, ['I'] = 1, ['J'] = 1, ['K'] = 1, ['L'] = 1,
['M'] = 1, ['N'] = 1, ['O'] = 1, ['P'] = 1, ['Q'] = 1, ['R'] = 1,
['S'] = 1, ['T'] = 1, ['U'] = 1, ['V'] = 1, ['W'] = 1, ['X'] = 1,
['Y'] = 1, ['Z'] = 1,
};
unsigned n_uppercase, i;
n_uppercase = 0;
for (i = 0; i < sz; ++i)
n_uppercase += uppercase[ buf[i] ];
return n_uppercase;
}
static uint32_t
calc_headers_size (const struct lsquic_http_headers *headers)
{
int i;
uint32_t size = 0;
for (i = 0; i < headers->count; ++i)
if (headers->headers[i].buf)
size += 32 + headers->headers[i].name_len +
headers->headers[i].val_len;
return size;
}
static int
have_oversize_strings (const struct lsquic_http_headers *headers)
{
#if LSXPACK_MAX_STRLEN > LSHPACK_MAX_STRLEN
int i, have;
for (i = 0, have = 0; i < headers->count; ++i)
{
if (headers->headers[i].buf)
{
have |= headers->headers[i].name_len > LSHPACK_MAX_STRLEN;
have |= headers->headers[i].val_len > LSHPACK_MAX_STRLEN;
}
}
return have;
#else
return 0;
#endif
}
static int
check_headers_size (const struct lsquic_frame_writer *fw,
const struct lsquic_http_headers *headers)
{
uint32_t headers_sz;
headers_sz = calc_headers_size(headers);
if (headers_sz <= fw->fw_max_header_list_sz)
return 0;
else if (fw->fw_flags & FW_SERVER)
{
LSQ_INFO("Sending headers larger (%u bytes) than max allowed (%u)",
headers_sz, fw->fw_max_header_list_sz);
return 0;
}
else
{
LSQ_INFO("Headers size %u is larger than max allowed (%u)",
headers_sz, fw->fw_max_header_list_sz);
errno = EMSGSIZE;
return -1;
}
}
static int
check_headers_case (const struct lsquic_frame_writer *fw,
const struct lsquic_http_headers *headers)
{
unsigned n_uppercase;
int i;
n_uppercase = 0;
for (i = 0; i < headers->count; ++i)
if (headers->headers[i].buf)
n_uppercase += count_uppercase((unsigned char *)
lsxpack_header_get_name(&headers->headers[i]),
headers->headers[i].name_len);
if (n_uppercase)
{
LSQ_INFO("Uppercase letters in header names");
errno = EINVAL;
return -1;
}
return 0;
}
static int
write_headers (struct lsquic_frame_writer *fw,
const struct lsquic_http_headers *headers,
struct header_framer_ctx *hfc, unsigned char *buf,
const unsigned buf_sz)
{
unsigned char *end;
int i, s;
for (i = 0; i < headers->count; ++i)
{
if (headers->headers[i].buf == NULL)
continue;
end = lshpack_enc_encode(fw->fw_henc, buf, buf + buf_sz,
&headers->headers[i]);
if (end > buf)
{
s = hfc_write(hfc, buf, end - buf);
if (s < 0)
return s;
#if LSQUIC_CONN_STATS
fw->fw_conn_stats->out.headers_uncomp +=
headers->headers[i].name_len
+ headers->headers[i].val_len;
fw->fw_conn_stats->out.headers_comp += end - buf;
#endif
}
else
{
/* Ignore errors, matching HTTP2 behavior in our server code */
}
}
return 0;
}
int
lsquic_frame_writer_write_headers (struct lsquic_frame_writer *fw,
lsquic_stream_id_t stream_id,
const struct lsquic_http_headers *headers,
int eos, unsigned weight)
{
struct header_framer_ctx hfc;
int s;
struct http_prio_frame prio_frame;
enum http_frame_header_flags flags;
unsigned char *buf;
/* Internal function: weight must be valid here */
assert(weight >= 1 && weight <= 256);
if (fw->fw_max_header_list_sz && 0 != check_headers_size(fw, headers))
return -1;
if (0 != check_headers_case(fw, headers))
return -1;
if (have_oversize_strings(headers))
return -1;
if (eos)
flags = HFHF_END_STREAM;
else
flags = 0;
if (!(fw->fw_flags & FW_SERVER))
flags |= HFHF_PRIORITY;
hfc_init(&hfc, fw, fw->fw_max_frame_sz, HTTP_FRAME_HEADERS, stream_id,
flags);
if (!(fw->fw_flags & FW_SERVER))
{
memset(&prio_frame.hpf_stream_id, 0, sizeof(prio_frame.hpf_stream_id));
prio_frame.hpf_weight = weight - 1;
s = hfc_write(&hfc, &prio_frame, sizeof(struct http_prio_frame));
if (s < 0)
return s;
}
buf = malloc(MAX_COMP_HEADER_FIELD_SIZE);
if (!buf)
return -1;
s = write_headers(fw, headers, &hfc, buf, MAX_COMP_HEADER_FIELD_SIZE);
free(buf);
if (0 == s)
{
EV_LOG_GENERATED_HTTP_HEADERS(LSQUIC_LOG_CONN_ID, stream_id,
fw->fw_flags & FW_SERVER, &prio_frame, headers);
hfc_terminate_frame(&hfc, HFHF_END_HEADERS);
return lsquic_frame_writer_flush(fw);
}
else
return s;
}
int
lsquic_frame_writer_write_promise (struct lsquic_frame_writer *fw,
lsquic_stream_id_t stream_id64, lsquic_stream_id_t promised_stream_id64,
const struct lsquic_http_headers *headers)
{
uint32_t stream_id = stream_id64;
uint32_t promised_stream_id = promised_stream_id64;
struct header_framer_ctx hfc;
struct http_push_promise_frame push_frame;
unsigned char *buf;
int s;
fiu_return_on("frame_writer/writer_promise", -1);
if (fw->fw_max_header_list_sz && 0 != check_headers_size(fw, headers))
return -1;
if (0 != check_headers_case(fw, headers))
return -1;
if (have_oversize_strings(headers))
return -1;
hfc_init(&hfc, fw, fw->fw_max_frame_sz, HTTP_FRAME_PUSH_PROMISE,
stream_id, 0);
promised_stream_id = htonl(promised_stream_id);
memcpy(push_frame.hppf_promised_id, &promised_stream_id, 4);
s = hfc_write(&hfc, &push_frame, sizeof(struct http_push_promise_frame));
if (s < 0)
return s;
buf = malloc(MAX_COMP_HEADER_FIELD_SIZE);
if (!buf)
return -1;
s = write_headers(fw, headers, &hfc, buf, MAX_COMP_HEADER_FIELD_SIZE);
if (s != 0)
{
free(buf);
return -1;
}
free(buf);
EV_LOG_GENERATED_HTTP_PUSH_PROMISE(LSQUIC_LOG_CONN_ID, stream_id,
htonl(promised_stream_id), headers);
hfc_terminate_frame(&hfc, HFHF_END_HEADERS);
return lsquic_frame_writer_flush(fw);
}
void
lsquic_frame_writer_max_header_list_size (struct lsquic_frame_writer *fw,
uint32_t max_size)
{
LSQ_DEBUG("set max_header_list_sz to %u", max_size);
fw->fw_max_header_list_sz = max_size;
}
static int
write_settings (struct lsquic_frame_writer *fw,
const struct lsquic_http2_setting *settings, unsigned n_settings)
{
struct http_frame_header fh;
unsigned payload_length;
uint32_t val;
uint16_t id;
int s;
payload_length = n_settings * 6;
memset(&fh, 0, sizeof(fh));
fh.hfh_type = HTTP_FRAME_SETTINGS;
fh.hfh_length[0] = payload_length >> 16;
fh.hfh_length[1] = payload_length >> 8;
fh.hfh_length[2] = payload_length;
s = lsquic_frab_list_write(&fw->fw_fral, &fh, sizeof(fh));
if (s != 0)
return s;
do
{
id = htons(settings->id);
val = htonl(settings->value);
if (0 != (s = lsquic_frab_list_write(&fw->fw_fral, &id, sizeof(id))) ||
0 != (s = lsquic_frab_list_write(&fw->fw_fral, &val, sizeof(val))))
return s;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "wrote HTTP SETTINGS frame: "
"%s=%"PRIu32, lsquic_http_setting_id2str(settings->id),
settings->value);
++settings;
}
while (--n_settings);
return 0;
}
int
lsquic_frame_writer_write_settings (struct lsquic_frame_writer *fw,
const struct lsquic_http2_setting *settings, unsigned n_settings)
{
unsigned settings_per_frame;
unsigned n;
if (0 == n_settings)
{
errno = EINVAL;
return -1;
}
settings_per_frame = fw->fw_max_frame_sz / SETTINGS_FRAME_SZ;
n = 0;
do {
if (settings_per_frame > n_settings - n)
settings_per_frame = n_settings - n;
if (0 != write_settings(fw, &settings[n], settings_per_frame))
return -1;
n += settings_per_frame;
} while (n < n_settings);
return lsquic_frame_writer_flush(fw);
}
int
lsquic_frame_writer_write_priority (struct lsquic_frame_writer *fw,
lsquic_stream_id_t stream_id64, int exclusive,
lsquic_stream_id_t stream_dep_id64, unsigned weight)
{
uint32_t stream_id = stream_id64;
uint32_t stream_dep_id = stream_dep_id64;
unsigned char buf[ sizeof(struct http_frame_header) +
sizeof(struct http_prio_frame) ];
struct http_frame_header *fh = (void *) &buf[0];
struct http_prio_frame *prio_frame = (void *) &buf[sizeof(*fh)];
int s;
if (stream_dep_id & (1UL << 31))
{
LSQ_WARN("stream ID too high (%u): cannot write PRIORITY frame",
stream_dep_id);
return -1;
}
if (weight < 1 || weight > 256)
return -1;
memset(fh, 0, sizeof(*fh));
fh->hfh_type = HTTP_FRAME_PRIORITY;
fh->hfh_length[2] = sizeof(struct http_prio_frame);
stream_id = htonl(stream_id);
memcpy(fh->hfh_stream_id, &stream_id, 4);
stream_dep_id |= !!exclusive << 31;
stream_id = htonl(stream_dep_id);
memcpy(prio_frame->hpf_stream_id, &stream_id, 4);
prio_frame->hpf_weight = weight - 1;
s = lsquic_frab_list_write(&fw->fw_fral, buf, sizeof(buf));
if (s != 0)
return s;
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "wrote HTTP PRIORITY frame: "
"stream %"PRIu32"; weight: %u; exclusive: %d",
htonl(stream_id), weight, !!exclusive);
return lsquic_frame_writer_flush(fw);
}
size_t
lsquic_frame_writer_mem_used (const struct lsquic_frame_writer *fw)
{
size_t size;
size = sizeof(*fw)
+ lsquic_frab_list_mem_used(&fw->fw_fral)
- sizeof(fw->fw_fral);
return size;
}