5 ex_http2_server_8c example
gullradriel edited this page 2026-10-09 00:51:57 +02:00

ex_http2_server.c

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. ```c

/*

  • Nilorea Library
  • Copyright (C) 2005-2026 Castagnier Mickael
  • Licensed under the Apache License, Version 2.0 (the "License");
  • you may not use this file except in compliance with the License.
  • You may obtain a copy of the License at
  • http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software
  • distributed under the License is distributed on an "AS IS" BASIS,
  • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  • implied. See the License for the specific language governing
  • permissions and limitations under the License.
  • SPDX-License-Identifier: Apache-2.0 */

#include "nilorea/n_http2_server.h" #include "nilorea/n_log.h"

#include <getopt.h> #include <stdlib.h> #include <string.h>

static int failures = 0;

/* ------------------------------------------------------------------ / / the scripted peer / / ------------------------------------------------------------------ */

/* A byte script the server reads from, plus the bytes it wrote back. / typedef struct PEER { unsigned char in[65536]; / what the client sends / size_t in_len; / its length / size_t in_off; / how much the server has consumed / unsigned char out[262144]; / what the server sent / size_t out_len; / its length / size_t chunk; / max bytes served per read (0 = no limit) / int read_fail; / return -1 from the read callback */ } PEER;

static ssize_t peer_read(void* io, void* buf, size_t len) { PEER* p = (PEER*)io; size_t n = p->in_len - p->in_off; if (p->read_fail) return -1; if (n == 0) return 0; /* end of the script: a clean peer close */ if (n > len) n = len; if (p->chunk && n > p->chunk) n = p->chunk; memcpy(buf, p->in + p->in_off, n); p->in_off += n; return (ssize_t)n; }

static ssize_t peer_write(void* io, const void* buf, size_t len) { PEER* p = (PEER*)io; if (p->out_len + len > sizeof(p->out)) return -1; memcpy(p->out + p->out_len, buf, len); p->out_len += len; return (ssize_t)len; }

/* Append raw bytes to the client script. / static void peer_put(PEER p, const void* buf, size_t len) { if (p->in_len + len > sizeof(p->in)) { failures++; return; } memcpy(p->in + p->in_len, buf, len); p->in_len += len; }

/* Append one frame (header then payload) to the client script. / static void peer_frame(PEER p, int type, int flags, uint32_t sid, const unsigned char* payload, size_t len) { unsigned char hdr[N_H2_FRAME_HEADER_LEN]; n_http2_frame_build_header(hdr, sizeof(hdr), (uint32_t)len, type, flags, sid); peer_put(p, hdr, sizeof(hdr)); if (len) peer_put(p, payload, len); }

/* Append the client preface and an empty SETTINGS. / static void peer_prologue(PEER p) { peer_put(p, N_H2_PREFACE, strlen(N_H2_PREFACE)); peer_frame(p, N_H2_SETTINGS, 0, 0, NULL, 0); }

/* Append a HEADERS frame carrying an HPACK-encoded header list. / static void peer_headers(PEER p, uint32_t sid, const N_H2_HEADER* h, size_t n, int end_stream) { unsigned char block[4096]; size_t w = n_http2_hpack_encode(h, n, block, sizeof(block)); int flags = N_H2_FLAG_END_HEADERS | (end_stream ? N_H2_FLAG_END_STREAM : 0); if (w == 0) { failures++; return; } peer_frame(p, N_H2_HEADERS, flags, sid, block, w); }

/* Append a WINDOW_UPDATE frame. / static void peer_window_update(PEER p, uint32_t sid, uint32_t inc) { 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); peer_frame(p, N_H2_WINDOW_UPDATE, 0, sid, b, sizeof(b)); }

/* Count the frames of one type the server sent, and sum their payload lengths. / static size_t out_count(const PEER p, int type, size_t* total_payload) { size_t off = 0, count = 0; if (total_payload) *total_payload = 0; while (off < p->out_len) { N_H2_FRAME f; size_t consumed = 0; if (n_http2_frame_parse(p->out + off, p->out_len - off, &f, &consumed) != 1) break; if (f.type == type) { count++; if (total_payload) *total_payload += f.length; } off += consumed; } return count; }

/* Find the nth frame of a type the server sent. Returns 1 when found. / static int out_find(const PEER p, int type, size_t nth, N_H2_FRAME* out) { size_t off = 0, seen = 0; while (off < p->out_len) { N_H2_FRAME f; size_t consumed = 0; if (n_http2_frame_parse(p->out + off, p->out_len - off, &f, &consumed) != 1) break; if (f.type == type && seen++ == nth) { *out = f; return 1; } off += consumed; } return 0; }

/* ------------------------------------------------------------------ / / the handler / / ------------------------------------------------------------------ */

/* What the request callback recorded, so the checks can look at it. / typedef struct SEEN { int calls; char method[32]; char path[256]; char authority[128]; char scheme[16]; char body[4096]; size_t body_len; size_t reply_len; / bytes of body to answer with / int reply_status; / :status to answer with / int stop_after; / return -1 from the callback after this many calls / int expect_send_fail; / the response is expected NOT to go out in full / unsigned char reply; /* the reply body (owned by the test) */ } SEEN;

static int on_request(void* ud, N_H2_SERVER* srv, const N_H2_REQUEST* req) { SEEN* s = (SEEN*)ud; N_H2_HEADER h[2]; char status[8]; s->calls++; snprintf(s->method, sizeof(s->method), "%s", req->method ? req->method : ""); snprintf(s->path, sizeof(s->path), "%s", req->path ? req->path : ""); snprintf(s->authority, sizeof(s->authority), "%s", req->authority ? req->authority : ""); snprintf(s->scheme, sizeof(s->scheme), "%s", req->scheme ? req->scheme : ""); s->body_len = req->body_len < sizeof(s->body) ? req->body_len : sizeof(s->body) - 1; if (req->body && s->body_len) memcpy(s->body, req->body, s->body_len); s->body[s->body_len] = '\0';

snprintf(status, sizeof(status), "%d", s->reply_status ? s->reply_status : 200);
h[0].name = (char*)":status";
h[0].value = status;
h[1].name = (char*)"content-type";
h[1].value = (char*)"text/plain";
if ((n_http2_server_respond(srv, req->stream_id, h, 2, s->reply, s->reply_len) != 0) != (s->expect_send_fail != 0))
    failures++;
return (s->stop_after && s->calls >= s->stop_after) ? -1 : 0;

}

/* ------------------------------------------------------------------ / / checks / / ------------------------------------------------------------------ */

/* The handshake reads the preface, answers with SETTINGS plus a connection WINDOW_UPDATE, and acknowledges the client's SETTINGS. / static void test_handshake(void) { PEER p; N_H2_SERVER srv; N_H2_FRAME f;

memset(&p, 0, sizeof(p));
peer_prologue(&p);
srv = n_http2_server_new(&p, peer_read, peer_write);
if (!srv) {
    failures++;
    return;
}
if (n_http2_server_handshake(srv) != 0) failures++;
if (out_count(&p, N_H2_SETTINGS, NULL) != 2) failures++;      /* ours plus the ACK */
if (out_count(&p, N_H2_WINDOW_UPDATE, NULL) != 1) failures++; /* the connection window */
if (!out_find(&p, N_H2_SETTINGS, 0, &f) || f.flags != 0) failures++;
if (!out_find(&p, N_H2_SETTINGS, 1, &f) || !(f.flags & N_H2_FLAG_ACK)) failures++;
/* our SETTINGS must announce a stream receive window, or a body over 64KB stalls */
if (out_find(&p, N_H2_SETTINGS, 0, &f)) {
    N_H2_SETTING set[8];
    size_t n = 0, i;
    int seen_win = 0;
    if (n_http2_settings_parse(f.payload, f.length, set, 8, &n) != 0) failures++;
    for (i = 0; i < n; i++)
        if (set[i].id == N_H2_SETTINGS_INITIAL_WINDOW_SIZE && set[i].value > 65535)
            seen_win = 1;
    if (!seen_win) failures++;
}
n_http2_server_free(&srv);
if (srv) failures++;

}

/* A bad preface, and a first frame that is not SETTINGS, are both refused. / static void test_handshake_errors(void) { PEER p; N_H2_SERVER srv;

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);
srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != -1) failures++;
if (!n_http2_server_error(srv)) failures++;
n_http2_server_free(&srv);

memset(&p, 0, sizeof(p));
peer_put(&p, N_H2_PREFACE, strlen(N_H2_PREFACE));
peer_frame(&p, N_H2_PING, 0, 0, (const unsigned char*)"12345678", 8);
srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != -1) failures++;
n_http2_server_free(&srv);

/* a truncated preface is an error, not a hang */
memset(&p, 0, sizeof(p));
peer_put(&p, N_H2_PREFACE, 10);
srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != -1) failures++;
n_http2_server_free(&srv);

}

/* One GET: the pseudo-headers reach the callback and the response comes back as HEADERS with END_STREAM plus one DATA frame. / static void test_get(void) { PEER p; SEEN seen; N_H2_SERVER srv; N_H2_FRAME f; N_H2_HEADER req[4]; unsigned char body[] = "hello h2";

memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
seen.reply = body;
seen.reply_len = sizeof(body) - 1;
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";
peer_prologue(&p);
peer_headers(&p, 1, req, 4, 1);

srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
if (seen.calls != 1) failures++;
if (strcmp(seen.method, "GET") != 0) failures++;
if (strcmp(seen.path, "/a?b=c") != 0) failures++;
if (strcmp(seen.authority, "example.test") != 0) failures++;
if (strcmp(seen.scheme, "https") != 0) failures++;
if (seen.body_len != 0) failures++;
if (n_http2_server_request_count(srv) != 1) failures++;

/* the response: HEADERS on stream 1, then DATA with END_STREAM */
if (!out_find(&p, N_H2_HEADERS, 0, &f) || f.stream_id != 1) failures++;
if (!(f.flags & N_H2_FLAG_END_HEADERS)) failures++;
if (f.flags & N_H2_FLAG_END_STREAM) failures++; /* a body follows */
if (!out_find(&p, N_H2_DATA, 0, &f) || f.stream_id != 1) failures++;
if (!(f.flags & N_H2_FLAG_END_STREAM)) failures++;
if (f.length != sizeof(body) - 1 || memcmp(f.payload, body, f.length) != 0) failures++;
if (out_count(&p, N_H2_GOAWAY, NULL) != 1) failures++;
n_http2_server_free(&srv);

}

/* A POST body split over several DATA frames is reassembled, and every byte is given back to the connection window (including the END_STREAM frame's). / static void test_post_body(void) { PEER p; SEEN seen; N_H2_SERVER srv; N_H2_HEADER req[5]; 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";
peer_prologue(&p);
peer_headers(&p, 1, req, 5, 0);
peer_frame(&p, N_H2_DATA, 0, 1, (const unsigned char*)"user=admin", 10);
peer_frame(&p, N_H2_DATA, N_H2_FLAG_END_STREAM, 1, (const unsigned char*)"&pass=x", 7);

srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
if (seen.calls != 1) failures++;
if (strcmp(seen.method, "POST") != 0) failures++;
if (seen.body_len != 17 || strcmp(seen.body, "user=admin&pass=x") != 0) failures++;

/* the connection window must be replenished by all 17 body bytes: dropping
   the last frame's 10+7 would starve a long-lived connection one request at
   a time, which only shows up after many requests */
out_count(&p, N_H2_WINDOW_UPDATE, &given);
{
    size_t off = 0, conn_inc = 0;
    while (off < p.out_len) {
        N_H2_FRAME f;
        size_t consumed = 0;
        if (n_http2_frame_parse(p.out + off, p.out_len - off, &f, &consumed) != 1)
            break;
        if (f.type == N_H2_WINDOW_UPDATE && f.stream_id == 0 && f.length == 4) {
            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) /* skip the big handshake update */
                conn_inc += inc;
        }
        off += consumed;
    }
    if (conn_inc != 17) failures++;
}
n_http2_server_free(&srv);

}

/* A header block split into CONTINUATION frames is reassembled; a frame interleaved inside the block is a connection error. / static void test_continuation(void) { PEER p; SEEN seen; N_H2_SERVER srv; N_H2_HEADER req[4]; 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";
w = n_http2_hpack_encode(req, 4, block, sizeof(block));
if (w < 4) {
    failures++;
    return;
}
half = w / 2;

memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
peer_prologue(&p);
peer_frame(&p, N_H2_HEADERS, N_H2_FLAG_END_STREAM, 1, block, half);
peer_frame(&p, N_H2_CONTINUATION, N_H2_FLAG_END_HEADERS, 1, block + half, w - half);
srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
if (seen.calls != 1 || strcmp(seen.path, "/split") != 0) failures++;
n_http2_server_free(&srv);

/* a PING between the HEADERS and its CONTINUATION leaves HPACK undefined:
   the connection must fail rather than decode a guess */
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
peer_prologue(&p);
peer_frame(&p, N_H2_HEADERS, N_H2_FLAG_END_STREAM, 1, block, half);
peer_frame(&p, N_H2_PING, 0, 0, (const unsigned char*)"12345678", 8);
peer_frame(&p, N_H2_CONTINUATION, N_H2_FLAG_END_HEADERS, 1, block + half, w - half);
srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) == 0) failures++; /* a connection error */
if (seen.calls != 0) failures++;
if (!n_http2_server_error(srv)) failures++;
n_http2_server_free(&srv);

/* a CONTINUATION for another stream is the same class of error */
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
peer_prologue(&p);
peer_frame(&p, N_H2_HEADERS, N_H2_FLAG_END_STREAM, 1, block, half);
peer_frame(&p, N_H2_CONTINUATION, N_H2_FLAG_END_HEADERS, 3, block + half, w - half);
srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) == 0) failures++;
n_http2_server_free(&srv);

}

/* Several streams on one connection are served in the order they complete. / static void test_multiplex(void) { PEER p; SEEN seen; N_H2_SERVER srv; N_H2_HEADER req[4]; N_H2_FRAME f;

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";
peer_prologue(&p);
peer_headers(&p, 1, req, 4, 1);
req[3].value = (char*)"/two";
peer_headers(&p, 3, req, 4, 1);
req[3].value = (char*)"/three";
peer_headers(&p, 5, req, 4, 1);

srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
if (seen.calls != 3) failures++;
if (strcmp(seen.path, "/three") != 0) failures++; /* the last one served */
if (out_count(&p, N_H2_HEADERS, NULL) != 3) failures++;
if (!out_find(&p, N_H2_HEADERS, 0, &f) || f.stream_id != 1) failures++;
if (!out_find(&p, N_H2_HEADERS, 1, &f) || f.stream_id != 3) failures++;
if (!out_find(&p, N_H2_HEADERS, 2, &f) || f.stream_id != 5) failures++;
n_http2_server_free(&srv);

}

/* PING is answered with a PING ACK carrying the same payload; a client GOAWAY ends the run. / static void test_ping_goaway(void) { PEER p; SEEN seen; N_H2_SERVER srv; N_H2_FRAME f; unsigned char goaway[8];

memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
memset(goaway, 0, sizeof(goaway));
peer_prologue(&p);
peer_frame(&p, N_H2_PING, 0, 0, (const unsigned char*)"abcdefgh", 8);
peer_frame(&p, N_H2_GOAWAY, 0, 0, goaway, sizeof(goaway));
srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
if (!out_find(&p, N_H2_PING, 0, &f)) failures++;
if (!(f.flags & N_H2_FLAG_ACK) || f.length != 8 || memcmp(f.payload, "abcdefgh", 8) != 0) failures++;
if (seen.calls != 0) failures++;
n_http2_server_free(&srv);

}

/* A response larger than the peer's initial 65535-byte window is written only as far as the window allows, then continues once a WINDOW_UPDATE arrives. This is the check a server that ignores send-side flow control fails by hanging. / static void test_send_flow_control(void) { PEER p; SEEN seen; N_H2_SERVER srv; N_H2_HEADER req[4]; size_t data_bytes = 0; const size_t reply_len = 100000; unsigned char* reply = malloc(reply_len);

if (!reply) {
    failures++;
    return;
}
memset(reply, 'A', reply_len);
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
seen.reply = reply;
seen.reply_len = reply_len;
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";
peer_prologue(&p);
peer_headers(&p, 1, req, 4, 1);
/* the client reopens both windows, but only after the request: the server
   must park mid-body and resume, not deadlock and not overshoot */
peer_window_update(&p, 0, 100000);
peer_window_update(&p, 1, 100000);

srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
out_count(&p, N_H2_DATA, &data_bytes);
if (data_bytes != reply_len) failures++;
/* 100000 bytes at the default 16384 max frame size is 7 DATA frames */
if (out_count(&p, N_H2_DATA, NULL) != 7) failures++;
n_http2_server_free(&srv);
free(reply);

}

/* When the peer never reopens the window the write fails instead of hanging. / static void test_send_window_stall(void) { PEER p; SEEN seen; N_H2_SERVER srv; N_H2_HEADER req[4]; const size_t reply_len = 100000; unsigned char* reply = malloc(reply_len);

if (!reply) {
    failures++;
    return;
}
memset(reply, 'B', reply_len);
memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
seen.reply = reply;
seen.reply_len = reply_len;
seen.expect_send_fail = 1;
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";
peer_prologue(&p);
peer_headers(&p, 1, req, 4, 1);

srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
if (!n_http2_server_error(srv)) failures++;
{
    size_t sent = 0;
    out_count(&p, N_H2_DATA, &sent);
    if (sent != 65535) failures++; /* exactly the initial window, no more */
}
n_http2_server_free(&srv);
free(reply);

}

/* A reset stream is dropped, and its request never reaches the callback. / static void test_rst_stream(void) { PEER p; SEEN seen; N_H2_SERVER srv; N_H2_HEADER req[4]; unsigned char code[4] = {0, 0, 0, N_H2_ERR_CANCEL};

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";
peer_prologue(&p);
peer_headers(&p, 1, req, 4, 0);
peer_frame(&p, N_H2_RST_STREAM, 0, 1, code, sizeof(code));
peer_frame(&p, N_H2_DATA, N_H2_FLAG_END_STREAM, 1, (const unsigned char*)"late", 4);

srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
if (seen.calls != 0) failures++;
n_http2_server_free(&srv);

}

/* The callback can stop the connection by returning -1, and the server still says goodbye with a GOAWAY. / static void test_callback_stop(void) { PEER p; SEEN seen; N_H2_SERVER srv; N_H2_HEADER req[4];

memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
seen.stop_after = 1;
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";
peer_prologue(&p);
peer_headers(&p, 1, req, 4, 1);
peer_headers(&p, 3, req, 4, 1);

srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
if (seen.calls != 1) failures++;
if (out_count(&p, N_H2_GOAWAY, NULL) != 1) failures++;
n_http2_server_free(&srv);

}

/* The frame reader must not depend on how the bytes are cut up by the transport: the same script served one byte at a time behaves identically. / static void test_dribbled_reads(void) { PEER p; SEEN seen; N_H2_SERVER srv; N_H2_HEADER req[4];

memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
p.chunk = 1;
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";
peer_prologue(&p);
peer_headers(&p, 1, req, 4, 1);

srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != 0) failures++;
if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
if (seen.calls != 1 || strcmp(seen.path, "/dribble") != 0) failures++;
n_http2_server_free(&srv);

}

/* Guards: NULL arguments are refused, and a read error is reported. / static void test_guards(void) { PEER p; SEEN seen; N_H2_SERVER srv;

if (n_http2_server_new(NULL, NULL, peer_write) != NULL) failures++;
if (n_http2_server_new(NULL, peer_read, NULL) != NULL) failures++;
if (n_http2_server_handshake(NULL) != -1) failures++;
if (n_http2_server_error(NULL) != NULL) failures++;
if (n_http2_server_request_count(NULL) != 0) failures++;
n_http2_server_free(NULL); /* must not crash */

memset(&p, 0, sizeof(p));
memset(&seen, 0, sizeof(seen));
p.read_fail = 1;
srv = n_http2_server_new(&p, peer_read, peer_write);
if (n_http2_server_handshake(srv) != -1) failures++;
if (n_http2_server_run(srv, NULL, &seen) != -1) failures++;
n_http2_server_free(&srv);

}

void process_args(int argc, char** argv) { int opt = 0; while ((opt = getopt(argc, argv, "hvV:")) != EOF) { switch (opt) { case 'V': if (!strncmp("LOG_NULL", optarg, 8)) set_log_level(LOG_NULL); else if (!strncmp("LOG_NOTICE", optarg, 10)) set_log_level(LOG_NOTICE); else if (!strncmp("LOG_INFO", optarg, 8)) set_log_level(LOG_INFO); else if (!strncmp("LOG_ERR", optarg, 7)) set_log_level(LOG_ERR); else if (!strncmp("LOG_DEBUG", optarg, 9)) set_log_level(LOG_DEBUG); break; case 'v': fprintf(stderr, "ex_http2_server\n"); exit(1); default: break; } } }

int main(int argc, char** argv) { set_log_level(LOG_ERR); process_args(argc, argv);

/* run one check and name it when it fails, so a regression points at its case instead of at a single total */ #define RUNT(f)
do {
int before = failures;
f();
if (failures != before)
n_log(LOG_ERR, "%s: %d failure(s)", #f, failures - before);
} while (0)

RUNT(test_handshake);
RUNT(test_handshake_errors);
RUNT(test_get);
RUNT(test_post_body);
RUNT(test_continuation);
RUNT(test_multiplex);
RUNT(test_ping_goaway);
RUNT(test_send_flow_control);
RUNT(test_send_window_stall);
RUNT(test_rst_stream);
RUNT(test_callback_stop);
RUNT(test_dribbled_reads);
RUNT(test_guards);

if (failures) {
    n_log(LOG_ERR, "ex_http2_server: %d failure(s)", failures);
    return 1;
}
n_log(LOG_NOTICE, "ex_http2_server: all checks passed");
return 0;

}


_Filename: ex_http2_server.c_