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

/*****************************************************************************
* 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 = ARRAY_SIZE(resp_hdrv);
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;
}