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.
507 lines
13 KiB
507 lines
13 KiB
/*****************************************************************************
|
|
* h2frame_test.c: HTTP/2 frame formatting tests
|
|
*****************************************************************************
|
|
* 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
|
|
|
|
#undef NDEBUG
|
|
|
|
#include <assert.h>
|
|
#include <inttypes.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "h2frame.h"
|
|
#include <vlc_common.h>
|
|
#include "conn.h"
|
|
|
|
static char dummy;
|
|
static char *const CTX = &dummy;
|
|
|
|
static unsigned settings;
|
|
|
|
/* Callbacks */
|
|
void vlc_http_dbg(void *stream, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vfprintf(stream, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void vlc_h2_setting(void *ctx, uint_fast16_t id, uint_fast32_t value)
|
|
{
|
|
assert(ctx == CTX);
|
|
settings++;
|
|
|
|
fprintf(stderr, "* Setting %s: %"PRIuFAST32"\n",
|
|
vlc_h2_setting_name(id), value);
|
|
|
|
switch (id)
|
|
{
|
|
case VLC_H2_SETTING_HEADER_TABLE_SIZE:
|
|
assert(value == VLC_H2_MAX_HEADER_TABLE);
|
|
break;
|
|
case VLC_H2_SETTING_ENABLE_PUSH:
|
|
assert(value == 0);
|
|
break;
|
|
case VLC_H2_SETTING_MAX_CONCURRENT_STREAMS:
|
|
assert(value == VLC_H2_MAX_STREAMS);
|
|
break;
|
|
case VLC_H2_SETTING_INITIAL_WINDOW_SIZE:
|
|
assert(value == VLC_H2_INIT_WINDOW);
|
|
break;
|
|
case VLC_H2_SETTING_MAX_FRAME_SIZE:
|
|
assert(value == VLC_H2_MAX_FRAME);
|
|
break;
|
|
case VLC_H2_SETTING_MAX_HEADER_LIST_SIZE:
|
|
assert(value == VLC_H2_MAX_HEADER_LIST);
|
|
break;
|
|
default:
|
|
assert(!"Known setting");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static unsigned settings_acked;
|
|
|
|
static int vlc_h2_settings_done(void *ctx)
|
|
{
|
|
assert(ctx == CTX);
|
|
settings_acked++;
|
|
(void) ctx;
|
|
return 0;
|
|
}
|
|
|
|
#define PING_VALUE UINT64_C(0x1122334455667788)
|
|
|
|
static unsigned pings;
|
|
|
|
static int vlc_h2_ping(void *ctx, uint_fast64_t opaque)
|
|
{
|
|
assert(ctx == CTX);
|
|
assert(opaque == PING_VALUE);
|
|
pings++;
|
|
return 0;
|
|
}
|
|
|
|
static uint_fast32_t remote_error;
|
|
|
|
static void vlc_h2_error(void *ctx, uint_fast32_t code)
|
|
{
|
|
assert(ctx == CTX);
|
|
(void) code;
|
|
}
|
|
|
|
static int vlc_h2_reset(void *ctx, uint_fast32_t last_seq, uint_fast32_t code)
|
|
{
|
|
assert(ctx == CTX);
|
|
assert(last_seq == 0);
|
|
remote_error = code;
|
|
return 0;
|
|
}
|
|
|
|
static void vlc_h2_window_status(void *ctx, uint32_t * restrict rcwd)
|
|
{
|
|
assert(ctx == CTX);
|
|
*rcwd = (1u << 31) - 1;
|
|
}
|
|
|
|
static void vlc_h2_window_update(void *ctx, uint_fast32_t credit)
|
|
{
|
|
assert(ctx == CTX);
|
|
assert(credit == 0x1000);
|
|
}
|
|
|
|
#define STREAM_ID 0x76543210
|
|
char stream_cookie; /* dummy unique value */
|
|
|
|
static void *vlc_h2_stream_lookup(void *ctx, uint_fast32_t id)
|
|
{
|
|
assert(ctx == CTX);
|
|
|
|
if (id != STREAM_ID)
|
|
return NULL;
|
|
return &stream_cookie;
|
|
}
|
|
|
|
static const char *const resp_hdrv[][2] = {
|
|
{ ":status", "200" },
|
|
{ "cache-control", "private" },
|
|
{ "date", "Mon, 21 Oct 2013 20:13:22 GMT" },
|
|
{ "location", "https://www.example.com" },
|
|
{ "content-encoding", "gzip" },
|
|
{ "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; "
|
|
"version=1" },
|
|
};
|
|
static const unsigned resp_hdrc = sizeof (resp_hdrv) / sizeof (resp_hdrv[0]);
|
|
|
|
static unsigned stream_header_tables;
|
|
|
|
static void vlc_h2_stream_headers(void *ctx, unsigned count,
|
|
const char *const hdrs[][2])
|
|
{
|
|
assert(ctx == &stream_cookie);
|
|
assert(count == resp_hdrc);
|
|
|
|
for (unsigned i = 0; i < count; i++)
|
|
{
|
|
assert(!strcmp(hdrs[i][0], resp_hdrv[i][0]));
|
|
assert(!strcmp(hdrs[i][1], resp_hdrv[i][1]));
|
|
}
|
|
|
|
stream_header_tables++;
|
|
}
|
|
|
|
#define MESSAGE "Hello world!"
|
|
|
|
static unsigned stream_blocks;
|
|
|
|
static int vlc_h2_stream_data(void *ctx, struct vlc_h2_frame *f)
|
|
{
|
|
size_t len;
|
|
const uint8_t *buf = vlc_h2_frame_data_get(f, &len);
|
|
|
|
assert(ctx == &stream_cookie);
|
|
assert(len == sizeof (MESSAGE));
|
|
assert(!memcmp(buf, MESSAGE, len));
|
|
stream_blocks++;
|
|
free(f);
|
|
return 0;
|
|
}
|
|
|
|
static unsigned stream_ends;
|
|
|
|
static void vlc_h2_stream_end(void *ctx)
|
|
{
|
|
assert(ctx == &stream_cookie);
|
|
stream_ends++;
|
|
}
|
|
|
|
static int vlc_h2_stream_error(void *ctx, uint_fast32_t id, uint_fast32_t code)
|
|
{
|
|
assert(ctx == CTX);
|
|
|
|
switch (id)
|
|
{
|
|
case STREAM_ID + 2:
|
|
assert(code == VLC_H2_REFUSED_STREAM);
|
|
break;
|
|
case STREAM_ID + 4:
|
|
assert(code == VLC_H2_PROTOCOL_ERROR);
|
|
break;
|
|
case STREAM_ID + 6:
|
|
assert(code == VLC_H2_FRAME_SIZE_ERROR);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int vlc_h2_stream_reset(void *ctx, uint_fast32_t code)
|
|
{
|
|
assert(ctx == &stream_cookie);
|
|
assert(code == VLC_H2_CANCEL);
|
|
return 0;
|
|
}
|
|
|
|
static void vlc_h2_stream_window_update(void *ctx, uint_fast32_t credit)
|
|
{
|
|
assert(ctx == &stream_cookie);
|
|
(void) credit;
|
|
}
|
|
|
|
/* Frame formatting */
|
|
static struct vlc_h2_frame *resize(struct vlc_h2_frame *f, size_t size)
|
|
{ /* NOTE: increasing size would require realloc() */
|
|
f->data[0] = size >> 16;
|
|
f->data[1] = size >> 8;
|
|
f->data[2] = size;
|
|
return f;
|
|
}
|
|
|
|
static struct vlc_h2_frame *retype(struct vlc_h2_frame *f, unsigned char type)
|
|
{
|
|
f->data[3] = type;
|
|
return f;
|
|
}
|
|
|
|
static struct vlc_h2_frame *reflag(struct vlc_h2_frame *f, unsigned char flags)
|
|
{
|
|
f->data[4] = flags;
|
|
return f;
|
|
}
|
|
|
|
static struct vlc_h2_frame *globalize(struct vlc_h2_frame *f)
|
|
{
|
|
memset(f->data + 5, 0, 4);
|
|
return f;
|
|
}
|
|
|
|
static struct vlc_h2_frame *localize(struct vlc_h2_frame *f)
|
|
{
|
|
f->data[5] = (STREAM_ID >> 24) & 0xff;
|
|
f->data[6] = (STREAM_ID >> 16) & 0xff;
|
|
f->data[7] = (STREAM_ID >> 8) & 0xff;
|
|
f->data[8] = STREAM_ID & 0xff;
|
|
return f;
|
|
}
|
|
|
|
static struct vlc_h2_frame *response(bool eos)
|
|
{
|
|
/* Use ridiculously small MTU to test headers fragmentation */
|
|
return vlc_h2_frame_headers(STREAM_ID, 16, eos, resp_hdrc, resp_hdrv);
|
|
}
|
|
|
|
static struct vlc_h2_frame *data(bool eos)
|
|
{
|
|
return vlc_h2_frame_data(STREAM_ID, MESSAGE, sizeof (MESSAGE), eos);
|
|
}
|
|
|
|
static struct vlc_h2_frame *priority(void)
|
|
{
|
|
return localize(resize(retype(data(false), 0x2), 5));
|
|
}
|
|
|
|
static struct vlc_h2_frame *rst_stream(void)
|
|
{
|
|
return vlc_h2_frame_rst_stream(STREAM_ID, VLC_H2_CANCEL);
|
|
}
|
|
|
|
static struct vlc_h2_frame *ping(void)
|
|
{
|
|
return vlc_h2_frame_ping(PING_VALUE);
|
|
}
|
|
|
|
static struct vlc_h2_frame *goaway(void)
|
|
{
|
|
return vlc_h2_frame_goaway(0, VLC_H2_NO_ERROR);
|
|
}
|
|
|
|
static struct vlc_h2_frame *unknown(void)
|
|
{
|
|
return retype(ping(), 200);
|
|
}
|
|
|
|
/* Test harness */
|
|
static unsigned test_raw_seqv(struct vlc_h2_parser *p, va_list ap)
|
|
{
|
|
struct vlc_h2_frame *f;
|
|
unsigned i = 0;
|
|
|
|
assert(p != NULL);
|
|
|
|
while ((f = va_arg(ap, struct vlc_h2_frame *)) != NULL)
|
|
if (vlc_h2_parse(p, f) == 0)
|
|
i++;
|
|
|
|
return i;
|
|
}
|
|
|
|
static unsigned test_raw_seq(struct vlc_h2_parser *p, ...)
|
|
{
|
|
va_list ap;
|
|
unsigned i;
|
|
|
|
assert(p != NULL);
|
|
va_start(ap, p);
|
|
i = test_raw_seqv(p, ap);
|
|
va_end(ap);
|
|
return i;
|
|
}
|
|
|
|
static const struct vlc_h2_parser_cbs vlc_h2_frame_test_callbacks =
|
|
{
|
|
vlc_h2_setting,
|
|
vlc_h2_settings_done,
|
|
vlc_h2_ping,
|
|
vlc_h2_error,
|
|
vlc_h2_reset,
|
|
vlc_h2_window_status,
|
|
vlc_h2_window_update,
|
|
vlc_h2_stream_lookup,
|
|
vlc_h2_stream_error,
|
|
vlc_h2_stream_headers,
|
|
vlc_h2_stream_data,
|
|
vlc_h2_stream_end,
|
|
vlc_h2_stream_reset,
|
|
vlc_h2_stream_window_update,
|
|
};
|
|
|
|
static unsigned test_seq(void *ctx, ...)
|
|
{
|
|
struct vlc_h2_parser *p;
|
|
va_list ap;
|
|
unsigned i;
|
|
|
|
settings = settings_acked = 0;
|
|
pings = 0;
|
|
remote_error = -1;
|
|
stream_header_tables = stream_blocks = stream_ends = 0;
|
|
|
|
p = vlc_h2_parse_init(ctx, &vlc_h2_frame_test_callbacks);
|
|
assert(p != NULL);
|
|
|
|
i = test_raw_seq(p, vlc_h2_frame_settings(), vlc_h2_frame_settings_ack(),
|
|
NULL);
|
|
assert(i == 2);
|
|
assert(settings >= 1);
|
|
assert(settings_acked == 1);
|
|
|
|
va_start(ap, ctx);
|
|
i = test_raw_seqv(p, ap);
|
|
va_end(ap);
|
|
|
|
assert(test_raw_seq(p, goaway(), NULL) == 1);
|
|
assert(remote_error == VLC_H2_NO_ERROR);
|
|
|
|
vlc_h2_parse_destroy(p);
|
|
return i;
|
|
}
|
|
|
|
static unsigned test_bad_seq(void *ctx, ...)
|
|
{
|
|
struct vlc_h2_parser *p;
|
|
va_list ap;
|
|
unsigned i;
|
|
|
|
p = vlc_h2_parse_init(ctx, &vlc_h2_frame_test_callbacks);
|
|
assert(p != NULL);
|
|
|
|
i = test_raw_seq(p, vlc_h2_frame_settings(), vlc_h2_frame_settings_ack(),
|
|
NULL);
|
|
assert(i == 2);
|
|
|
|
va_start(ap, ctx);
|
|
i = test_raw_seqv(p, ap);
|
|
va_end(ap);
|
|
|
|
i = test_raw_seq(p, goaway(), NULL);
|
|
assert(i == 0); /* in failed state */
|
|
vlc_h2_parse_destroy(p);
|
|
return i;
|
|
}
|
|
|
|
static void test_preface_fail(void)
|
|
{
|
|
struct vlc_h2_parser *p;
|
|
|
|
p = vlc_h2_parse_init(CTX, &vlc_h2_frame_test_callbacks);
|
|
assert(p != NULL);
|
|
|
|
assert(test_raw_seq(p, ping(), ping(), NULL) == 0);
|
|
|
|
vlc_h2_parse_destroy(p);
|
|
}
|
|
|
|
static void test_header_block_fail(void)
|
|
{
|
|
struct vlc_h2_frame *hf = response(true);
|
|
struct vlc_h2_frame *pf = ping();
|
|
|
|
/* Check what happens if non-CONTINUATION frame after HEADERS */
|
|
assert(hf != NULL && hf->next != NULL && pf != NULL);
|
|
pf->next = hf->next;
|
|
hf->next = pf;
|
|
assert(test_bad_seq(CTX, hf, NULL) == 0);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = test_seq(CTX, NULL);
|
|
assert(ret == 0);
|
|
|
|
ret = test_seq(CTX, ping(), vlc_h2_frame_pong(42), ping(), NULL);
|
|
assert(ret == 3);
|
|
assert(pings == 2);
|
|
assert(stream_header_tables == 0);
|
|
assert(stream_blocks == 0);
|
|
assert(stream_ends == 0);
|
|
|
|
ret = test_seq(CTX, response(true), NULL);
|
|
assert(ret == 1);
|
|
assert(pings == 0);
|
|
assert(stream_header_tables == 1);
|
|
assert(stream_blocks == 0);
|
|
assert(stream_ends == 1);
|
|
|
|
ret = test_seq(CTX, response(false), data(true), ping(),
|
|
response(false), data(false), data(true),
|
|
response(false), data(false), priority(), unknown(),
|
|
NULL);
|
|
assert(ret == 10);
|
|
assert(pings == 1);
|
|
assert(stream_header_tables == 3);
|
|
assert(stream_blocks == 4);
|
|
assert(stream_ends == 2);
|
|
|
|
ret = test_seq(CTX, rst_stream(),
|
|
vlc_h2_frame_window_update(0, 0x1000),
|
|
vlc_h2_frame_headers(STREAM_ID + 2,
|
|
VLC_H2_DEFAULT_MAX_FRAME, true,
|
|
resp_hdrc, resp_hdrv),
|
|
NULL);
|
|
assert(ret == 3);
|
|
assert(pings == 0);
|
|
assert(stream_header_tables == 0);
|
|
assert(stream_blocks == 0);
|
|
assert(stream_ends == 0);
|
|
|
|
test_preface_fail();
|
|
test_header_block_fail();
|
|
|
|
test_bad_seq(CTX, globalize(response(true)), NULL);
|
|
test_bad_seq(CTX, resize(reflag(response(true), 0x08), 0), NULL);
|
|
test_bad_seq(CTX, resize(reflag(response(true), 0x20), 4), NULL);
|
|
test_bad_seq(CTX, globalize(data(true)), NULL);
|
|
test_bad_seq(CTX, globalize(priority()), NULL);
|
|
test_bad_seq(CTX, globalize(rst_stream()), NULL);
|
|
test_bad_seq(CTX, localize(vlc_h2_frame_settings()), NULL);
|
|
test_bad_seq(CTX, resize(vlc_h2_frame_settings(), 5), NULL);
|
|
test_bad_seq(CTX, resize(ping(), 7), NULL);
|
|
test_bad_seq(CTX, localize(ping()), NULL);
|
|
test_bad_seq(CTX, localize(goaway()), NULL);
|
|
test_bad_seq(CTX, resize(goaway(), 7), NULL);
|
|
test_bad_seq(CTX, vlc_h2_frame_window_update(0, 0), NULL);
|
|
test_bad_seq(CTX, resize(vlc_h2_frame_window_update(0, 0), 3), NULL);
|
|
|
|
test_seq(CTX, resize(vlc_h2_frame_window_update(STREAM_ID + 6, 0), 5),
|
|
NULL);
|
|
test_seq(CTX, vlc_h2_frame_window_update(STREAM_ID + 4, 0), NULL);
|
|
|
|
/* TODO: PUSH_PROMISE, PRIORITY, padding, unknown, invalid stuff... */
|
|
|
|
/* Dummy API test */
|
|
assert(vlc_h2_frame_data(1, NULL, 1 << 28, false) == NULL);
|
|
|
|
for (unsigned i = 0; i < 65536; i++)
|
|
assert(strlen(vlc_h2_setting_name(i)) > 0);
|
|
for (unsigned i = 0; i < 100; i++) /* lets not try all 4 billions */
|
|
assert(strlen(vlc_h2_strerror(i)) > 0);
|
|
|
|
return 0;
|
|
}
|
|
|