Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_network_alpn.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
34
35#include "nilorea/n_common.h"
37#include "nilorea/n_log.h"
38#include "nilorea/n_network.h"
39#include "nilorea/n_str.h"
40#include "nilorea/n_x509.h"
41
42#include <getopt.h>
43#include <pthread.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47#include <unistd.h>
48
49#include <openssl/ssl.h>
50
52#define ALPN_HOST "alpn.host.example"
54#define ROUNDS 3
56#define H2_REPLY "served-over-h2"
57
58static int failures = 0;
59
61typedef struct CA_CTX {
64} CA_CTX;
65
67typedef struct SRV {
70 char selected[ROUNDS][32];
73 char h2_path[128];
74} SRV;
75
76/* Parse the -V LOG_LEVEL verbosity argument. */
77void process_args(int argc, char** argv) {
78 int opt = 0;
79 while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
80 switch (opt) {
81 case 'V':
82 if (!strncmp("LOG_NULL", optarg, 8))
84 else if (!strncmp("LOG_NOTICE", optarg, 10))
86 else if (!strncmp("LOG_INFO", optarg, 8))
88 else if (!strncmp("LOG_ERR", optarg, 7))
90 else if (!strncmp("LOG_DEBUG", optarg, 9))
92 break;
93 case 'v':
94 fprintf(stderr, "ex_network_alpn\n");
95 exit(1);
96 case 'h':
97 default:
98 fprintf(stderr, "usage: %s [-V LOG_LEVEL]\n", argv[0]);
99 break;
100 }
101 }
102}
103
104/* SNI pick callback: mint a leaf for the requested host signed by the CA. */
105static int pick_cb(const char* sni, N_STR** cert_pem, N_STR** key_pem, void* user_data) {
106 CA_CTX* ca = (CA_CTX*)user_data;
107 const char* host = (sni && sni[0]) ? sni : "localhost";
108 return n_x509_mint_host_cert(host, ca->cert, ca->key, 825, cert_pem, key_pem);
109}
110
111/* ------------------------------------------------------------------ */
112/* byte I/O over a TLS NETWORK, for the HTTP/2 state machine */
113/* ------------------------------------------------------------------ */
114
115static ssize_t tls_read(void* io, void* buf, size_t len) {
116 NETWORK* n = (NETWORK*)io;
117 int r = SSL_read(n->ssl, buf, (int)len);
118 return (r < 0) ? -1 : (ssize_t)r;
119}
120
121static ssize_t tls_write(void* io, const void* buf, size_t len) {
122 NETWORK* n = (NETWORK*)io;
123 size_t off = 0;
124 while (off < len) {
125 int w = SSL_write(n->ssl, (const char*)buf + off, (int)(len - off));
126 if (w <= 0)
127 return -1;
128 off += (size_t)w;
129 }
130 return (ssize_t)len;
131}
132
133/* ------------------------------------------------------------------ */
134/* the server */
135/* ------------------------------------------------------------------ */
136
137/* Answer every HTTP/2 request with a small text body. */
138static int h2_on_request(void* ud, N_H2_SERVER* srv, const N_H2_REQUEST* req) {
139 SRV* s = (SRV*)ud;
140 N_H2_HEADER h[2];
141 s->h2_requests++;
142 snprintf(s->h2_path, sizeof(s->h2_path), "%s", req->path ? req->path : "");
143 h[0].name = (char*)":status";
144 h[0].value = (char*)"200";
145 h[1].name = (char*)"content-type";
146 h[1].value = (char*)"text/plain";
147 if (n_http2_server_respond(srv, req->stream_id, h, 2, (const unsigned char*)H2_REPLY, strlen(H2_REPLY)) != 0)
148 failures++;
149 return 0;
150}
151
152/* Accept ROUNDS connections, negotiating ALPN on each. */
153static void* server_fn(void* p) {
154 static const char* const protos[] = {"h2", "http/1.1"};
155 SRV* s = (SRV*)p;
156 int round;
157 for (round = 0; round < ROUNDS; round++) {
159 if (!c)
160 break;
161 if (netw_ssl_server_handshake_ex(c, pick_cb, s->ca, protos, 2) != TRUE) {
162 n_log(LOG_ERR, "server TLS handshake failed on round %d", round);
163 failures++;
164 netw_close(&c);
165 continue;
166 }
167 if (netw_ssl_get_alpn(c, s->selected[round], sizeof(s->selected[round])) != TRUE)
168 s->selected[round][0] = '\0';
169 if (strcmp(s->selected[round], "h2") == 0) {
171 if (!h2 || n_http2_server_handshake(h2) != 0 || n_http2_server_run(h2, h2_on_request, s) != 0) {
172 n_log(LOG_ERR, "HTTP/2 server failed: %s", _str(n_http2_server_error(h2)));
173 failures++;
174 }
176 } else {
177 /* the non-h2 rounds only prove the handshake completed */
178 char buf[16] = "";
179 if (SSL_read(c->ssl, buf, 4) > 0)
180 tls_write(c, "pong", 4);
181 }
182 s->rounds_done++;
183 netw_close(&c);
184 }
185 return NULL;
186}
187
188/* ------------------------------------------------------------------ */
189/* a minimal HTTP/2 client, enough to check the server answers */
190/* ------------------------------------------------------------------ */
191
192/* Send one GET on stream 1 and read the response status and body. Returns 0 on
193 success, -1 on a protocol or I/O error. */
194static int h2_client_get(NETWORK* c, const char* path, int* status, char* body, size_t body_cap) {
195 unsigned char block[512], frame[1024], rb[8192];
196 N_H2_HEADER req[4];
198 size_t w, rlen = 0, blen = 0;
199 int done = 0, rc = -1;
200
201 *status = 0;
202 body[0] = '\0';
203 req[0].name = (char*)":method";
204 req[0].value = (char*)"GET";
205 req[1].name = (char*)":scheme";
206 req[1].value = (char*)"https";
207 req[2].name = (char*)":authority";
208 req[2].value = (char*)ALPN_HOST;
209 req[3].name = (char*)":path";
210 req[3].value = (char*)path;
211 w = n_http2_hpack_encode(req, 4, block, sizeof(block));
212 if (w == 0 || !dec)
213 goto out;
214
215 if (tls_write(c, N_H2_PREFACE, strlen(N_H2_PREFACE)) < 0)
216 goto out;
217 if (n_http2_frame_build_header(frame, sizeof(frame), 0, N_H2_SETTINGS, 0, 0) == 0)
218 goto out;
219 if (tls_write(c, frame, N_H2_FRAME_HEADER_LEN) < 0)
220 goto out;
221 if (n_http2_frame_build_header(frame, sizeof(frame), (uint32_t)w, N_H2_HEADERS, N_H2_FLAG_END_HEADERS | N_H2_FLAG_END_STREAM, 1) == 0)
222 goto out;
223 memcpy(frame + N_H2_FRAME_HEADER_LEN, block, w);
224 if (tls_write(c, frame, N_H2_FRAME_HEADER_LEN + w) < 0)
225 goto out;
226
227 while (!done) {
228 N_H2_FRAME f;
229 size_t consumed = 0;
230 int pr = (rlen > 0) ? n_http2_frame_parse(rb, rlen, &f, &consumed) : 0;
231 if (pr < 0)
232 goto out;
233 if (pr == 0) {
234 ssize_t n;
235 if (rlen == sizeof(rb))
236 goto out;
237 n = tls_read(c, rb + rlen, sizeof(rb) - rlen);
238 if (n <= 0)
239 goto out;
240 rlen += (size_t)n;
241 continue;
242 }
243 if (f.type == N_H2_HEADERS && f.stream_id == 1) {
244 N_H2_HEADER hdrs[32];
245 size_t count = 0, i;
246 if (n_http2_hpack_decode(dec, f.payload, f.length, hdrs, 32, &count) != 0)
247 goto out;
248 for (i = 0; i < count; i++)
249 if (hdrs[i].name && strcmp(hdrs[i].name, ":status") == 0)
250 *status = atoi(hdrs[i].value ? hdrs[i].value : "0");
251 n_http2_hpack_headers_free(hdrs, count);
252 } else if (f.type == N_H2_DATA && f.stream_id == 1) {
253 if (blen + f.length < body_cap) {
254 memcpy(body + blen, f.payload, f.length);
255 blen += f.length;
256 body[blen] = '\0';
257 }
258 }
259 if (f.stream_id == 1 && (f.flags & N_H2_FLAG_END_STREAM))
260 done = 1;
261 memmove(rb, rb + consumed, rlen - consumed);
262 rlen -= consumed;
263 }
264 rc = 0;
265out:
266 n_http2_hpack_free(&dec);
267 return rc;
268}
269
270/* ------------------------------------------------------------------ */
271/* the client rounds */
272/* ------------------------------------------------------------------ */
273
274/* Connect, offer the given ALPN list (none when count is 0), and report what was
275 negotiated in got[]. Returns the connected NETWORK, or NULL. */
276static NETWORK* client_connect(const char* port, const char* const* protos, size_t count, char* got, size_t got_len) {
277 NETWORK* cli = NULL;
278 got[0] = '\0';
279 if (netw_ssl_connect_client_to(&cli, "127.0.0.1", (char*)port, NETWORK_IPALL, 5000) != TRUE || !cli)
280 return NULL;
281 netw_ssl_set_verify(cli, 0);
282 if (count > 0 && netw_ssl_set_alpn(cli, protos, count) != TRUE) {
283 netw_close(&cli);
284 return NULL;
285 }
286 if (netw_ssl_do_handshake(cli, (char*)ALPN_HOST) != TRUE) {
287 netw_close(&cli);
288 return NULL;
289 }
290 netw_ssl_get_alpn(cli, got, got_len);
291 return cli;
292}
293
294/* Bind a loopback listening socket on the first free port in a range. */
295static NETWORK* bind_free(char* port_out, size_t port_out_len) {
296 int p;
297 for (p = 18400; p < 18500; p++) {
298 NETWORK* l = NULL;
299 char ps[16];
300 snprintf(ps, sizeof(ps), "%d", p);
301 if (netw_make_listening(&l, "127.0.0.1", ps, 5, NETWORK_IPALL) == TRUE && l) {
302 snprintf(port_out, port_out_len, "%s", ps);
303 return l;
304 }
305 if (l)
306 netw_close(&l);
307 }
308 return NULL;
309}
310
311int main(int argc, char** argv) {
312 static const char* const both[] = {"h2", "http/1.1"};
313 static const char* const only11[] = {"http/1.1"};
314 CA_CTX ca = {NULL, NULL};
315 NETWORK* listen = NULL;
316 NETWORK* cli = NULL;
317 SRV srv;
318 pthread_t th;
319 char port[16] = "";
320 char got[32] = "";
321
323 process_args(argc, argv);
324 memset(&srv, 0, sizeof(srv));
325
327 if (n_x509_generate_ca("Nilorea ALPN Test CA", 3650, &ca.cert, &ca.key) != 0 || !ca.cert || !ca.key) {
328 n_log(LOG_ERR, "CA generation failed");
329 return 1;
330 }
331 listen = bind_free(port, sizeof(port));
332 if (!listen) {
333 n_log(LOG_ERR, "could not bind a loopback port");
334 free_nstr(&ca.cert);
335 free_nstr(&ca.key);
336 return 1;
337 }
338 srv.listen = listen;
339 srv.ca = &ca;
340 pthread_create(&th, NULL, server_fn, &srv);
341
342 /* round 1: both offered, h2 selected, and a real HTTP/2 exchange over it */
343 cli = client_connect(port, both, 2, got, sizeof(got));
344 if (!cli) {
345 n_log(LOG_ERR, "round 1 connect failed");
346 failures++;
347 } else {
348 int status = 0;
349 char body[128] = "";
350 if (strcmp(got, "h2") != 0) {
351 n_log(LOG_ERR, "round 1 negotiated '%s', expected h2", got);
352 failures++;
353 }
354 if (h2_client_get(cli, "/alpn", &status, body, sizeof(body)) != 0) {
355 n_log(LOG_ERR, "round 1 HTTP/2 exchange failed");
356 failures++;
357 }
358 if (status != 200 || strcmp(body, H2_REPLY) != 0) {
359 n_log(LOG_ERR, "round 1 got status %d body '%s'", status, body);
360 failures++;
361 }
362 netw_close(&cli);
363 }
364
365 /* round 2: the client speaks only HTTP/1.1, so that is what is selected */
366 cli = client_connect(port, only11, 1, got, sizeof(got));
367 if (!cli) {
368 n_log(LOG_ERR, "round 2 connect failed");
369 failures++;
370 } else {
371 char buf[8] = "";
372 if (strcmp(got, "http/1.1") != 0) {
373 n_log(LOG_ERR, "round 2 negotiated '%s', expected http/1.1", got);
374 failures++;
375 }
376 tls_write(cli, "ping", 4);
377 if (SSL_read(cli->ssl, buf, 4) <= 0) {
378 n_log(LOG_ERR, "round 2 exchange failed");
379 failures++;
380 }
381 netw_close(&cli);
382 }
383
384 /* round 3: no ALPN offered at all: nothing is negotiated, and the handshake
385 still succeeds. A server that answered with a protocol here would break
386 every client that does not speak ALPN. */
387 cli = client_connect(port, NULL, 0, got, sizeof(got));
388 if (!cli) {
389 n_log(LOG_ERR, "round 3 connect failed");
390 failures++;
391 } else {
392 char buf[8] = "";
393 if (got[0] != '\0') {
394 n_log(LOG_ERR, "round 3 negotiated '%s', expected nothing", got);
395 failures++;
396 }
397 tls_write(cli, "ping", 4);
398 if (SSL_read(cli->ssl, buf, 4) <= 0) {
399 n_log(LOG_ERR, "round 3 exchange failed");
400 failures++;
401 }
402 netw_close(&cli);
403 }
404
405 pthread_join(th, NULL);
406 if (srv.rounds_done != ROUNDS) {
407 n_log(LOG_ERR, "the server served %d of %d rounds", srv.rounds_done, ROUNDS);
408 failures++;
409 }
410 if (srv.h2_requests != 1 || strcmp(srv.h2_path, "/alpn") != 0) {
411 n_log(LOG_ERR, "the HTTP/2 handler saw %d request(s), path '%s'", srv.h2_requests, srv.h2_path);
412 failures++;
413 }
414 if (strcmp(srv.selected[0], "h2") != 0 || strcmp(srv.selected[1], "http/1.1") != 0 || srv.selected[2][0] != '\0') {
415 n_log(LOG_ERR, "the server side saw '%s', '%s', '%s'", srv.selected[0], srv.selected[1], srv.selected[2]);
416 failures++;
417 }
418
419 /* guards */
420 if (netw_ssl_set_alpn(NULL, both, 2) != FALSE) failures++;
421 if (netw_ssl_get_alpn(NULL, got, sizeof(got)) != FALSE) failures++;
422 if (netw_ssl_server_handshake_ex(NULL, pick_cb, &ca, both, 2) != FALSE) failures++;
423
424 netw_close(&listen);
425 free_nstr(&ca.cert);
426 free_nstr(&ca.key);
428
429 if (failures) {
430 n_log(LOG_ERR, "ex_network_alpn: %d failure(s)", failures);
431 return 1;
432 }
433 n_log(LOG_NOTICE, "ex_network_alpn: all checks passed");
434 return 0;
435}
void process_args(int argc, char **argv)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
static int h2_client_get(NETWORK *c, const char *path, int *status, char *body, size_t body_cap)
static void * server_fn(void *p)
#define ROUNDS
how many connections the server accepts before it stops
#define ALPN_HOST
the host name the client requests via SNI
static int h2_on_request(void *ud, N_H2_SERVER *srv, const N_H2_REQUEST *req)
static NETWORK * bind_free(char *port_out, size_t port_out_len)
#define H2_REPLY
the body the HTTP/2 handler answers with
static int pick_cb(const char *sni, N_STR **cert_pem, N_STR **key_pem, void *user_data)
static NETWORK * client_connect(const char *port, const char *const *protos, size_t count, char *got, size_t got_len)
static ssize_t tls_read(void *io, void *buf, size_t len)
static ssize_t tls_write(void *io, const void *buf, size_t len)
N_STR * key
the CA private key PEM
N_STR * cert
the CA certificate PEM
CA used by the server to mint per-host leaves.
volatile sig_atomic_t done
char * port
NETWORK * listen
the listening socket
char h2_path[128]
the :path of the last HTTP/2 request
int rounds_done
how many connections it served
int h2_requests
requests served over HTTP/2
char selected[3][32]
what ALPN the server answered per round
CA_CTX * ca
the CA to mint leaves with
server worker context
#define _str(__PTR)
define true
Definition n_common.h:193
#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
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
A box including a string and his lenght.
Definition n_str.h:61
int n_x509_generate_ca(const char *cn, int days, N_STR **ca_cert_pem, N_STR **ca_key_pem)
Generate a self-signed certificate authority (CA) keypair and cert.
Definition n_x509.c:226
int n_x509_mint_host_cert(const char *host, const N_STR *ca_cert_pem, const N_STR *ca_key_pem, int days, N_STR **leaf_cert_pem, N_STR **leaf_key_pem)
Mint a per-host leaf certificate signed by the given CA.
Definition n_x509.c:285
SSL * ssl
SSL handle.
Definition n_network.h:446
int netw_ssl_set_verify(NETWORK *netw, int enable)
enable or disable SSL peer certificate verification
Definition n_network.c:2053
int netw_ssl_server_handshake_ex(NETWORK *netw, netw_sni_pick_cb pick, void *user_data, const char *const *alpn, size_t alpn_count)
upgrade an already-accepted plaintext connection to a TLS server, selecting the certificate per clien...
Definition n_network.c:1947
int netw_init_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1335
int netw_make_listening(NETWORK **netw, char *addr, char *port, int nbpending, int ip_version)
Make a NETWORK be a Listening network.
Definition n_network.c:3493
int netw_ssl_do_handshake(NETWORK *netw, const char *sni_hostname)
Complete the SSL handshake on an already-connected NETWORK.
Definition n_network.c:2948
int netw_unload_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1368
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
Definition n_network.h:48
int netw_ssl_connect_client_to(NETWORK **netw, char *host, char *port, int ip_version, int connect_timeout_ms)
Connect as an SSL client without providing a client certificate.
Definition n_network.c:2839
int netw_ssl_set_alpn(NETWORK *netw, const char *const *protos, size_t count)
offer an ALPN protocol list on a TLS client connection, before its handshake
Definition n_network.c:1834
NETWORK * netw_accept_from(NETWORK *from)
make a normal blocking 'accept' .
Definition n_network.c:4178
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:3258
int netw_ssl_get_alpn(NETWORK *netw, char *out, size_t out_len)
get the ALPN protocol the completed handshake selected
Definition n_network.c:1864
Structure of a NETWORK.
Definition n_network.h:375
Common headers and low-level functions & define.
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_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
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_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
#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
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
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
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.
uint32_t stream_id
the stream the request arrived on
const char * path
the :path pseudo-header, or NULL when absent
one complete request received on a stream; every pointer is owned by the server and valid only for th...
Generic log system.
Network Engine.
N_STR and string function declaration.
X.509 helpers: self-signed CA generation and per-host leaf minting.