You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1057 lines
29 KiB

/*****************************************************************************
* h2frame.c: HTTP/2 frame formatting
*****************************************************************************
* Copyright (C) 2015 Rémi Denis-Courmont
*
* This program 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; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <vlc_common.h>
#include "conn.h"
#include "hpack.h"
#include "h2frame.h"
static struct vlc_h2_frame *
vlc_h2_frame_alloc(uint_fast8_t type, uint_fast8_t flags,
uint_fast32_t stream_id, size_t length)
{
assert((stream_id >> 31) == 0);
if (unlikely(length > VLC_H2_MAX_MAX_FRAME))
{
errno = EINVAL;
return NULL;
}
struct vlc_h2_frame *f = malloc(sizeof (*f) + 9 + length);
if (unlikely(f == NULL))
return NULL;
f->next = NULL;
f->data[0] = length >> 16;
f->data[1] = length >> 8;
f->data[2] = length;
f->data[3] = type;
f->data[4] = flags;
SetDWBE(f->data + 5, stream_id);
return f;
}
#define vlc_h2_frame_payload(f) ((f)->data + 9)
static uint_fast32_t vlc_h2_frame_length(const struct vlc_h2_frame *f)
{
const uint8_t *buf = f->data;
return (buf[0] << 16) | (buf[1] << 8) | buf[2];
}
size_t vlc_h2_frame_size(const struct vlc_h2_frame *f)
{
return 9 + vlc_h2_frame_length(f);
}
static uint_fast8_t vlc_h2_frame_type(const struct vlc_h2_frame *f)
{
return f->data[3];
}
static uint_fast8_t vlc_h2_frame_flags(const struct vlc_h2_frame *f)
{
return f->data[4];
}
static uint_fast32_t vlc_h2_frame_id(const struct vlc_h2_frame *f)
{
return GetDWBE(f->data + 5) & 0x7FFFFFFF;
}
enum {
VLC_H2_FRAME_DATA,
VLC_H2_FRAME_HEADERS,
VLC_H2_FRAME_PRIORITY,
VLC_H2_FRAME_RST_STREAM,
VLC_H2_FRAME_SETTINGS,
VLC_H2_FRAME_PUSH_PROMISE,
VLC_H2_FRAME_PING,
VLC_H2_FRAME_GOAWAY,
VLC_H2_FRAME_WINDOW_UPDATE,
VLC_H2_FRAME_CONTINUATION,
};
static const char *vlc_h2_type_name(uint_fast8_t type)
{
static const char names[][14] = {
[VLC_H2_FRAME_DATA] = "DATA",
[VLC_H2_FRAME_HEADERS] = "HEADERS",
[VLC_H2_FRAME_PRIORITY] = "PRIORITY",
[VLC_H2_FRAME_RST_STREAM] = "RST_STREAM",
[VLC_H2_FRAME_SETTINGS] = "SETTINGS",
[VLC_H2_FRAME_PUSH_PROMISE] = "PUSH_PROMISE",
[VLC_H2_FRAME_PING] = "PING",
[VLC_H2_FRAME_GOAWAY] = "GOAWAY",
[VLC_H2_FRAME_WINDOW_UPDATE] = "WINDOW_UPDATE",
[VLC_H2_FRAME_CONTINUATION] = "CONTINUATION",
};
if (type >= ARRAY_SIZE(names) || names[type][0] == '\0')
return "<unknown>";
return names[type];
}
enum {
VLC_H2_DATA_END_STREAM = 0x01,
VLC_H2_DATA_PADDED = 0x08,
};
enum {
VLC_H2_HEADERS_END_STREAM = 0x01,
VLC_H2_HEADERS_END_HEADERS = 0x04,
VLC_H2_HEADERS_PADDED = 0x08,
VLC_H2_HEADERS_PRIORITY = 0x20,
};
enum {
VLC_H2_SETTINGS_ACK = 0x01,
};
enum {
VLC_H2_PUSH_PROMISE_END_HEADERS = 0x04,
VLC_H2_PUSH_PROMISE_PADDED = 0x08,
};
enum {
VLC_H2_PING_ACK = 0x01,
};
enum {
VLC_H2_CONTINUATION_END_HEADERS = 0x04,
};
struct vlc_h2_frame *
vlc_h2_frame_headers(uint_fast32_t stream_id, uint_fast32_t mtu, bool eos,
unsigned count, const char *const headers[][2])
{
struct vlc_h2_frame *f;
uint8_t flags = eos ? VLC_H2_HEADERS_END_STREAM : 0;
size_t len = hpack_encode(NULL, 0, headers, count);
if (likely(len <= mtu))
{ /* Most common case: single frame - with zero copy */
flags |= VLC_H2_HEADERS_END_HEADERS;
f = vlc_h2_frame_alloc(VLC_H2_FRAME_HEADERS, flags, stream_id, len);
if (unlikely(f == NULL))
return NULL;
hpack_encode(vlc_h2_frame_payload(f), len, headers, count);
return f;
}
/* Edge case: HEADERS frame then CONTINUATION frame(s) */
uint8_t *payload = malloc(len);
if (unlikely(payload == NULL))
return NULL;
hpack_encode(payload, len, headers, count);
struct vlc_h2_frame **pp = &f, *n;
const uint8_t *offset = payload;
uint_fast8_t type = VLC_H2_FRAME_HEADERS;
f = NULL;
while (len > mtu)
{
n = vlc_h2_frame_alloc(type, flags, stream_id, mtu);
if (unlikely(n == NULL))
goto error;
memcpy(vlc_h2_frame_payload(n), offset, mtu);
*pp = n;
pp = &n->next;
type = VLC_H2_FRAME_CONTINUATION;
flags = 0;
offset += mtu;
len -= mtu;
}
flags |= VLC_H2_CONTINUATION_END_HEADERS;
n = vlc_h2_frame_alloc(type, flags, stream_id, len);
if (unlikely(n == NULL))
goto error;
memcpy(vlc_h2_frame_payload(n), offset, len);
*pp = n;
free(payload);
return f;
error:
while (f != NULL)
{
n = f->next;
free(f);
f = n;
}
free(payload);
return NULL;
}
struct vlc_h2_frame *
vlc_h2_frame_data(uint_fast32_t stream_id, const void *buf, size_t len,
bool eos)
{
struct vlc_h2_frame *f;
uint8_t flags = eos ? VLC_H2_DATA_END_STREAM : 0;
f = vlc_h2_frame_alloc(VLC_H2_FRAME_DATA, flags, stream_id, len);
if (len > 0 && likely(f != NULL))
memcpy(vlc_h2_frame_payload(f), buf, len);
return f;
}
struct vlc_h2_frame *
vlc_h2_frame_rst_stream(uint_fast32_t stream_id, uint_fast32_t error_code)
{
struct vlc_h2_frame *f = vlc_h2_frame_alloc(VLC_H2_FRAME_RST_STREAM, 0,
stream_id, 4);
if (likely(f != NULL))
SetDWBE(vlc_h2_frame_payload(f), error_code);
return f;
}
struct vlc_h2_frame *vlc_h2_frame_settings(void)
{
unsigned n = (VLC_H2_MAX_HEADER_TABLE != VLC_H2_DEFAULT_MAX_HEADER_TABLE)
+ 1 /* ENABLE_PUSH */
#if defined(VLC_H2_MAX_STREAMS)
+ 1
#endif
+ (VLC_H2_INIT_WINDOW != VLC_H2_DEFAULT_INIT_WINDOW)
+ (VLC_H2_MAX_FRAME != VLC_H2_DEFAULT_MAX_FRAME)
#if defined(VLC_H2_MAX_HEADER_LIST)
+ 1
#endif
;
struct vlc_h2_frame *f = vlc_h2_frame_alloc(VLC_H2_FRAME_SETTINGS, 0, 0,
n * 6);
if (unlikely(f == NULL))
return NULL;
uint8_t *p = vlc_h2_frame_payload(f);
#if (VLC_H2_MAX_HEADER_TABLE != VLC_H2_DEFAULT_MAX_HEADER_TABLE)
SetWBE(p, VLC_H2_SETTING_HEADER_TABLE_SIZE);
SetDWBE(p + 2, VLC_H2_MAX_HEADER_TABLE);
p += 6;
#endif
SetWBE(p, VLC_H2_SETTING_ENABLE_PUSH);
SetDWBE(p + 2, 0);
p += 6;
#if defined(VLC_H2_MAX_STREAMS)
SetWBE(p, VLC_H2_SETTING_MAX_CONCURRENT_STREAMS);
SetDWBE(p + 2, VLC_H2_MAX_STREAMS);
p += 6;
#endif
#if (VLC_H2_INIT_WINDOW != VLC_H2_DEFAULT_INIT_WINDOW)
static_assert (VLC_H2_INIT_WINDOW < 2147483648,
"Illegal initial window value");
SetWBE(p, VLC_H2_SETTING_INITIAL_WINDOW_SIZE);
SetDWBE(p + 2, VLC_H2_INIT_WINDOW);
p += 6;
#endif
#if (VLC_H2_MAX_FRAME != VLC_H2_DEFAULT_MAX_FRAME)
static_assert (VLC_H2_MAX_FRAME >= 16384 && VLC_H2_MAX_FRAME < 16777216,
"Illegal maximum frame size");
SetWBE(p, VLC_H2_SETTING_MAX_FRAME_SIZE);
SetDWBE(p + 2, VLC_H2_MAX_FRAME);
p += 6;
#endif
#if defined(VLC_H2_MAX_HEADER_LIST)
SetWBE(p, VLC_H2_SETTING_MAX_HEADER_LIST_SIZE);
SetDWBE(p + 2, VLC_H2_MAX_HEADER_LIST);
p += 6;
#endif
return f;
}
struct vlc_h2_frame *vlc_h2_frame_settings_ack(void)
{
return vlc_h2_frame_alloc(VLC_H2_FRAME_SETTINGS, VLC_H2_SETTINGS_ACK, 0,
0);
}
const char *vlc_h2_setting_name(uint_fast16_t id)
{
static const char names[][20] = {
[0] = "Unknown setting",
[VLC_H2_SETTING_HEADER_TABLE_SIZE] = "Header table size",
[VLC_H2_SETTING_ENABLE_PUSH] = "Enable push",
[VLC_H2_SETTING_MAX_CONCURRENT_STREAMS] = "Concurrent streams",
[VLC_H2_SETTING_INITIAL_WINDOW_SIZE] = "Initial window size",
[VLC_H2_SETTING_MAX_FRAME_SIZE] = "Frame size",
[VLC_H2_SETTING_MAX_HEADER_LIST_SIZE] = "Header list size",
};
if (id >= ARRAY_SIZE(names) || names[id][0] == '\0')
id = 0;
return names[id];
}
struct vlc_h2_frame *vlc_h2_frame_ping(uint64_t opaque)
{
struct vlc_h2_frame *f = vlc_h2_frame_alloc(VLC_H2_FRAME_PING, 0, 0, 8);
if (likely(f != NULL))
memcpy(vlc_h2_frame_payload(f), &opaque, 8);
return f;
}
struct vlc_h2_frame *vlc_h2_frame_pong(uint64_t opaque)
{
struct vlc_h2_frame *f = vlc_h2_frame_alloc(VLC_H2_FRAME_PING,
VLC_H2_PING_ACK, 0, 8);
if (likely(f != NULL))
memcpy(vlc_h2_frame_payload(f), &opaque, 8);
return f;
}
struct vlc_h2_frame *
vlc_h2_frame_goaway(uint_fast32_t last_stream_id, uint_fast32_t error_code)
{
struct vlc_h2_frame *f = vlc_h2_frame_alloc(VLC_H2_FRAME_GOAWAY, 0, 0, 8);
if (likely(f != NULL))
{
uint8_t *p = vlc_h2_frame_payload(f);
SetDWBE(p, last_stream_id);
SetDWBE(p + 4, error_code);
}
return f;
}
struct vlc_h2_frame *
vlc_h2_frame_window_update(uint_fast32_t stream_id, uint_fast32_t credit)
{
assert((stream_id >> 31) == 0);
struct vlc_h2_frame *f = vlc_h2_frame_alloc(VLC_H2_FRAME_WINDOW_UPDATE,
0, stream_id, 4);
if (likely(f != NULL))
{
uint8_t *p = vlc_h2_frame_payload(f);
SetDWBE(p, credit);
}
return f;
}
const char *vlc_h2_strerror(uint_fast32_t code)
{
static const char names[][20] = {
[VLC_H2_NO_ERROR] = "No error",
[VLC_H2_PROTOCOL_ERROR] = "Protocol error",
[VLC_H2_INTERNAL_ERROR] = "Internal error",
[VLC_H2_FLOW_CONTROL_ERROR] = "Flow control error",
[VLC_H2_SETTINGS_TIMEOUT] = "Settings time-out",
[VLC_H2_STREAM_CLOSED] = "Stream closed",
[VLC_H2_FRAME_SIZE_ERROR] = "Frame size error",
[VLC_H2_REFUSED_STREAM] = "Refused stream",
[VLC_H2_CANCEL] = "Cancellation",
[VLC_H2_COMPRESSION_ERROR] = "Compression error",
[VLC_H2_CONNECT_ERROR] = "CONNECT error",
[VLC_H2_ENHANCE_YOUR_CALM] = "Excessive load",
[VLC_H2_INADEQUATE_SECURITY] = "Inadequate security",
[VLC_H2_HTTP_1_1_REQUIRED] = "Required HTTP/1.1",
};
if (code >= ARRAY_SIZE(names) || names[code][0] == '\0')
return "Unknown error";
return names[code];
}
void vlc_h2_frame_dump(void *opaque, const struct vlc_h2_frame *f,
const char *msg)
{
size_t len = vlc_h2_frame_length(f);
uint_fast8_t type = vlc_h2_frame_type(f);
uint_fast8_t flags = vlc_h2_frame_flags(f);
uint_fast32_t sid = vlc_h2_frame_id(f);
if (sid != 0)
vlc_http_dbg(opaque, "%s %s (0x%02"PRIxFAST8") frame of %zu bytes, "
"flags 0x%02"PRIxFAST8", stream %"PRIuFAST32, msg,
vlc_h2_type_name(type), type, len, flags, sid);
else
vlc_http_dbg(opaque, "%s %s (0x%02"PRIxFAST8") frame of %zu bytes, "
"flags 0x%02"PRIxFAST8", global", msg,
vlc_h2_type_name(type), type, len, flags);
}
const uint8_t *(vlc_h2_frame_data_get)(const struct vlc_h2_frame *f,
size_t *restrict lenp)
{
assert(vlc_h2_frame_type(f) == VLC_H2_FRAME_DATA);
size_t len = vlc_h2_frame_length(f);
uint_fast8_t flags = vlc_h2_frame_flags(f);
const uint8_t *ptr = vlc_h2_frame_payload(f);
/* At this point, the frame has already been validated by the parser. */
if (flags & VLC_H2_DATA_PADDED)
{
assert(len >= 1u && len >= 1u + ptr[0]);
len -= 1u + *(ptr++);
}
*lenp = len;
return ptr;
}
typedef int (*vlc_h2_parser)(struct vlc_h2_parser *, struct vlc_h2_frame *,
size_t, uint_fast32_t);
/** HTTP/2 incoming frames parser */
struct vlc_h2_parser
{
void *opaque;
const struct vlc_h2_parser_cbs *cbs;
vlc_h2_parser parser; /*< Parser state / callback for next frame */
struct
{
uint32_t sid; /*< Ongoing stream identifier */
bool eos; /*< End of stream after headers block */
size_t len; /*< Compressed headers buffer length */
uint8_t *buf; /*< Compressed headers buffer base address */
struct hpack_decoder *decoder; /*< HPACK decompressor state */
} headers; /*< Compressed headers reception state */
uint32_t rcwd_size; /*< Receive congestion window (bytes) */
};
static int vlc_h2_parse_generic(struct vlc_h2_parser *, struct vlc_h2_frame *,
size_t, uint_fast32_t);
static int vlc_h2_parse_headers_block(struct vlc_h2_parser *,
struct vlc_h2_frame *, size_t,
uint_fast32_t);
static int vlc_h2_parse_error(struct vlc_h2_parser *p, uint_fast32_t code)
{
p->cbs->error(p->opaque, code);
return -1;
}
static int vlc_h2_stream_error(struct vlc_h2_parser *p, uint_fast32_t id,
uint_fast32_t code)
{
return p->cbs->stream_error(p->opaque, id, code);
}
static void *vlc_h2_stream_lookup(struct vlc_h2_parser *p, uint_fast32_t id)
{
return p->cbs->stream_lookup(p->opaque, id);
}
static void vlc_h2_parse_headers_start(struct vlc_h2_parser *p,
uint_fast32_t sid, bool eos)
{
assert(sid != 0);
assert(p->headers.sid == 0);
p->parser = vlc_h2_parse_headers_block;
p->headers.sid = sid;
p->headers.eos = eos;
p->headers.len = 0;
}
static int vlc_h2_parse_headers_append(struct vlc_h2_parser *p,
const uint8_t *data, size_t len)
{
assert(p->headers.sid != 0);
if (p->headers.len + len > 65536)
return vlc_h2_parse_error(p, VLC_H2_INTERNAL_ERROR);
uint8_t *buf = realloc(p->headers.buf, p->headers.len + len);
if (unlikely(buf == NULL))
return vlc_h2_parse_error(p, VLC_H2_INTERNAL_ERROR);
p->headers.buf = buf;
memcpy(p->headers.buf + p->headers.len, data, len);
p->headers.len += len;
return 0;
}
static int vlc_h2_parse_headers_end(struct vlc_h2_parser *p)
{
char *headers[VLC_H2_MAX_HEADERS][2];
/* TODO: limit total decompressed size of the headers list */
int n = hpack_decode(p->headers.decoder, p->headers.buf, p->headers.len,
headers, VLC_H2_MAX_HEADERS);
if (n > VLC_H2_MAX_HEADERS)
{
for (unsigned i = 0; i < VLC_H2_MAX_HEADERS; i++)
{
free(headers[i][0]);
free(headers[i][1]);
}
n = -1;
}
if (n < 0)
return vlc_h2_parse_error(p, VLC_H2_COMPRESSION_ERROR);
void *s = vlc_h2_stream_lookup(p, p->headers.sid);
int val = 0;
if (s != NULL)
{
const char *ch[VLC_H2_MAX_HEADERS][2];
for (int i = 0; i < n; i++)
ch[i][0] = headers[i][0], ch[i][1] = headers[i][1];
p->cbs->stream_headers(s, n, ch);
if (p->headers.eos)
p->cbs->stream_end(s);
}
else
/* NOTE: The specification implies that the error should be sent for
* the first header frame. But we actually want to receive the whole
* fragmented headers block, to preserve the HPACK decoder state.
* So we send the error at the last header frame instead. */
val = vlc_h2_stream_error(p, p->headers.sid, VLC_H2_REFUSED_STREAM);
for (int i = 0; i < n; i++)
{
free(headers[i][0]);
free(headers[i][1]);
}
p->parser = vlc_h2_parse_generic;
p->headers.sid = 0;
return val;
}
/** Parses an HTTP/2 DATA frame */
static int vlc_h2_parse_frame_data(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
uint_fast8_t flags = vlc_h2_frame_flags(f);
const uint8_t *ptr = vlc_h2_frame_payload(f);
if (id == 0)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
}
if (len > VLC_H2_MAX_FRAME)
{
free(f);
return vlc_h2_stream_error(p, id, VLC_H2_FRAME_SIZE_ERROR);
}
if (flags & VLC_H2_DATA_PADDED)
{
if (len < 1 || len < (1u + ptr[0]))
{
free(f);
return vlc_h2_stream_error(p, id, VLC_H2_FRAME_SIZE_ERROR);
}
len -= 1 + ptr[0];
}
if (len > p->rcwd_size)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_FLOW_CONTROL_ERROR);
}
p->rcwd_size -= len;
p->cbs->window_status(p->opaque, &p->rcwd_size);
void *s = vlc_h2_stream_lookup(p, id);
if (s == NULL)
{
free(f);
return vlc_h2_stream_error(p, id, VLC_H2_STREAM_CLOSED);
}
int ret = p->cbs->stream_data(s, f);
/* Frame gets consumed here ^^ */
if (flags & VLC_H2_DATA_END_STREAM)
p->cbs->stream_end(s);
return ret;
}
/** Parses an HTTP/2 HEADERS frame */
static int vlc_h2_parse_frame_headers(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
uint_fast8_t flags = vlc_h2_frame_flags(f);
const uint8_t *ptr = vlc_h2_frame_payload(f);
if (id == 0)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
}
if (len > VLC_H2_MAX_FRAME)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
}
if (flags & VLC_H2_HEADERS_PADDED)
{
if (len < 1 || len < (1u + ptr[0]))
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
}
len -= 1 + ptr[0];
ptr++;
}
if (flags & VLC_H2_HEADERS_PRIORITY)
{ /* Ignore priorities for now as we do not upload anything. */
if (len < 5)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
}
ptr += 5;
len -= 5;
}
vlc_h2_parse_headers_start(p, id, flags & VLC_H2_HEADERS_END_STREAM);
int ret = vlc_h2_parse_headers_append(p, ptr, len);
if (ret == 0 && (flags & VLC_H2_HEADERS_END_HEADERS))
ret = vlc_h2_parse_headers_end(p);
free(f);
return ret;
}
/** Parses an HTTP/2 PRIORITY frame */
static int vlc_h2_parse_frame_priority(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
free(f);
if (id == 0)
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
if (len != 5)
return vlc_h2_stream_error(p, id, VLC_H2_FRAME_SIZE_ERROR);
/* Ignore priorities for now as we do not upload much. */
return 0;
}
/** Parses an HTTP/2 RST_STREAM frame */
static int vlc_h2_parse_frame_rst_stream(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
if (id == 0)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
}
if (len != 4)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
}
void *s = vlc_h2_stream_lookup(p, id);
uint_fast32_t code = GetDWBE(vlc_h2_frame_payload(f));
free(f);
if (s == NULL)
return 0;
return p->cbs->stream_reset(s, code);
}
/** Parses an HTTP/2 SETTINGS frame */
static int vlc_h2_parse_frame_settings(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
const uint8_t *ptr = vlc_h2_frame_payload(f);
if (id != 0)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
}
if (len % 6 || len > VLC_H2_MAX_FRAME)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
}
if (vlc_h2_frame_flags(f) & VLC_H2_SETTINGS_ACK)
{
free(f);
if (len != 0)
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
/* Ignore ACKs for now as we never change settings. */
return 0;
}
for (const uint8_t *end = ptr + len; ptr < end; ptr += 6)
p->cbs->setting(p->opaque, GetWBE(ptr), GetDWBE(ptr + 2));
free(f);
return p->cbs->settings_done(p->opaque);
}
/** Parses an HTTP/2 PUSH_PROMISE frame */
static int vlc_h2_parse_frame_push_promise(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
uint8_t flags = vlc_h2_frame_flags(f);
const uint8_t *ptr = vlc_h2_frame_payload(f);
if (id == 0)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
}
if (len > VLC_H2_MAX_FRAME)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
}
if (flags & VLC_H2_PUSH_PROMISE_PADDED)
{
if (len < 1 || len < (1u + ptr[0]))
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
}
len -= 1 + ptr[0];
ptr++;
}
/* Not permitted by our settings. */
free(f);
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
}
/** Parses an HTTP/2 PING frame */
static int vlc_h2_parse_frame_ping(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
uint64_t opaque;
if (id != 0)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
}
if (len != 8)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
}
if (vlc_h2_frame_flags(f) & VLC_H2_PING_ACK)
{
free(f);
return 0;
}
memcpy(&opaque, vlc_h2_frame_payload(f), 8);
free(f);
return p->cbs->ping(p->opaque, opaque);
}
/** Parses an HTTP/2 GOAWAY frame */
static int vlc_h2_parse_frame_goaway(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
const uint8_t *ptr = vlc_h2_frame_payload(f);
if (id != 0)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
}
if (len < 8 || len > VLC_H2_MAX_FRAME)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
}
uint_fast32_t last_id = GetDWBE(ptr) & 0x7FFFFFFF;
uint_fast32_t code = GetDWBE(ptr + 4);
free(f);
return p->cbs->reset(p->opaque, last_id, code);
}
/** Parses an HTTP/2 WINDOW_UPDATE frame */
static int vlc_h2_parse_frame_window_update(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
if (len != 4)
{
free(f);
if (id == 0)
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
return vlc_h2_stream_error(p, id, VLC_H2_FRAME_SIZE_ERROR);
}
uint_fast32_t credit = GetDWBE(vlc_h2_frame_payload(f)) & 0x7fffffffu;
free(f);
if (credit == 0)
{
if (id == 0)
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
return vlc_h2_stream_error(p, id, VLC_H2_PROTOCOL_ERROR);
}
if (id == 0)
p->cbs->window_update(p->opaque, credit);
else
{
void *s = vlc_h2_stream_lookup(p, id);
if (s != NULL)
p->cbs->stream_window_update(s, credit);
}
return 0;
}
/** Parses an HTTP/2 CONTINUATION frame */
static int vlc_h2_parse_frame_continuation(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
const uint8_t *ptr = vlc_h2_frame_payload(f);
/* Stream ID must match with the previous frame. */
if (id == 0 || id != p->headers.sid)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
}
if (len > VLC_H2_MAX_FRAME)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
}
int ret = vlc_h2_parse_headers_append(p, ptr, len);
if (ret == 0 && (vlc_h2_frame_flags(f) & VLC_H2_CONTINUATION_END_HEADERS))
ret = vlc_h2_parse_headers_end(p);
free(f);
return 0;
}
/** Parses an HTTP/2 frame of unknown type */
static int vlc_h2_parse_frame_unknown(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
free(f);
if (len > VLC_H2_MAX_FRAME)
{
if (id == 0)
return vlc_h2_parse_error(p, VLC_H2_FRAME_SIZE_ERROR);
return vlc_h2_stream_error(p, id, VLC_H2_FRAME_SIZE_ERROR);
}
/* Ignore frames of unknown type as specified. */
return 0;
}
static const vlc_h2_parser vlc_h2_parsers[] = {
[VLC_H2_FRAME_DATA] = vlc_h2_parse_frame_data,
[VLC_H2_FRAME_HEADERS] = vlc_h2_parse_frame_headers,
[VLC_H2_FRAME_PRIORITY] = vlc_h2_parse_frame_priority,
[VLC_H2_FRAME_RST_STREAM] = vlc_h2_parse_frame_rst_stream,
[VLC_H2_FRAME_SETTINGS] = vlc_h2_parse_frame_settings,
[VLC_H2_FRAME_PUSH_PROMISE] = vlc_h2_parse_frame_push_promise,
[VLC_H2_FRAME_PING] = vlc_h2_parse_frame_ping,
[VLC_H2_FRAME_GOAWAY] = vlc_h2_parse_frame_goaway,
[VLC_H2_FRAME_WINDOW_UPDATE] = vlc_h2_parse_frame_window_update,
[VLC_H2_FRAME_CONTINUATION] = vlc_h2_parse_frame_continuation,
};
/** Parses the HTTP/2 connection preface. */
static int vlc_h2_parse_preface(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
/* The length must be within the specification default limits. */
if (len > VLC_H2_DEFAULT_MAX_FRAME
/* The type must SETTINGS. */
|| vlc_h2_frame_type(f) != VLC_H2_FRAME_SETTINGS
/* The SETTINGS ACK flag must be clear. */
|| (vlc_h2_frame_flags(f) & VLC_H2_SETTINGS_ACK))
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
}
p->parser = vlc_h2_parse_generic;
return vlc_h2_parse_frame_settings(p, f, len, id);
}
/** Parses any HTTP/2 frame. */
static int vlc_h2_parse_generic(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
uint_fast8_t type = vlc_h2_frame_type(f);
vlc_h2_parser func = vlc_h2_parse_frame_unknown;
assert(p->headers.sid == 0);
if (type < ARRAY_SIZE(vlc_h2_parsers) && vlc_h2_parsers[type] != NULL)
func = vlc_h2_parsers[type];
return func(p, f, len, id);
}
static int vlc_h2_parse_headers_block(struct vlc_h2_parser *p,
struct vlc_h2_frame *f, size_t len,
uint_fast32_t id)
{
assert(p->headers.sid != 0);
/* After a HEADER, PUSH_PROMISE of CONTINUATION frame without the
* END_HEADERS flag, must come a CONTINUATION frame. */
if (vlc_h2_frame_type(f) != VLC_H2_FRAME_CONTINUATION)
{
free(f);
return vlc_h2_parse_error(p, VLC_H2_PROTOCOL_ERROR);
}
return vlc_h2_parse_frame_continuation(p, f, len, id);
}
static int vlc_h2_parse_failed(struct vlc_h2_parser *p, struct vlc_h2_frame *f,
size_t len, uint_fast32_t id)
{
free(f);
(void) p; (void) len; (void) id;
return -1;
}
int vlc_h2_parse(struct vlc_h2_parser *p, struct vlc_h2_frame *f)
{
int ret = 0;
while (f != NULL)
{
struct vlc_h2_frame *next = f->next;
size_t len = vlc_h2_frame_length(f);
uint_fast32_t id = vlc_h2_frame_id(f);
f->next = NULL;
ret = p->parser(p, f, len, id);
if (ret)
p->parser = vlc_h2_parse_failed;
f = next;
}
return ret;
}
struct vlc_h2_parser *vlc_h2_parse_init(void *ctx,
const struct vlc_h2_parser_cbs *cbs)
{
struct vlc_h2_parser *p = malloc(sizeof (*p));
if (unlikely(p == NULL))
return NULL;
p->opaque = ctx;
p->cbs = cbs;
p->parser = vlc_h2_parse_preface;
p->headers.sid = 0;
p->headers.buf = NULL;
p->headers.len = 0;
p->headers.decoder = hpack_decode_init(VLC_H2_MAX_HEADER_TABLE);
if (unlikely(p->headers.decoder == NULL))
{
free(p);
return NULL;
}
p->rcwd_size = 65535; /* initial per-connection value */
return p;
}
void vlc_h2_parse_destroy(struct vlc_h2_parser *p)
{
hpack_decode_destroy(p->headers.decoder);
free(p->headers.buf);
free(p);
}