/*****************************************************************************
* 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;
}