Packages
hackney
3.0.2
4.7.2
4.7.1
4.7.0
4.6.1
4.6.0
4.5.2
4.5.1
4.5.0
4.4.5
4.4.3
4.4.2
4.4.1
4.4.0
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.3
4.0.2
4.0.1
4.0.0
3.2.1
3.2.0
3.1.2
3.1.1
3.1.0
3.0.3
3.0.2
3.0.1
3.0.0
retired
2.0.1
2.0.0
2.0.0-beta.1
1.25.0
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.2
1.18.1
1.18.0
1.17.4
1.17.3
1.17.2
1.17.1
1.17.0
1.16.0
1.15.2
1.15.1
1.15.0
1.14.3
1.14.2
1.14.0
1.13.0
1.12.1
1.12.0
1.11.0
1.10.1
1.10.0
1.9.0
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.0
1.7.1
1.7.0
1.6.6
retired
1.6.5
1.6.4
retired
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.10
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.6
1.0.5
1.0.2
1.0.1
0.15.2
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
Simple HTTP client with HTTP/1.1, HTTP/2, and HTTP/3 support
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
c_src/lsquic/src/liblsquic/lsquic_frame_writer.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;
}