Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_http2.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
27#include "nilorea/n_http2.h"
28#include "nilorea/n_log.h"
29
30#include <getopt.h>
31#include <stdlib.h>
32#include <string.h>
33
34static int failures = 0;
35
36/* Round-trip a frame header (build then parse) and check every field. */
37static void test_roundtrip(void) {
38 unsigned char buf[64];
39 N_H2_FRAME f;
40 size_t consumed = 0, w;
41
42 /* a HEADERS frame on stream 1 with END_HEADERS|END_STREAM and a payload */
43 w = n_http2_frame_build_header(buf, sizeof(buf), 5, N_H2_HEADERS,
46 memcpy(buf + w, "hello", 5); /* the header block (opaque here) */
47
48 if (n_http2_frame_parse(buf, w + 5, &f, &consumed) != 1) failures++;
49 if (f.length != 5 || f.type != N_H2_HEADERS) failures++;
51 if (f.stream_id != 1) failures++;
52 if (consumed != w + 5 || !f.payload || memcmp(f.payload, "hello", 5) != 0) failures++;
53
54 /* a 24-bit stream id and a zero-length frame (no payload pointer) */
55 w = n_http2_frame_build_header(buf, sizeof(buf), 0, N_H2_WINDOW_UPDATE, 0, 0x7FFFFFFFu);
56 if (n_http2_frame_parse(buf, w, &f, &consumed) != 1) failures++;
57 if (f.stream_id != 0x7FFFFFFFu || f.length != 0 || f.payload != NULL) failures++;
58 if (consumed != N_H2_FRAME_HEADER_LEN) failures++;
59
60 /* the reserved top bit of the stream id must be cleared on parse */
61 buf[5] = (unsigned char)(buf[5] | 0x80);
62 if (n_http2_frame_parse(buf, N_H2_FRAME_HEADER_LEN, &f, &consumed) != 1) failures++;
63 if (f.stream_id != 0x7FFFFFFFu) failures++;
64}
65
66/* A truncated buffer needs more; an over-large length is rejected. */
67static void test_boundaries(void) {
68 unsigned char buf[16];
69 N_H2_FRAME f;
70 size_t consumed = 0;
71
72 n_http2_frame_build_header(buf, sizeof(buf), 4, N_H2_DATA, 0, 3);
73 if (n_http2_frame_parse(buf, N_H2_FRAME_HEADER_LEN, &f, &consumed) != 0) failures++; /* payload missing */
74 if (n_http2_frame_parse(buf, 3, &f, &consumed) != 0) failures++; /* header incomplete */
75
76 /* a declared length beyond the max frame size is a protocol error */
77 buf[0] = 0xFF;
78 buf[1] = 0xFF;
79 buf[2] = 0xFF;
80 if (n_http2_frame_parse(buf, sizeof(buf), &f, &consumed) != -1) failures++;
81
82 /* guards */
83 if (n_http2_frame_parse(NULL, 9, &f, &consumed) != -1) failures++;
84 if (n_http2_frame_build_header(buf, 4, 0, N_H2_PING, 0, 0) != 0) failures++; /* out too small */
85 if (n_http2_frame_build_header(buf, sizeof(buf), 0x01000000u, N_H2_DATA, 0, 1) != 0) failures++; /* 25-bit length */
86}
87
88/* SETTINGS payload parses into id/value pairs. */
89static void test_settings(void) {
90 unsigned char pay[12];
91 N_H2_SETTING s[4];
92 size_t count = 0;
93
94 /* MAX_CONCURRENT_STREAMS = 100, INITIAL_WINDOW_SIZE = 65535 */
95 pay[0] = 0x00;
97 pay[2] = 0x00;
98 pay[3] = 0x00;
99 pay[4] = 0x00;
100 pay[5] = 100;
101 pay[6] = 0x00;
103 pay[8] = 0x00;
104 pay[9] = 0x00;
105 pay[10] = 0xFF;
106 pay[11] = 0xFF;
107
108 if (n_http2_settings_parse(pay, sizeof(pay), s, 4, &count) != 0) failures++;
109 if (count != 2) failures++;
110 if (s[0].id != N_H2_SETTINGS_MAX_CONCURRENT_STREAMS || s[0].value != 100) failures++;
111 if (s[1].id != N_H2_SETTINGS_INITIAL_WINDOW_SIZE || s[1].value != 65535) failures++;
112
113 /* an empty SETTINGS (an ACK carries none) is valid */
114 if (n_http2_settings_parse(NULL, 0, s, 4, &count) != 0 || count != 0) failures++;
115
116 /* a length not a multiple of 6 is a frame-size error */
117 if (n_http2_settings_parse(pay, 5, s, 4, &count) != -1) failures++;
118}
119
120static void test_preface(void) {
121 /* the client preface is a fixed 24-byte string */
122 if (strlen(N_H2_PREFACE) != 24) failures++;
123 if (memcmp(N_H2_PREFACE, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 24) != 0) failures++;
124}
125
126/* Return the value of the header named `name` in a decoded list, or NULL. */
127static const char* find_hdr(const N_H2_HEADER* h, size_t n, const char* name) {
128 size_t i;
129 for (i = 0; i < n; i++)
130 if (h[i].name && strcmp(h[i].name, name) == 0)
131 return h[i].value;
132 return NULL;
133}
134
135/* HPACK: encode a header list, decode it back, and check every field survives,
136 including a value long enough to force a multi-byte HPACK integer length. */
137static void test_hpack_roundtrip(void) {
138 char big[301];
139 N_H2_HEADER in[4];
140 N_H2_HEADER out[8];
141 unsigned char buf[1024];
142 size_t w, count = 0;
144
145 memset(big, 'x', 300);
146 big[300] = '\0';
147 in[0].name = (char*)":method";
148 in[0].value = (char*)"GET";
149 in[1].name = (char*)":path";
150 in[1].value = (char*)"/a/b?c=d";
151 in[2].name = (char*)"x-custom";
152 in[2].value = (char*)"hello world";
153 in[3].name = (char*)"x-big";
154 in[3].value = big;
155
156 w = n_http2_hpack_encode(in, 4, buf, sizeof(buf));
157 if (w == 0) failures++;
158 if (n_http2_hpack_decode(dec, buf, w, out, 8, &count) != 0) failures++;
159 if (count != 4) failures++;
160 if (!find_hdr(out, count, ":method") || strcmp(find_hdr(out, count, ":method"), "GET") != 0) failures++;
161 if (!find_hdr(out, count, ":path") || strcmp(find_hdr(out, count, ":path"), "/a/b?c=d") != 0) failures++;
162 if (!find_hdr(out, count, "x-custom") || strcmp(find_hdr(out, count, "x-custom"), "hello world") != 0) failures++;
163 if (!find_hdr(out, count, "x-big") || strlen(find_hdr(out, count, "x-big")) != 300) failures++;
164
165 n_http2_hpack_headers_free(out, count);
166 n_http2_hpack_free(&dec);
167}
168
169/* HPACK: the RFC 7541 C.3 request sequence (no Huffman) on one context, which
170 exercises indexed static fields and dynamic-table references across requests. */
171static void test_hpack_rfc_requests(void) {
172 /* C.3.1 */
173 static const unsigned char r1[] = {0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d};
174 /* C.3.2: 0xbe re-references :authority from the dynamic table */
175 static const unsigned char r2[] = {0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65};
176 /* C.3.3 */
177 static const unsigned char r3[] = {0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65};
178 N_H2_HEADER out[16];
179 size_t count = 0;
181
182 if (n_http2_hpack_decode(dec, r1, sizeof(r1), out, 16, &count) != 0) failures++;
183 if (count != 4) failures++;
184 if (!find_hdr(out, count, ":method") || strcmp(find_hdr(out, count, ":method"), "GET") != 0) failures++;
185 if (!find_hdr(out, count, ":authority") || strcmp(find_hdr(out, count, ":authority"), "www.example.com") != 0) failures++;
186 n_http2_hpack_headers_free(out, count);
187
188 if (n_http2_hpack_decode(dec, r2, sizeof(r2), out, 16, &count) != 0) failures++;
189 if (count != 5) failures++;
190 if (!find_hdr(out, count, ":authority") || strcmp(find_hdr(out, count, ":authority"), "www.example.com") != 0) failures++;
191 if (!find_hdr(out, count, "cache-control") || strcmp(find_hdr(out, count, "cache-control"), "no-cache") != 0) failures++;
192 n_http2_hpack_headers_free(out, count);
193
194 if (n_http2_hpack_decode(dec, r3, sizeof(r3), out, 16, &count) != 0) failures++;
195 if (count != 5) failures++;
196 if (!find_hdr(out, count, ":path") || strcmp(find_hdr(out, count, ":path"), "/index.html") != 0) failures++;
197 if (!find_hdr(out, count, "custom-key") || strcmp(find_hdr(out, count, "custom-key"), "custom-value") != 0) failures++;
198 n_http2_hpack_headers_free(out, count);
199
200 n_http2_hpack_free(&dec);
201}
202
203/* HPACK: RFC 7541 C.4.1 (Huffman-coded :authority) and C.6.1 (a Huffman-coded
204 response with a date and URL, table size 256), which validate Huffman decode. */
205static void test_hpack_rfc_huffman(void) {
206 static const unsigned char req[] = {0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff};
207 static const unsigned char resp[] = {
208 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 0x77, 0x1a, 0x4b, 0x61, 0x96, 0xd0, 0x7a, 0xbe,
209 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6,
210 0x2d, 0x1b, 0xff, 0x6e, 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8,
211 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3};
212 N_H2_HEADER out[16];
213 size_t count = 0;
215 N_H2_HPACK* ds = n_http2_hpack_new(256);
216
217 if (n_http2_hpack_decode(dr, req, sizeof(req), out, 16, &count) != 0) failures++;
218 if (!find_hdr(out, count, ":authority") || strcmp(find_hdr(out, count, ":authority"), "www.example.com") != 0) failures++;
219 n_http2_hpack_headers_free(out, count);
220
221 if (n_http2_hpack_decode(ds, resp, sizeof(resp), out, 16, &count) != 0) failures++;
222 if (count != 4) failures++;
223 if (!find_hdr(out, count, ":status") || strcmp(find_hdr(out, count, ":status"), "302") != 0) failures++;
224 if (!find_hdr(out, count, "cache-control") || strcmp(find_hdr(out, count, "cache-control"), "private") != 0) failures++;
225 if (!find_hdr(out, count, "date") || strcmp(find_hdr(out, count, "date"), "Mon, 21 Oct 2013 20:13:21 GMT") != 0) failures++;
226 if (!find_hdr(out, count, "location") || strcmp(find_hdr(out, count, "location"), "https://www.example.com") != 0) failures++;
227 n_http2_hpack_headers_free(out, count);
228
231}
232
233/* HPACK: a valid block with more fields than the caller's out[] capacity is
234 rejected cleanly (the decoder frees the in-flight field and any already
235 stored ones exactly once). ASan/LSan guards against a double free here. */
236static void test_hpack_overflow(void) {
237 /* RFC 7541 C.3.1 decodes to 4 header fields */
238 static const unsigned char r1[] = {0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d};
239 N_H2_HEADER out[2];
240 size_t count = 123;
242
243 /* out[] only holds 2, so the 3rd field overflows and the decode fails */
244 if (n_http2_hpack_decode(dec, r1, sizeof(r1), out, 2, &count) != -1) failures++;
245
246 n_http2_hpack_free(&dec);
247}
248
249/* HPACK: guards and a malformed block are rejected without a crash. */
250static void test_hpack_guards(void) {
251 static const unsigned char bad_index[] = {0x80}; /* indexed field, index 0 */
252 static const unsigned char truncated[] = {0x40, 0x05, 0x61}; /* literal name len 5 but 1 byte */
253 N_H2_HEADER out[4];
254 size_t count = 0;
256
257 if (n_http2_hpack_decode(dec, bad_index, sizeof(bad_index), out, 4, &count) != -1) failures++;
258 if (n_http2_hpack_decode(dec, truncated, sizeof(truncated), out, 4, &count) != -1) failures++;
259 if (n_http2_hpack_decode(NULL, bad_index, 1, out, 4, &count) != -1) failures++;
260
261 n_http2_hpack_free(&dec);
262}
263
264void process_args(int argc, char** argv) {
265 int opt = 0;
266 while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
267 switch (opt) {
268 case 'V':
269 if (!strncmp("LOG_NULL", optarg, 8))
271 else if (!strncmp("LOG_NOTICE", optarg, 10))
273 else if (!strncmp("LOG_INFO", optarg, 8))
275 else if (!strncmp("LOG_ERR", optarg, 7))
277 else if (!strncmp("LOG_DEBUG", optarg, 9))
279 break;
280 case 'v':
281 fprintf(stderr, "ex_http2\n");
282 exit(1);
283 default:
284 break;
285 }
286 }
287}
288
289int main(int argc, char** argv) {
291 process_args(argc, argv);
292
296 test_preface();
302
303 if (failures) {
304 n_log(LOG_ERR, "ex_http2: %d failure(s)", failures);
305 return 1;
306 }
307 n_log(LOG_NOTICE, "ex_http2: all checks passed");
308 return 0;
309}
static int failures
int main(void)
void process_args(int argc, char **argv)
Definition ex_common.c:48
static void test_hpack_rfc_huffman(void)
Definition ex_http2.c:205
static void test_hpack_roundtrip(void)
Definition ex_http2.c:137
static void test_roundtrip(void)
Definition ex_http2.c:37
static void test_settings(void)
Definition ex_http2.c:89
static const char * find_hdr(const N_H2_HEADER *h, size_t n, const char *name)
Definition ex_http2.c:127
static void test_preface(void)
Definition ex_http2.c:120
static void test_boundaries(void)
Definition ex_http2.c:67
static void test_hpack_guards(void)
Definition ex_http2.c:250
static void test_hpack_overflow(void)
Definition ex_http2.c:236
static void test_hpack_rfc_requests(void)
Definition ex_http2.c:171
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:89
#define LOG_DEBUG
debug-level messages
Definition n_log.h:84
#define LOG_ERR
error conditions
Definition n_log.h:76
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
Definition n_log.c:121
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:80
#define LOG_NULL
no log output
Definition n_log.h:46
#define LOG_INFO
informational
Definition n_log.h:82
int n_http2_hpack_decode(N_H2_HPACK *ctx, const unsigned char *block, size_t len, N_H2_HEADER *out, size_t max_out, size_t *count)
decode an HPACK header block into out[] (owned name/value strings).
Definition n_http2.c:472
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
N_H2_HPACK * n_http2_hpack_new(size_t max_table_size)
create an HPACK codec context with the given dynamic table size limit (0 uses N_H2_HPACK_DEFAULT_TABL...
Definition n_http2.c:353
void n_http2_hpack_free(N_H2_HPACK **pctx)
free an HPACK codec context and NULL the caller's pointer
Definition n_http2.c:375
void n_http2_hpack_headers_free(N_H2_HEADER *headers, size_t n)
free the owned name/value strings of a decoded header array (does not free the array itself)
Definition n_http2.c:460
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
HPACK codec context: the dynamic table (index 0 = most recently added)
Definition n_http2.c:182
HTTP/2 (RFC 7540) wire framing plus HPACK header compression (RFC 7541)
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_FRAME_HEADER_LEN
length of the fixed frame header in bytes
Definition n_http2.h:53
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
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
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_SETTINGS_MAX_CONCURRENT_STREAMS
Definition n_http2.h:84
#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
Generic log system.