675 lines
19 KiB
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;
|
|
}
|