Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_http2_server.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
32
34#include "nilorea/n_log.h"
35
36#include <getopt.h>
37#include <stdlib.h>
38#include <string.h>
39
40static int failures = 0;
41
42/* ------------------------------------------------------------------ */
43/* the scripted peer */
44/* ------------------------------------------------------------------ */
45
46/* A byte script the server reads from, plus the bytes it wrote back. */
47typedef struct PEER {
48 unsigned char in[65536]; /* what the client sends */
49 size_t in_len; /* its length */
50 size_t in_off; /* how much the server has consumed */
51 unsigned char out[262144]; /* what the server sent */
52 size_t out_len; /* its length */
53 size_t chunk; /* max bytes served per read (0 = no limit) */
54 int read_fail; /* return -1 from the read callback */
55} PEER;
56
57static ssize_t peer_read(void* io, void* buf, size_t len) {
58 PEER* p = (PEER*)io;
59 size_t n = p->in_len - p->in_off;
60 if (p->read_fail)
61 return -1;
62 if (n == 0)
63 return 0; /* end of the script: a clean peer close */
64 if (n > len)
65 n = len;
66 if (p->chunk && n > p->chunk)
67 n = p->chunk;
68 memcpy(buf, p->in + p->in_off, n);
69 p->in_off += n;
70 return (ssize_t)n;
71}
72
73static ssize_t peer_write(void* io, const void* buf, size_t len) {
74 PEER* p = (PEER*)io;
75 if (p->out_len + len > sizeof(p->out))
76 return -1;
77 memcpy(p->out + p->out_len, buf, len);
78 p->out_len += len;
79 return (ssize_t)len;
80}
81
82/* Append raw bytes to the client script. */
83static void peer_put(PEER* p, const void* buf, size_t len) {
84 if (p->in_len + len > sizeof(p->in)) {
85 failures++;
86 return;
87 }
88 memcpy(p->in + p->in_len, buf, len);
89 p->in_len += len;
90}
91
92/* Append one frame (header then payload) to the client script. */
93static void peer_frame(PEER* p, int type, int flags, uint32_t sid, const unsigned char* payload, size_t len) {
94 unsigned char hdr[N_H2_FRAME_HEADER_LEN];
95 n_http2_frame_build_header(hdr, sizeof(hdr), (uint32_t)len, type, flags, sid);
96 peer_put(p, hdr, sizeof(hdr));
97 if (len)
98 peer_put(p, payload, len);
99}
100
101/* Append the client preface and an empty SETTINGS. */
102static void peer_prologue(PEER* p) {
104 peer_frame(p, N_H2_SETTINGS, 0, 0, NULL, 0);
105}
106
107/* Append a HEADERS frame carrying an HPACK-encoded header list. */
108static void peer_headers(PEER* p, uint32_t sid, const N_H2_HEADER* h, size_t n, int end_stream) {
109 unsigned char block[4096];
110 size_t w = n_http2_hpack_encode(h, n, block, sizeof(block));
111 int flags = N_H2_FLAG_END_HEADERS | (end_stream ? N_H2_FLAG_END_STREAM : 0);
112 if (w == 0) {
113 failures++;
114 return;
115 }
116 peer_frame(p, N_H2_HEADERS, flags, sid, block, w);
117}
118
119/* Append a WINDOW_UPDATE frame. */
120static void peer_window_update(PEER* p, uint32_t sid, uint32_t inc) {
121 unsigned char b[4];
122 b[0] = (unsigned char)((inc >> 24) & 0x7F);
123 b[1] = (unsigned char)((inc >> 16) & 0xFF);
124 b[2] = (unsigned char)((inc >> 8) & 0xFF);
125 b[3] = (unsigned char)(inc & 0xFF);
126 peer_frame(p, N_H2_WINDOW_UPDATE, 0, sid, b, sizeof(b));
127}
128
129/* Count the frames of one type the server sent, and sum their payload lengths. */
130static size_t out_count(const PEER* p, int type, size_t* total_payload) {
131 size_t off = 0, count = 0;
132 if (total_payload)
133 *total_payload = 0;
134 while (off < p->out_len) {
135 N_H2_FRAME f;
136 size_t consumed = 0;
137 if (n_http2_frame_parse(p->out + off, p->out_len - off, &f, &consumed) != 1)
138 break;
139 if (f.type == type) {
140 count++;
141 if (total_payload)
142 *total_payload += f.length;
143 }
144 off += consumed;
145 }
146 return count;
147}
148
149/* Find the nth frame of a type the server sent. Returns 1 when found. */
150static int out_find(const PEER* p, int type, size_t nth, N_H2_FRAME* out) {
151 size_t off = 0, seen = 0;
152 while (off < p->out_len) {
153 N_H2_FRAME f;
154 size_t consumed = 0;
155 if (n_http2_frame_parse(p->out + off, p->out_len - off, &f, &consumed) != 1)
156 break;
157 if (f.type == type && seen++ == nth) {
158 *out = f;
159 return 1;
160 }
161 off += consumed;
162 }
163 return 0;
164}
165
166/* ------------------------------------------------------------------ */
167/* the handler */
168/* ------------------------------------------------------------------ */
169
170/* What the request callback recorded, so the checks can look at it. */
171typedef struct SEEN {
172 int calls;
173 char method[32];
174 char path[256];
175 char authority[128];
176 char scheme[16];
177 char body[4096];
178 size_t body_len;
179 size_t reply_len; /* bytes of body to answer with */
180 int reply_status; /* :status to answer with */
181 int stop_after; /* return -1 from the callback after this many calls */
182 int expect_send_fail; /* the response is expected NOT to go out in full */
183 unsigned char* reply; /* the reply body (owned by the test) */
184} SEEN;
185
186static int on_request(void* ud, N_H2_SERVER* srv, const N_H2_REQUEST* req) {
187 SEEN* s = (SEEN*)ud;
188 N_H2_HEADER h[2];
189 char status[8];
190 s->calls++;
191 snprintf(s->method, sizeof(s->method), "%s", req->method ? req->method : "");
192 snprintf(s->path, sizeof(s->path), "%s", req->path ? req->path : "");
193 snprintf(s->authority, sizeof(s->authority), "%s", req->authority ? req->authority : "");
194 snprintf(s->scheme, sizeof(s->scheme), "%s", req->scheme ? req->scheme : "");
195 s->body_len = req->body_len < sizeof(s->body) ? req->body_len : sizeof(s->body) - 1;
196 if (req->body && s->body_len)
197 memcpy(s->body, req->body, s->body_len);
198 s->body[s->body_len] = '\0';
199
200 snprintf(status, sizeof(status), "%d", s->reply_status ? s->reply_status : 200);
201 h[0].name = (char*)":status";
202 h[0].value = status;
203 h[1].name = (char*)"content-type";
204 h[1].value = (char*)"text/plain";
205 if ((n_http2_server_respond(srv, req->stream_id, h, 2, s->reply, s->reply_len) != 0) != (s->expect_send_fail != 0))
206 failures++;
207 return (s->stop_after && s->calls >= s->stop_after) ? -1 : 0;
208}
209
210/* ------------------------------------------------------------------ */
211/* checks */
212/* ------------------------------------------------------------------ */
213
214/* The handshake reads the preface, answers with SETTINGS plus a connection
215 WINDOW_UPDATE, and acknowledges the client's SETTINGS. */
216static void test_handshake(void) {
217 PEER p;
218 N_H2_SERVER* srv;
219 N_H2_FRAME f;
220
221 memset(&p, 0, sizeof(p));
222 peer_prologue(&p);
224 if (!srv) {
225 failures++;
226 return;
227 }
228 if (n_http2_server_handshake(srv) != 0) failures++;
229 if (out_count(&p, N_H2_SETTINGS, NULL) != 2) failures++; /* ours plus the ACK */
230 if (out_count(&p, N_H2_WINDOW_UPDATE, NULL) != 1) failures++; /* the connection window */
231 if (!out_find(&p, N_H2_SETTINGS, 0, &f) || f.flags != 0) failures++;
232 if (!out_find(&p, N_H2_SETTINGS, 1, &f) || !(f.flags & N_H2_FLAG_ACK)) failures++;
233 /* our SETTINGS must announce a stream receive window, or a body over 64KB stalls */
234 if (out_find(&p, N_H2_SETTINGS, 0, &f)) {
235 N_H2_SETTING set[8];
236 size_t n = 0, i;
237 int seen_win = 0;
238 if (n_http2_settings_parse(f.payload, f.length, set, 8, &n) != 0) failures++;
239 for (i = 0; i < n; i++)
240 if (set[i].id == N_H2_SETTINGS_INITIAL_WINDOW_SIZE && set[i].value > 65535)
241 seen_win = 1;
242 if (!seen_win) failures++;
243 }
245 if (srv) failures++;
246}
247
248/* A bad preface, and a first frame that is not SETTINGS, are both refused. */
249static void test_handshake_errors(void) {
250 PEER p;
251 N_H2_SERVER* srv;
252
253 memset(&p, 0, sizeof(p));
254 peer_put(&p, "GET / HTTP/1.1\r\nHost: x\r\n\r\n\r\n\r\n\r\n", 33);
256 if (n_http2_server_handshake(srv) != -1) failures++;
257 if (!n_http2_server_error(srv)) failures++;
259
260 memset(&p, 0, sizeof(p));
261 peer_put(&p, N_H2_PREFACE, strlen(N_H2_PREFACE));
262 peer_frame(&p, N_H2_PING, 0, 0, (const unsigned char*)"12345678", 8);
264 if (n_http2_server_handshake(srv) != -1) failures++;
266
267 /* a truncated preface is an error, not a hang */
268 memset(&p, 0, sizeof(p));
269 peer_put(&p, N_H2_PREFACE, 10);
271 if (n_http2_server_handshake(srv) != -1) failures++;
273}
274
275/* One GET: the pseudo-headers reach the callback and the response comes back as
276 HEADERS with END_STREAM plus one DATA frame. */
277static void test_get(void) {
278 PEER p;
279 SEEN seen;
280 N_H2_SERVER* srv;
281 N_H2_FRAME f;
282 N_H2_HEADER req[4];
283 unsigned char body[] = "hello h2";
284
285 memset(&p, 0, sizeof(p));
286 memset(&seen, 0, sizeof(seen));
287 seen.reply = body;
288 seen.reply_len = sizeof(body) - 1;
289 req[0].name = (char*)":method";
290 req[0].value = (char*)"GET";
291 req[1].name = (char*)":scheme";
292 req[1].value = (char*)"https";
293 req[2].name = (char*)":authority";
294 req[2].value = (char*)"example.test";
295 req[3].name = (char*)":path";
296 req[3].value = (char*)"/a?b=c";
297 peer_prologue(&p);
298 peer_headers(&p, 1, req, 4, 1);
299
301 if (n_http2_server_handshake(srv) != 0) failures++;
302 if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
303 if (seen.calls != 1) failures++;
304 if (strcmp(seen.method, "GET") != 0) failures++;
305 if (strcmp(seen.path, "/a?b=c") != 0) failures++;
306 if (strcmp(seen.authority, "example.test") != 0) failures++;
307 if (strcmp(seen.scheme, "https") != 0) failures++;
308 if (seen.body_len != 0) failures++;
309 if (n_http2_server_request_count(srv) != 1) failures++;
310
311 /* the response: HEADERS on stream 1, then DATA with END_STREAM */
312 if (!out_find(&p, N_H2_HEADERS, 0, &f) || f.stream_id != 1) failures++;
314 if (f.flags & N_H2_FLAG_END_STREAM) failures++; /* a body follows */
315 if (!out_find(&p, N_H2_DATA, 0, &f) || f.stream_id != 1) failures++;
316 if (!(f.flags & N_H2_FLAG_END_STREAM)) failures++;
317 if (f.length != sizeof(body) - 1 || memcmp(f.payload, body, f.length) != 0) failures++;
318 if (out_count(&p, N_H2_GOAWAY, NULL) != 1) failures++;
320}
321
322/* A POST body split over several DATA frames is reassembled, and every byte is
323 given back to the connection window (including the END_STREAM frame's). */
324static void test_post_body(void) {
325 PEER p;
326 SEEN seen;
327 N_H2_SERVER* srv;
328 N_H2_HEADER req[5];
329 size_t given = 0;
330
331 memset(&p, 0, sizeof(p));
332 memset(&seen, 0, sizeof(seen));
333 req[0].name = (char*)":method";
334 req[0].value = (char*)"POST";
335 req[1].name = (char*)":scheme";
336 req[1].value = (char*)"https";
337 req[2].name = (char*)":authority";
338 req[2].value = (char*)"example.test";
339 req[3].name = (char*)":path";
340 req[3].value = (char*)"/submit";
341 req[4].name = (char*)"content-type";
342 req[4].value = (char*)"application/x-www-form-urlencoded";
343 peer_prologue(&p);
344 peer_headers(&p, 1, req, 5, 0);
345 peer_frame(&p, N_H2_DATA, 0, 1, (const unsigned char*)"user=admin", 10);
346 peer_frame(&p, N_H2_DATA, N_H2_FLAG_END_STREAM, 1, (const unsigned char*)"&pass=x", 7);
347
349 if (n_http2_server_handshake(srv) != 0) failures++;
350 if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
351 if (seen.calls != 1) failures++;
352 if (strcmp(seen.method, "POST") != 0) failures++;
353 if (seen.body_len != 17 || strcmp(seen.body, "user=admin&pass=x") != 0) failures++;
354
355 /* the connection window must be replenished by all 17 body bytes: dropping
356 the last frame's 10+7 would starve a long-lived connection one request at
357 a time, which only shows up after many requests */
358 out_count(&p, N_H2_WINDOW_UPDATE, &given);
359 {
360 size_t off = 0, conn_inc = 0;
361 while (off < p.out_len) {
362 N_H2_FRAME f;
363 size_t consumed = 0;
364 if (n_http2_frame_parse(p.out + off, p.out_len - off, &f, &consumed) != 1)
365 break;
366 if (f.type == N_H2_WINDOW_UPDATE && f.stream_id == 0 && f.length == 4) {
367 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];
368 if (inc < 1000) /* skip the big handshake update */
369 conn_inc += inc;
370 }
371 off += consumed;
372 }
373 if (conn_inc != 17) failures++;
374 }
376}
377
378/* A header block split into CONTINUATION frames is reassembled; a frame
379 interleaved inside the block is a connection error. */
380static void test_continuation(void) {
381 PEER p;
382 SEEN seen;
383 N_H2_SERVER* srv;
384 N_H2_HEADER req[4];
385 unsigned char block[4096];
386 size_t w, half;
387
388 req[0].name = (char*)":method";
389 req[0].value = (char*)"GET";
390 req[1].name = (char*)":scheme";
391 req[1].value = (char*)"https";
392 req[2].name = (char*)":authority";
393 req[2].value = (char*)"example.test";
394 req[3].name = (char*)":path";
395 req[3].value = (char*)"/split";
396 w = n_http2_hpack_encode(req, 4, block, sizeof(block));
397 if (w < 4) {
398 failures++;
399 return;
400 }
401 half = w / 2;
402
403 memset(&p, 0, sizeof(p));
404 memset(&seen, 0, sizeof(seen));
405 peer_prologue(&p);
406 peer_frame(&p, N_H2_HEADERS, N_H2_FLAG_END_STREAM, 1, block, half);
407 peer_frame(&p, N_H2_CONTINUATION, N_H2_FLAG_END_HEADERS, 1, block + half, w - half);
409 if (n_http2_server_handshake(srv) != 0) failures++;
410 if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
411 if (seen.calls != 1 || strcmp(seen.path, "/split") != 0) failures++;
413
414 /* a PING between the HEADERS and its CONTINUATION leaves HPACK undefined:
415 the connection must fail rather than decode a guess */
416 memset(&p, 0, sizeof(p));
417 memset(&seen, 0, sizeof(seen));
418 peer_prologue(&p);
419 peer_frame(&p, N_H2_HEADERS, N_H2_FLAG_END_STREAM, 1, block, half);
420 peer_frame(&p, N_H2_PING, 0, 0, (const unsigned char*)"12345678", 8);
421 peer_frame(&p, N_H2_CONTINUATION, N_H2_FLAG_END_HEADERS, 1, block + half, w - half);
423 if (n_http2_server_handshake(srv) != 0) failures++;
424 if (n_http2_server_run(srv, on_request, &seen) == 0) failures++; /* a connection error */
425 if (seen.calls != 0) failures++;
426 if (!n_http2_server_error(srv)) failures++;
428
429 /* a CONTINUATION for another stream is the same class of error */
430 memset(&p, 0, sizeof(p));
431 memset(&seen, 0, sizeof(seen));
432 peer_prologue(&p);
433 peer_frame(&p, N_H2_HEADERS, N_H2_FLAG_END_STREAM, 1, block, half);
434 peer_frame(&p, N_H2_CONTINUATION, N_H2_FLAG_END_HEADERS, 3, block + half, w - half);
436 if (n_http2_server_handshake(srv) != 0) failures++;
437 if (n_http2_server_run(srv, on_request, &seen) == 0) failures++;
439}
440
441/* Several streams on one connection are served in the order they complete. */
442static void test_multiplex(void) {
443 PEER p;
444 SEEN seen;
445 N_H2_SERVER* srv;
446 N_H2_HEADER req[4];
447 N_H2_FRAME f;
448
449 memset(&p, 0, sizeof(p));
450 memset(&seen, 0, sizeof(seen));
451 req[0].name = (char*)":method";
452 req[0].value = (char*)"GET";
453 req[1].name = (char*)":scheme";
454 req[1].value = (char*)"https";
455 req[2].name = (char*)":authority";
456 req[2].value = (char*)"example.test";
457 req[3].name = (char*)":path";
458 req[3].value = (char*)"/one";
459 peer_prologue(&p);
460 peer_headers(&p, 1, req, 4, 1);
461 req[3].value = (char*)"/two";
462 peer_headers(&p, 3, req, 4, 1);
463 req[3].value = (char*)"/three";
464 peer_headers(&p, 5, req, 4, 1);
465
467 if (n_http2_server_handshake(srv) != 0) failures++;
468 if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
469 if (seen.calls != 3) failures++;
470 if (strcmp(seen.path, "/three") != 0) failures++; /* the last one served */
471 if (out_count(&p, N_H2_HEADERS, NULL) != 3) failures++;
472 if (!out_find(&p, N_H2_HEADERS, 0, &f) || f.stream_id != 1) failures++;
473 if (!out_find(&p, N_H2_HEADERS, 1, &f) || f.stream_id != 3) failures++;
474 if (!out_find(&p, N_H2_HEADERS, 2, &f) || f.stream_id != 5) failures++;
476}
477
478/* PING is answered with a PING ACK carrying the same payload; a client GOAWAY
479 ends the run. */
480static void test_ping_goaway(void) {
481 PEER p;
482 SEEN seen;
483 N_H2_SERVER* srv;
484 N_H2_FRAME f;
485 unsigned char goaway[8];
486
487 memset(&p, 0, sizeof(p));
488 memset(&seen, 0, sizeof(seen));
489 memset(goaway, 0, sizeof(goaway));
490 peer_prologue(&p);
491 peer_frame(&p, N_H2_PING, 0, 0, (const unsigned char*)"abcdefgh", 8);
492 peer_frame(&p, N_H2_GOAWAY, 0, 0, goaway, sizeof(goaway));
494 if (n_http2_server_handshake(srv) != 0) failures++;
495 if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
496 if (!out_find(&p, N_H2_PING, 0, &f)) failures++;
497 if (!(f.flags & N_H2_FLAG_ACK) || f.length != 8 || memcmp(f.payload, "abcdefgh", 8) != 0) failures++;
498 if (seen.calls != 0) failures++;
500}
501
502/* A response larger than the peer's initial 65535-byte window is written only as
503 far as the window allows, then continues once a WINDOW_UPDATE arrives. This is
504 the check a server that ignores send-side flow control fails by hanging. */
505static void test_send_flow_control(void) {
506 PEER p;
507 SEEN seen;
508 N_H2_SERVER* srv;
509 N_H2_HEADER req[4];
510 size_t data_bytes = 0;
511 const size_t reply_len = 100000;
512 unsigned char* reply = malloc(reply_len);
513
514 if (!reply) {
515 failures++;
516 return;
517 }
518 memset(reply, 'A', reply_len);
519 memset(&p, 0, sizeof(p));
520 memset(&seen, 0, sizeof(seen));
521 seen.reply = reply;
522 seen.reply_len = reply_len;
523 req[0].name = (char*)":method";
524 req[0].value = (char*)"GET";
525 req[1].name = (char*)":scheme";
526 req[1].value = (char*)"https";
527 req[2].name = (char*)":authority";
528 req[2].value = (char*)"example.test";
529 req[3].name = (char*)":path";
530 req[3].value = (char*)"/big";
531 peer_prologue(&p);
532 peer_headers(&p, 1, req, 4, 1);
533 /* the client reopens both windows, but only after the request: the server
534 must park mid-body and resume, not deadlock and not overshoot */
535 peer_window_update(&p, 0, 100000);
536 peer_window_update(&p, 1, 100000);
537
539 if (n_http2_server_handshake(srv) != 0) failures++;
540 if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
541 out_count(&p, N_H2_DATA, &data_bytes);
542 if (data_bytes != reply_len) failures++;
543 /* 100000 bytes at the default 16384 max frame size is 7 DATA frames */
544 if (out_count(&p, N_H2_DATA, NULL) != 7) failures++;
546 free(reply);
547}
548
549/* When the peer never reopens the window the write fails instead of hanging. */
550static void test_send_window_stall(void) {
551 PEER p;
552 SEEN seen;
553 N_H2_SERVER* srv;
554 N_H2_HEADER req[4];
555 const size_t reply_len = 100000;
556 unsigned char* reply = malloc(reply_len);
557
558 if (!reply) {
559 failures++;
560 return;
561 }
562 memset(reply, 'B', reply_len);
563 memset(&p, 0, sizeof(p));
564 memset(&seen, 0, sizeof(seen));
565 seen.reply = reply;
566 seen.reply_len = reply_len;
567 seen.expect_send_fail = 1;
568 req[0].name = (char*)":method";
569 req[0].value = (char*)"GET";
570 req[1].name = (char*)":scheme";
571 req[1].value = (char*)"https";
572 req[2].name = (char*)":authority";
573 req[2].value = (char*)"example.test";
574 req[3].name = (char*)":path";
575 req[3].value = (char*)"/big";
576 peer_prologue(&p);
577 peer_headers(&p, 1, req, 4, 1);
578
580 if (n_http2_server_handshake(srv) != 0) failures++;
581 if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
582 if (!n_http2_server_error(srv)) failures++;
583 {
584 size_t sent = 0;
585 out_count(&p, N_H2_DATA, &sent);
586 if (sent != 65535) failures++; /* exactly the initial window, no more */
587 }
589 free(reply);
590}
591
592/* A reset stream is dropped, and its request never reaches the callback. */
593static void test_rst_stream(void) {
594 PEER p;
595 SEEN seen;
596 N_H2_SERVER* srv;
597 N_H2_HEADER req[4];
598 unsigned char code[4] = {0, 0, 0, N_H2_ERR_CANCEL};
599
600 memset(&p, 0, sizeof(p));
601 memset(&seen, 0, sizeof(seen));
602 req[0].name = (char*)":method";
603 req[0].value = (char*)"GET";
604 req[1].name = (char*)":scheme";
605 req[1].value = (char*)"https";
606 req[2].name = (char*)":authority";
607 req[2].value = (char*)"example.test";
608 req[3].name = (char*)":path";
609 req[3].value = (char*)"/gone";
610 peer_prologue(&p);
611 peer_headers(&p, 1, req, 4, 0);
612 peer_frame(&p, N_H2_RST_STREAM, 0, 1, code, sizeof(code));
613 peer_frame(&p, N_H2_DATA, N_H2_FLAG_END_STREAM, 1, (const unsigned char*)"late", 4);
614
616 if (n_http2_server_handshake(srv) != 0) failures++;
617 if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
618 if (seen.calls != 0) failures++;
620}
621
622/* The callback can stop the connection by returning -1, and the server still
623 says goodbye with a GOAWAY. */
624static void test_callback_stop(void) {
625 PEER p;
626 SEEN seen;
627 N_H2_SERVER* srv;
628 N_H2_HEADER req[4];
629
630 memset(&p, 0, sizeof(p));
631 memset(&seen, 0, sizeof(seen));
632 seen.stop_after = 1;
633 req[0].name = (char*)":method";
634 req[0].value = (char*)"GET";
635 req[1].name = (char*)":scheme";
636 req[1].value = (char*)"https";
637 req[2].name = (char*)":authority";
638 req[2].value = (char*)"example.test";
639 req[3].name = (char*)":path";
640 req[3].value = (char*)"/one";
641 peer_prologue(&p);
642 peer_headers(&p, 1, req, 4, 1);
643 peer_headers(&p, 3, req, 4, 1);
644
646 if (n_http2_server_handshake(srv) != 0) failures++;
647 if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
648 if (seen.calls != 1) failures++;
649 if (out_count(&p, N_H2_GOAWAY, NULL) != 1) failures++;
651}
652
653/* The frame reader must not depend on how the bytes are cut up by the transport:
654 the same script served one byte at a time behaves identically. */
655static void test_dribbled_reads(void) {
656 PEER p;
657 SEEN seen;
658 N_H2_SERVER* srv;
659 N_H2_HEADER req[4];
660
661 memset(&p, 0, sizeof(p));
662 memset(&seen, 0, sizeof(seen));
663 p.chunk = 1;
664 req[0].name = (char*)":method";
665 req[0].value = (char*)"GET";
666 req[1].name = (char*)":scheme";
667 req[1].value = (char*)"https";
668 req[2].name = (char*)":authority";
669 req[2].value = (char*)"example.test";
670 req[3].name = (char*)":path";
671 req[3].value = (char*)"/dribble";
672 peer_prologue(&p);
673 peer_headers(&p, 1, req, 4, 1);
674
676 if (n_http2_server_handshake(srv) != 0) failures++;
677 if (n_http2_server_run(srv, on_request, &seen) != 0) failures++;
678 if (seen.calls != 1 || strcmp(seen.path, "/dribble") != 0) failures++;
680}
681
682/* Guards: NULL arguments are refused, and a read error is reported. */
683static void test_guards(void) {
684 PEER p;
685 SEEN seen;
686 N_H2_SERVER* srv;
687
688 if (n_http2_server_new(NULL, NULL, peer_write) != NULL) failures++;
689 if (n_http2_server_new(NULL, peer_read, NULL) != NULL) failures++;
690 if (n_http2_server_handshake(NULL) != -1) failures++;
691 if (n_http2_server_error(NULL) != NULL) failures++;
692 if (n_http2_server_request_count(NULL) != 0) failures++;
693 n_http2_server_free(NULL); /* must not crash */
694
695 memset(&p, 0, sizeof(p));
696 memset(&seen, 0, sizeof(seen));
697 p.read_fail = 1;
699 if (n_http2_server_handshake(srv) != -1) failures++;
700 if (n_http2_server_run(srv, NULL, &seen) != -1) failures++;
702}
703
704void process_args(int argc, char** argv) {
705 int opt = 0;
706 while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
707 switch (opt) {
708 case 'V':
709 if (!strncmp("LOG_NULL", optarg, 8))
711 else if (!strncmp("LOG_NOTICE", optarg, 10))
713 else if (!strncmp("LOG_INFO", optarg, 8))
715 else if (!strncmp("LOG_ERR", optarg, 7))
717 else if (!strncmp("LOG_DEBUG", optarg, 9))
719 break;
720 case 'v':
721 fprintf(stderr, "ex_http2_server\n");
722 exit(1);
723 default:
724 break;
725 }
726 }
727}
728
729int main(int argc, char** argv) {
731 process_args(argc, argv);
732
733/* run one check and name it when it fails, so a regression points at its case
734 instead of at a single total */
735#define RUNT(f) \
736 do { \
737 int before = failures; \
738 f(); \
739 if (failures != before) \
740 n_log(LOG_ERR, "%s: %d failure(s)", #f, failures - before); \
741 } while (0)
742
745 RUNT(test_get);
756
757 if (failures) {
758 n_log(LOG_ERR, "ex_http2_server: %d failure(s)", failures);
759 return 1;
760 }
761 n_log(LOG_NOTICE, "ex_http2_server: all checks passed");
762 return 0;
763}
void process_args(int argc, char **argv)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
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)
size_t in_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)
unsigned char in[65536]
char method[32]
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]
size_t out_len
static void test_callback_stop(void)
static void test_handshake_errors(void)
size_t in_off
static void test_post_body(void)
static ssize_t peer_write(void *io, const void *buf, size_t len)
size_t body_len
char body[4096]
static void test_guards(void)
static ssize_t peer_read(void *io, void *buf, size_t len)
int stop_after
size_t reply_len
int expect_send_fail
static void test_continuation(void)
static void test_rst_stream(void)
static void peer_prologue(PEER *p)
static void test_handshake(void)
size_t chunk
char scheme[16]
static void test_get(void)
char authority[128]
int reply_status
char path[256]
unsigned char * reply
int read_fail
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 RUNT(f)
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:104
#define LOG_NULL
no log output
Definition n_log.h:70
#define LOG_INFO
informational
Definition n_log.h:106
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 ...
Definition n_http2.c:554
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.
Definition n_http2.c:70
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).
Definition n_http2.c:32
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.
Definition n_http2.c:52
char * value
header field value (owned after decode)
Definition n_http2.h:125
#define N_H2_PREFACE
the client connection preface sent before any HTTP/2 frame (RFC 7540 3.5)
Definition n_http2.h:51
#define N_H2_FLAG_END_HEADERS
HEADERS/PUSH_PROMISE/CONTINUATION: header block complete.
Definition n_http2.h:75
#define N_H2_RST_STREAM
RST_STREAM.
Definition n_http2.h:62
#define N_H2_FRAME_HEADER_LEN
length of the fixed frame header in bytes
Definition n_http2.h:53
#define N_H2_FLAG_ACK
SETTINGS/PING: acknowledgement.
Definition n_http2.h:74
char * name
header field name (owned after decode)
Definition n_http2.h:124
#define N_H2_SETTINGS_INITIAL_WINDOW_SIZE
Definition n_http2.h:85
#define N_H2_WINDOW_UPDATE
WINDOW_UPDATE.
Definition n_http2.h:67
#define N_H2_DATA
DATA.
Definition n_http2.h:59
uint32_t length
payload length (24-bit)
Definition n_http2.h:92
#define N_H2_CONTINUATION
CONTINUATION.
Definition n_http2.h:68
const unsigned char * payload
pointer into the input buffer (may be NULL when length is 0)
Definition n_http2.h:96
int type
frame type (N_H2_*)
Definition n_http2.h:93
int flags
frame flags
Definition n_http2.h:94
#define N_H2_SETTINGS
SETTINGS.
Definition n_http2.h:63
uint32_t stream_id
stream identifier (31-bit; the reserved bit is cleared)
Definition n_http2.h:95
#define N_H2_HEADERS
HEADERS.
Definition n_http2.h:60
#define N_H2_FLAG_END_STREAM
DATA/HEADERS: last frame of the stream.
Definition n_http2.h:73
#define N_H2_GOAWAY
GOAWAY.
Definition n_http2.h:66
#define N_H2_PING
PING.
Definition n_http2.h:65
a parsed HTTP/2 frame; payload aliases the input buffer
Definition n_http2.h:91
one HPACK header field; after a decode both strings are owned (heap)
Definition n_http2.h:123
one SETTINGS parameter (identifier and value)
Definition n_http2.h:100
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
server connection state
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...
Generic log system.