n_http2_server regression: preface, SETTINGS, requests, flow control.
n_http2_server regression: preface, SETTINGS, requests, flow control.
The server is driven against a scripted peer held in memory: the client bytes are pre-built into a buffer the read callback serves, and everything the server writes is collected by the write callback and parsed back as frames. No socket is involved, so every path (including the flow-control stalls, which need an exact byte budget) is deterministic.
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
unsigned char out[262144];
static ssize_t
peer_read(
void* io,
void* buf,
size_t len) {
return -1;
if (n == 0)
return 0;
if (n > len)
n = len;
return (ssize_t)n;
}
static ssize_t
peer_write(
void* io,
const void* buf,
size_t len) {
return -1;
return (ssize_t)len;
}
static void peer_put(
PEER* p,
const void* buf,
size_t len) {
return;
}
}
static void peer_frame(
PEER* p,
int type,
int flags, uint32_t sid,
const unsigned char* payload,
size_t len) {
if (len)
}
}
unsigned char block[4096];
if (w == 0) {
return;
}
}
unsigned char b[4];
b[0] = (unsigned char)((inc >> 24) & 0x7F);
b[1] = (unsigned char)((inc >> 16) & 0xFF);
b[2] = (unsigned char)((inc >> 8) & 0xFF);
b[3] = (unsigned char)(inc & 0xFF);
}
size_t off = 0, count = 0;
if (total_payload)
*total_payload = 0;
while (off < p->out_len) {
size_t consumed = 0;
break;
count++;
if (total_payload)
}
off += consumed;
}
return count;
}
size_t off = 0, seen = 0;
while (off < p->out_len) {
size_t consumed = 0;
break;
*out = f;
return 1;
}
off += consumed;
}
return 0;
}
char status[8];
h[0].name = (char*)":status";
h[0].value = status;
h[1].name = (char*)"content-type";
h[1].value = (char*)"text/plain";
}
memset(&p, 0, sizeof(p));
if (!srv) {
return;
}
size_t n = 0, i;
int seen_win = 0;
for (i = 0; i < n; i++)
seen_win = 1;
}
}
memset(&p, 0, sizeof(p));
peer_put(&p,
"GET / HTTP/1.1\r\nHost: x\r\n\r\n\r\n\r\n\r\n", 33);
memset(&p, 0, sizeof(p));
memset(&p, 0, sizeof(p));
}
unsigned char body[] = "hello h2";
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
req[0].
name = (
char*)
":method";
req[0].
value = (
char*)
"GET";
req[1].
name = (
char*)
":scheme";
req[1].
value = (
char*)
"https";
req[2].
name = (
char*)
":authority";
req[2].
value = (
char*)
"example.test";
req[3].
name = (
char*)
":path";
req[3].
value = (
char*)
"/a?b=c";
}
size_t given = 0;
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
req[0].
name = (
char*)
":method";
req[0].
value = (
char*)
"POST";
req[1].
name = (
char*)
":scheme";
req[1].
value = (
char*)
"https";
req[2].
name = (
char*)
":authority";
req[2].
value = (
char*)
"example.test";
req[3].
name = (
char*)
":path";
req[3].
value = (
char*)
"/submit";
req[4].
name = (
char*)
"content-type";
req[4].
value = (
char*)
"application/x-www-form-urlencoded";
{
size_t off = 0, conn_inc = 0;
size_t consumed = 0;
break;
uint32_t inc = ((uint32_t)(f.
payload[0] & 0x7F) << 24) | ((uint32_t)f.
payload[1] << 16) | ((uint32_t)f.
payload[2] << 8) | (uint32_t)f.
payload[3];
if (inc < 1000)
conn_inc += inc;
}
off += consumed;
}
}
}
unsigned char block[4096];
size_t w, half;
req[0].
name = (
char*)
":method";
req[0].
value = (
char*)
"GET";
req[1].
name = (
char*)
":scheme";
req[1].
value = (
char*)
"https";
req[2].
name = (
char*)
":authority";
req[2].
value = (
char*)
"example.test";
req[3].
name = (
char*)
":path";
req[3].
value = (
char*)
"/split";
if (w < 4) {
return;
}
half = w / 2;
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
}
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
req[0].
name = (
char*)
":method";
req[0].
value = (
char*)
"GET";
req[1].
name = (
char*)
":scheme";
req[1].
value = (
char*)
"https";
req[2].
name = (
char*)
":authority";
req[2].
value = (
char*)
"example.test";
req[3].
name = (
char*)
":path";
req[3].
value = (
char*)
"/one";
req[3].
value = (
char*)
"/two";
req[3].
value = (
char*)
"/three";
}
unsigned char goaway[8];
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
memset(goaway, 0, sizeof(goaway));
}
size_t data_bytes = 0;
const size_t reply_len = 100000;
unsigned char* reply = malloc(reply_len);
if (!reply) {
return;
}
memset(reply, 'A', reply_len);
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
req[0].
name = (
char*)
":method";
req[0].
value = (
char*)
"GET";
req[1].
name = (
char*)
":scheme";
req[1].
value = (
char*)
"https";
req[2].
name = (
char*)
":authority";
req[2].
value = (
char*)
"example.test";
req[3].
name = (
char*)
":path";
req[3].
value = (
char*)
"/big";
if (data_bytes != reply_len)
failures++;
free(reply);
}
const size_t reply_len = 100000;
unsigned char* reply = malloc(reply_len);
if (!reply) {
return;
}
memset(reply, 'B', reply_len);
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
req[0].
name = (
char*)
":method";
req[0].
value = (
char*)
"GET";
req[1].
name = (
char*)
":scheme";
req[1].
value = (
char*)
"https";
req[2].
name = (
char*)
":authority";
req[2].
value = (
char*)
"example.test";
req[3].
name = (
char*)
":path";
req[3].
value = (
char*)
"/big";
{
size_t sent = 0;
}
free(reply);
}
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
req[0].
name = (
char*)
":method";
req[0].
value = (
char*)
"GET";
req[1].
name = (
char*)
":scheme";
req[1].
value = (
char*)
"https";
req[2].
name = (
char*)
":authority";
req[2].
value = (
char*)
"example.test";
req[3].
name = (
char*)
":path";
req[3].
value = (
char*)
"/gone";
}
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
req[0].
name = (
char*)
":method";
req[0].
value = (
char*)
"GET";
req[1].
name = (
char*)
":scheme";
req[1].
value = (
char*)
"https";
req[2].
name = (
char*)
":authority";
req[2].
value = (
char*)
"example.test";
req[3].
name = (
char*)
":path";
req[3].
value = (
char*)
"/one";
}
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
req[0].
name = (
char*)
":method";
req[0].
value = (
char*)
"GET";
req[1].
name = (
char*)
":scheme";
req[1].
value = (
char*)
"https";
req[2].
name = (
char*)
":authority";
req[2].
value = (
char*)
"example.test";
req[3].
name = (
char*)
":path";
req[3].
value = (
char*)
"/dribble";
}
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
}
int opt = 0;
while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
switch (opt) {
case 'V':
if (!strncmp("LOG_NULL", optarg, 8))
else if (!strncmp("LOG_NOTICE", optarg, 10))
else if (!strncmp("LOG_INFO", optarg, 8))
else if (!strncmp("LOG_ERR", optarg, 7))
else if (!strncmp("LOG_DEBUG", optarg, 9))
break;
case 'v':
fprintf(stderr, "ex_http2_server\n");
exit(1);
default:
break;
}
}
}
int main(
int argc,
char** argv) {
#define RUNT(f) \
do { \
int before = failures; \
f(); \
if (failures != before) \
n_log(LOG_ERR, "%s: %d failure(s)", #f, failures - before); \
} while (0)
return 1;
}
return 0;
}
void process_args(int argc, char **argv)
static void type(N_GUI_CTX *gui, const char *s)
Type a whole string into whatever has focus.
static void peer_frame(PEER *p, int type, int flags, uint32_t sid, const unsigned char *payload, size_t len)
static void test_send_window_stall(void)
static int on_request(void *ud, N_H2_SERVER *srv, const N_H2_REQUEST *req)
static void peer_put(PEER *p, const void *buf, size_t len)
static void peer_window_update(PEER *p, uint32_t sid, uint32_t inc)
static void test_dribbled_reads(void)
static void peer_headers(PEER *p, uint32_t sid, const N_H2_HEADER *h, size_t n, int end_stream)
static void test_send_flow_control(void)
unsigned char out[262144]
static void test_callback_stop(void)
static void test_handshake_errors(void)
static void test_post_body(void)
static ssize_t peer_write(void *io, const void *buf, size_t len)
static void test_guards(void)
static ssize_t peer_read(void *io, void *buf, size_t len)
static void test_continuation(void)
static void test_rst_stream(void)
static void peer_prologue(PEER *p)
static void test_handshake(void)
static void test_get(void)
static void test_ping_goaway(void)
static int out_find(const PEER *p, int type, size_t nth, N_H2_FRAME *out)
static void test_multiplex(void)
static size_t out_count(const PEER *p, int type, size_t *total_payload)
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_DEBUG
debug-level messages
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
#define LOG_NOTICE
normal but significant condition
#define LOG_NULL
no log output
#define LOG_INFO
informational
size_t n_http2_hpack_encode(const N_H2_HEADER *headers, size_t n, unsigned char *out, size_t out_cap)
encode a header list into a block using literal representations without indexing and without Huffman ...
int n_http2_settings_parse(const unsigned char *payload, size_t len, N_H2_SETTING *out, size_t max, size_t *count)
parse a SETTINGS frame payload (6-byte id/value entries) into out.
int n_http2_frame_parse(const unsigned char *buf, size_t len, N_H2_FRAME *frame, size_t *consumed)
parse one HTTP/2 frame from a byte buffer (stateless).
size_t n_http2_frame_build_header(unsigned char *out, size_t out_cap, uint32_t length, int type, int flags, uint32_t stream_id)
write a 9-byte HTTP/2 frame header into out.
char * value
header field value (owned after decode)
#define N_H2_PREFACE
the client connection preface sent before any HTTP/2 frame (RFC 7540 3.5)
#define N_H2_FLAG_END_HEADERS
HEADERS/PUSH_PROMISE/CONTINUATION: header block complete.
#define N_H2_RST_STREAM
RST_STREAM.
#define N_H2_FRAME_HEADER_LEN
length of the fixed frame header in bytes
#define N_H2_FLAG_ACK
SETTINGS/PING: acknowledgement.
char * name
header field name (owned after decode)
#define N_H2_SETTINGS_INITIAL_WINDOW_SIZE
#define N_H2_WINDOW_UPDATE
WINDOW_UPDATE.
uint32_t length
payload length (24-bit)
#define N_H2_CONTINUATION
CONTINUATION.
const unsigned char * payload
pointer into the input buffer (may be NULL when length is 0)
int type
frame type (N_H2_*)
#define N_H2_SETTINGS
SETTINGS.
uint32_t stream_id
stream identifier (31-bit; the reserved bit is cleared)
#define N_H2_HEADERS
HEADERS.
#define N_H2_FLAG_END_STREAM
DATA/HEADERS: last frame of the stream.
#define N_H2_GOAWAY
GOAWAY.
a parsed HTTP/2 frame; payload aliases the input buffer
one HPACK header field; after a decode both strings are owned (heap)
one SETTINGS parameter (identifier and value)
int n_http2_server_respond(N_H2_SERVER *s, uint32_t stream_id, const N_H2_HEADER *headers, size_t nheaders, const unsigned char *body, size_t body_len)
write a complete response on a stream (HEADERS then DATA, END_STREAM)
int n_http2_server_run(N_H2_SERVER *s, n_h2_srv_request_cb cb, void *user_data)
serve the connection until it ends
N_H2_SERVER * n_http2_server_new(void *io, n_h2_srv_read_fn rd, n_h2_srv_write_fn wr)
create a server connection over the given I/O callbacks
void n_http2_server_free(N_H2_SERVER **srv)
free a server connection and NULL the caller's pointer
const char * n_http2_server_error(const N_H2_SERVER *s)
the reason the connection failed, or NULL when none was recorded
size_t n_http2_server_request_count(const N_H2_SERVER *s)
the number of requests delivered to the callback so far
int n_http2_server_handshake(N_H2_SERVER *s)
read and validate the client connection preface, then send ours
HTTP/2 (RFC 7540) server-side connection and stream state machine.
const char * authority
the :authority pseudo-header, or NULL when absent
uint32_t stream_id
the stream the request arrived on
const char * method
the :method pseudo-header, or NULL when absent
size_t body_len
the body length in bytes
#define N_H2_ERR_CANCEL
stream cancelled
const char * scheme
the :scheme pseudo-header, or NULL when absent
const char * path
the :path pseudo-header, or NULL when absent
const unsigned char * body
the request body, or NULL when there is none
one complete request received on a stream; every pointer is owned by the server and valid only for th...