emme coverage


Directory: src/
File: src/server.c
Date: 2026-03-27 20:24:50
Exec Total Coverage
Lines: 281 415 67.7%
Functions: 15 16 93.8%
Branches: 118 232 50.9%

Line Branch Exec Source
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <poll.h>
5 #include <unistd.h>
6 #include <arpa/inet.h>
7 #include <sys/epoll.h>
8 #include <pthread.h>
9 #include <fcntl.h>
10 #include <liburing.h>
11 #include <sys/sendfile.h>
12 #include <sys/time.h> // for instrumentation
13 #include <signal.h>
14 #include <errno.h>
15 #include "config.h"
16 #include "server.h"
17 #include "http_parser.h"
18 #include "router.h"
19 #include "tls.h"
20 #include <openssl/ssl.h>
21 #include <openssl/err.h>
22 #include "thread_pool.h"
23 #include "log.h"
24 #include <ctype.h>
25 #include "http2_response.h"
26
27 #ifndef DEBUG_H2
28 #define DEBUG_H2 0
29 #endif
30
31 #define H2_LOG(...) \
32 do { \
33 if (DEBUG_H2) \
34 log_message(LOG_LEVEL_DEBUG, __VA_ARGS__); \
35 } while (0)
36
37 typedef struct {
38 SSL *ssl;
39 ServerConfig *config;
40 int want_read;
41 int want_write;
42 size_t total_read;
43 int logged_preface;
44 } H2IO;
45
46 6 static int find_header_end(const char *buf, size_t len)
47 {
48
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (len < 4)
49 return -1;
50
2/2
✓ Branch 0 taken 12469 times.
✓ Branch 1 taken 1 times.
12470 for (size_t i = 0; i + 3 < len; i++)
51 {
52
3/4
✓ Branch 0 taken 308 times.
✓ Branch 1 taken 12161 times.
✓ Branch 2 taken 308 times.
✗ Branch 3 not taken.
12469 if (buf[i] == '\r' && buf[i + 1] == '\n' &&
53
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 303 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
308 buf[i + 2] == '\r' && buf[i + 3] == '\n')
54 5 return (int)(i + 4);
55 }
56 1 return -1;
57 }
58
59 /* Callback invoked for each header received in an HTTP/2 frame */
60 12 static int on_header_callback(nghttp2_session *session,
61 const nghttp2_frame *frame,
62 const uint8_t *name, size_t namelen,
63 const uint8_t *value, size_t valuelen,
64 uint8_t flags, void *user_data)
65 {
66 (void)session;
67 (void)flags;
68 (void)user_data;
69
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (frame->hd.type == NGHTTP2_HEADERS &&
70
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 frame->headers.cat == NGHTTP2_HCAT_REQUEST)
71 {
72 12 StreamData *data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
73
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
12 if (!data)
74 {
75 3 data = calloc(1, sizeof(StreamData));
76
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!data)
77 {
78 log_message(LOG_LEVEL_ERROR, "Failed to allocate HTTP/2 stream state");
79 return NGHTTP2_ERR_CALLBACK_FAILURE;
80 }
81 3 data->req.version = strdup("HTTP/2");
82
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!data->req.version)
83 {
84 free(data);
85 log_message(LOG_LEVEL_ERROR, "Failed to allocate HTTP/2 request version");
86 return NGHTTP2_ERR_CALLBACK_FAILURE;
87 }
88 3 nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, data);
89 }
90
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 if (namelen >= 1 && name[0] == ':')
91 {
92
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
12 if (strncmp((const char *)name, ":method", namelen) == 0)
93 {
94 3 free((void *)data->req.method);
95 3 data->req.method = strndup((const char *)value, valuelen);
96 }
97
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 else if (strncmp((const char *)name, ":path", namelen) == 0)
98 {
99 3 free((void *)data->req.path);
100 3 data->req.path = strndup((const char *)value, valuelen);
101 }
102
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 else if (strncmp((const char *)name, ":scheme", namelen) == 0)
103 ; /* ignore scheme */
104 3 else if (strncmp((const char *)name, ":authority", namelen) == 0)
105 {
106 /* authority not currently used by routing */
107 }
108 }
109 }
110 12 return 0;
111 }
112
113 3 static ssize_t http2_body_read_callback(
114 nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length,
115 uint32_t *data_flags, nghttp2_data_source *source, void *user_data)
116 {
117 (void)session;
118 (void)stream_id;
119 (void)user_data;
120 3 StreamData *data = (StreamData *)source->ptr;
121
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (!data || !data->resp)
122 {
123 *data_flags = NGHTTP2_DATA_FLAG_EOF;
124 return 0;
125 }
126 3 size_t remaining = data->resp->body_len - data->resp_sent;
127 3 size_t to_copy = remaining < length ? remaining : length;
128
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (to_copy > 0)
129 {
130 2 memcpy(buf, data->resp->body + data->resp_sent, to_copy);
131 2 data->resp_sent += to_copy;
132 }
133
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (data->resp_sent >= data->resp->body_len)
134 {
135 3 *data_flags = NGHTTP2_DATA_FLAG_EOF;
136 }
137 3 return to_copy;
138 }
139
140 /* Callback invoked when a complete frame is received */
141 6 static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
142 {
143 6 H2IO *io = (H2IO *)user_data;
144
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 ServerConfig *config = io ? io->config : NULL;
145 H2_LOG("on_frame_recv_callback: start");
146
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (frame->hd.type == NGHTTP2_HEADERS &&
147
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 frame->headers.cat == NGHTTP2_HCAT_REQUEST &&
148
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 (frame->hd.flags & NGHTTP2_FLAG_END_HEADERS))
149 {
150 3 StreamData *data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
151
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (data)
152 {
153 char raw_request[BUFFER_SIZE];
154 9 snprintf(raw_request, sizeof(raw_request), "%s %s %s\r\n",
155
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 data->req.method ? data->req.method : "GET",
156
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 data->req.path ? data->req.path : "/",
157
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 data->req.version ? data->req.version : "HTTP/2");
158 3 log_message(LOG_LEVEL_INFO, "Routing HTTP/2 request for path (synthesized): %s",
159
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 data->req.path ? data->req.path : "/");
160
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (!data->resp)
161 3 data->resp = calloc(1, sizeof(Http2Response));
162
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!data->resp)
163 {
164 log_message(LOG_LEVEL_ERROR, "Failed to allocate Http2Response");
165 return 0;
166 }
167 3 data->resp_sent = 0;
168
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (route_request_tls(&data->req, raw_request, strlen(raw_request), config, NULL, data->resp) != 0 &&
169 data->resp->status_code == 0) {
170 data->resp->status_code = 500;
171 snprintf(data->resp->status_text, sizeof(data->resp->status_text), "Internal Server Error");
172 data->resp->content_type[0] = '\0';
173 data->resp->body[0] = '\0';
174 data->resp->body_len = 0;
175 }
176
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (data->resp->status_code == 0)
177 data->resp->status_code = 200;
178 3 snprintf(data->resp->status_code_str, sizeof(data->resp->status_code_str),
179 3 "%d", data->resp->status_code);
180 3 snprintf(data->resp->content_length_str, sizeof(data->resp->content_length_str),
181 3 "%zu", data->resp->body_len);
182 3 data->resp->headers[0] = MAKE_NV(":status", data->resp->status_code_str);
183
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 data->resp->headers[1] = MAKE_NV("content-type",
184 data->resp->content_type[0] ? data->resp->content_type : "text/plain");
185 3 data->resp->headers[2] = MAKE_NV("content-length", data->resp->content_length_str);
186 3 data->resp->num_headers = 3;
187 nghttp2_data_provider data_prd;
188 3 data_prd.source.ptr = data;
189 3 data_prd.read_callback = http2_body_read_callback;
190 3 int rv = nghttp2_submit_response(session, frame->hd.stream_id,
191 3 data->resp->headers, data->resp->num_headers,
192 &data_prd);
193
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (rv != 0)
194 log_message(LOG_LEVEL_ERROR, "nghttp2_submit_response failed: %s", nghttp2_strerror(rv));
195 else
196 3 log_message(LOG_LEVEL_INFO, "nghttp2_submit_response succeeded for stream %d", frame->hd.stream_id);
197
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (rv == 0)
198 {
199 3 int send_rv = nghttp2_session_send(session);
200
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if (send_rv < 0 && send_rv != NGHTTP2_ERR_WOULDBLOCK)
201 log_message(LOG_LEVEL_ERROR, "nghttp2_session_send failed: %s", nghttp2_strerror(send_rv));
202 }
203 }
204 }
205 6 return 0;
206 }
207
208 /* Callback invoked when a stream is closed */
209 3 static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
210 uint32_t error_code, void *user_data)
211 {
212 (void)error_code;
213 (void)user_data;
214 3 StreamData *data = nghttp2_session_get_stream_user_data(session, stream_id);
215
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (data)
216 {
217 3 free((void *)data->req.method);
218 3 free((void *)data->req.path);
219 3 free((void *)data->req.version);
220 3 free(data->resp);
221 3 free(data);
222 3 nghttp2_session_set_stream_user_data(session, stream_id, NULL);
223 }
224 3 return 0;
225 }
226
227 SSL_CTX *ssl_ctx = NULL;
228 static struct io_uring global_ring; // Global io_uring instance for the accept loop.
229 static volatile sig_atomic_t g_shutdown = 0;
230 static int g_server_fd = -1;
231
232 static void log_io_uring_error(const char *context, int ret)
233 {
234 log_message(LOG_LEVEL_ERROR, "%s: %s", context, strerror(-ret));
235 }
236
237 8 static void handle_signal(int sig)
238 {
239 (void)sig;
240 8 g_shutdown = 1;
241
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (g_server_fd >= 0)
242 8 close(g_server_fd);
243 8 }
244
245 static void handle_http2_connection(SSL *ssl, int client_fd, ServerConfig *config, struct io_uring *ring);
246 static void handle_http1_connection(SSL *ssl, int client_fd, ServerConfig *config);
247
248 3 static void log_hex_prefix(const uint8_t *data, size_t len)
249 {
250 char hexbuf[256];
251 3 size_t max = len > 32 ? 32 : len;
252 3 size_t off = 0;
253
3/4
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 72 times.
✗ Branch 3 not taken.
75 for (size_t i = 0; i < max && off + 3 < sizeof(hexbuf); i++)
254 {
255 72 off += snprintf(hexbuf + off, sizeof(hexbuf) - off, "%02x ", data[i]);
256 }
257 3 hexbuf[off] = '\0';
258 H2_LOG("h2 recv prefix (%zu bytes): %s", max, hexbuf);
259 3 }
260
261 /* Callback for nghttp2 to send data via SSL */
262 12 static ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
263 size_t length, int flags, void *user_data)
264 {
265 (void)session;
266 (void)flags;
267 12 H2IO *io = (H2IO *)user_data;
268 12 io->want_read = 0;
269 12 io->want_write = 0;
270
271 12 ssize_t ret = SSL_write(io->ssl, data, length);
272
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (ret > 0)
273 {
274 H2_LOG("h2 send_callback wrote=%zd", ret);
275 12 return ret;
276 }
277
278 int ssl_error = SSL_get_error(io->ssl, ret);
279 if (ssl_error == SSL_ERROR_WANT_READ) {
280 io->want_read = 1;
281 H2_LOG("h2 send_callback WANT_READ");
282 return NGHTTP2_ERR_WOULDBLOCK;
283 }
284 if (ssl_error == SSL_ERROR_WANT_WRITE) {
285 io->want_write = 1;
286 H2_LOG("h2 send_callback WANT_WRITE");
287 return NGHTTP2_ERR_WOULDBLOCK;
288 }
289 log_message(LOG_LEVEL_ERROR, "SSL_write failed in send_callback. Error: %d", ssl_error);
290 return NGHTTP2_ERR_CALLBACK_FAILURE;
291 }
292
293 /* Callback for nghttp2 to receive data via SSL */
294 15 static ssize_t recv_callback(nghttp2_session *session, uint8_t *buf, size_t length,
295 int flags, void *user_data)
296 {
297 H2_LOG("recv_callback: start");
298 (void)session;
299 (void)flags;
300 15 H2IO *io = (H2IO *)user_data;
301 15 io->want_read = 0;
302 15 io->want_write = 0;
303
304 15 ssize_t ret = SSL_read(io->ssl, buf, length);
305
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 9 times.
15 if (ret <= 0)
306 {
307 6 int ssl_error = SSL_get_error(io->ssl, ret);
308
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (ssl_error == SSL_ERROR_ZERO_RETURN)
309 {
310 3 log_message(LOG_LEVEL_INFO, "SSL connection closed by peer");
311 3 return NGHTTP2_ERR_EOF;
312 }
313
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (ssl_error == SSL_ERROR_WANT_READ) {
314 3 io->want_read = 1;
315 H2_LOG("h2 recv_callback WANT_READ");
316 3 return NGHTTP2_ERR_WOULDBLOCK;
317 }
318 if (ssl_error == SSL_ERROR_WANT_WRITE) {
319 io->want_write = 1;
320 H2_LOG("h2 recv_callback WANT_WRITE");
321 return NGHTTP2_ERR_WOULDBLOCK;
322 }
323 log_message(LOG_LEVEL_ERROR, "SSL_read failed in recv_callback. Error: %d", ssl_error);
324 return NGHTTP2_ERR_CALLBACK_FAILURE;
325 }
326 9 io->total_read += (size_t)ret;
327
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 if (!io->logged_preface)
328 {
329 3 log_hex_prefix(buf, (size_t)ret);
330 3 io->logged_preface = 1;
331 }
332 H2_LOG("recv_callback: end, bytes read: %zd", ret);
333 9 return ret;
334 }
335
336 /* Nonblocking TLS handshake using thread-local io_uring with instrumentation */
337 8 static int perform_nonblocking_ssl_accept(SSL *ssl, int client_fd, struct io_uring *ring)
338 {
339 int ret;
340 struct timeval start, end;
341 8 gettimeofday(&start, NULL);
342
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 while ((ret = SSL_accept(ssl)) <= 0)
343 {
344 int err = SSL_get_error(ssl, ret);
345 if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
346 {
347 int poll_flags = (err == SSL_ERROR_WANT_READ) ? POLLIN : POLLOUT;
348 struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
349 if (!sqe)
350 {
351 log_message(LOG_LEVEL_ERROR, "Failed to get SQE during handshake");
352 return -1;
353 }
354 io_uring_prep_poll_add(sqe, client_fd, poll_flags);
355 int submit_ret = io_uring_submit(ring);
356 if (submit_ret < 0)
357 {
358 log_io_uring_error("io_uring_submit failed during handshake", submit_ret);
359 return -1;
360 }
361 struct io_uring_cqe *cqe;
362 int wait_ret = io_uring_wait_cqe(ring, &cqe);
363 if (wait_ret < 0)
364 {
365 log_io_uring_error("io_uring_wait_cqe failed during handshake", wait_ret);
366 return -1;
367 }
368 io_uring_cqe_seen(ring, cqe);
369 }
370 else
371 {
372 log_message(LOG_LEVEL_ERROR, "SSL_accept failed nonblockingly, error: %d", err);
373 return -1;
374 }
375 }
376 8 gettimeofday(&end, NULL);
377 8 long handshake_ms = (end.tv_sec - start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) / 1000;
378 8 log_message(LOG_LEVEL_INFO, "Nonblocking SSL handshake completed in %ld ms", handshake_ms);
379 8 return ret;
380 }
381
382 /* HTTP/2 connection handler using thread-local io_uring */
383 3 static void handle_http2_connection(SSL *ssl, int client_fd, ServerConfig *config, struct io_uring *ring)
384 {
385 (void)ring;
386 3 int flags = fcntl(client_fd, F_GETFL, 0);
387 3 fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
388 nghttp2_session_callbacks *callbacks;
389 nghttp2_session *session;
390
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (nghttp2_session_callbacks_new(&callbacks) != 0)
391 {
392 log_message(LOG_LEVEL_ERROR, "Failed to initialize HTTP/2 callbacks");
393 return;
394 }
395 3 nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
396 3 nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback);
397 3 nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback);
398 3 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback);
399 3 nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback);
400 3 H2IO io = {
401 .ssl = ssl,
402 .config = config,
403 .want_read = 0,
404 .want_write = 0,
405 .total_read = 0,
406 .logged_preface = 0,
407 };
408
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (nghttp2_session_server_new(&session, callbacks, &io) != 0)
409 {
410 log_message(LOG_LEVEL_ERROR, "Failed to create nghttp2 session");
411 nghttp2_session_callbacks_del(callbacks);
412 return;
413 }
414 3 log_message(LOG_LEVEL_INFO, "HTTP/2 session started");
415 3 int rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, NULL, 0);
416
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (rv < 0)
417 {
418 log_message(LOG_LEVEL_ERROR, "Failed to send initial SETTINGS frame: %s", nghttp2_strerror(rv));
419 nghttp2_session_del(session);
420 nghttp2_session_callbacks_del(callbacks);
421 return;
422 }
423 3 rv = nghttp2_session_send(session);
424
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if (rv < 0 && rv != NGHTTP2_ERR_WOULDBLOCK)
425 {
426 log_message(LOG_LEVEL_ERROR, "Failed to flush initial SETTINGS: %s", nghttp2_strerror(rv));
427 nghttp2_session_del(session);
428 nghttp2_session_callbacks_del(callbacks);
429 return;
430 }
431 3 rv = 0;
432
1/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 while (nghttp2_session_want_read(session) || nghttp2_session_want_write(session))
433 {
434 6 short events = 0;
435
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
6 if (io.want_read || io.want_write)
436 {
437
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (io.want_read)
438 3 events |= POLLIN;
439
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (io.want_write)
440 events |= POLLOUT;
441 }
442 else
443 {
444
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 if (nghttp2_session_want_read(session))
445 3 events |= POLLIN;
446
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (nghttp2_session_want_write(session))
447 events |= POLLOUT;
448 }
449
450 H2_LOG("h2 loop: want_read=%d want_write=%d io.want_read=%d io.want_write=%d events=0x%x",
451 nghttp2_session_want_read(session), nghttp2_session_want_write(session),
452 io.want_read, io.want_write, events);
453 6 struct pollfd pfd = {.fd = client_fd, .events = events};
454 6 int pret = poll(&pfd, 1, 1000);
455
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (pret < 0)
456 {
457 log_message(LOG_LEVEL_ERROR, "poll failed: %s", strerror(errno));
458 3 break;
459 }
460
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (pret == 0)
461 continue;
462
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL))
463 {
464 log_message(LOG_LEVEL_ERROR, "poll error/hangup: revents=%d", pfd.revents);
465 break;
466 }
467 H2_LOG("h2 loop: revents=0x%x", pfd.revents);
468
469
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (pfd.revents & POLLIN)
470 {
471 6 rv = nghttp2_session_recv(session);
472
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (rv < 0)
473 {
474
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (rv == NGHTTP2_ERR_EOF)
475 3 log_message(LOG_LEVEL_INFO, "HTTP/2 session closed by client");
476 else if (rv != NGHTTP2_ERR_WOULDBLOCK)
477 log_message(LOG_LEVEL_ERROR, "nghttp2_session_recv error: %s", nghttp2_strerror(rv));
478
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (rv != NGHTTP2_ERR_WOULDBLOCK)
479 3 break;
480 }
481 else
482 {
483 H2_LOG("h2 recv processed rv=%d total_read=%zu", rv, io.total_read);
484 }
485 }
486
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (pfd.revents & POLLOUT)
487 {
488 rv = nghttp2_session_send(session);
489 if (rv < 0)
490 {
491 if (rv != NGHTTP2_ERR_WOULDBLOCK)
492 {
493 log_message(LOG_LEVEL_ERROR, "nghttp2_session_send error: %s", nghttp2_strerror(rv));
494 break;
495 }
496 }
497 else
498 {
499 H2_LOG("h2 send processed rv=%d", rv);
500 }
501 }
502 }
503 3 nghttp2_session_del(session);
504 3 nghttp2_session_callbacks_del(callbacks);
505 }
506
507 /* HTTP/1.1 connection handler using legacy methods */
508 5 static void handle_http1_connection(SSL *ssl, int client_fd, ServerConfig *config)
509 {
510 (void)client_fd;
511
512 // Serve multiple HTTP/1.x requests on the same TLS socket
513 3 for (;;) {
514 char buffer[BUFFER_SIZE];
515 8 int total_read = 0;
516
517 // 1) Read up to the end of headers (\r\n\r\n)
518 8 int header_end = -1;
519
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
9 while (total_read < BUFFER_SIZE - 1) {
520 8 int n = SSL_read(ssl, buffer + total_read,
521 BUFFER_SIZE - 1 - total_read);
522
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if (n <= 0) {
523 // client closed connection or SSL error
524 5 goto shutdown_and_close;
525 }
526 6 total_read += n;
527 6 header_end = find_header_end(buffer, (size_t)total_read);
528
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 if (header_end >= 0)
529 5 break;
530 }
531 6 buffer[total_read] = '\0';
532
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
6 if (header_end < 0) {
533 1 const char *too_large =
534 "HTTP/1.1 431 Request Header Fields Too Large\r\n"
535 "Content-Length: 0\r\n"
536 "\r\n";
537 1 SSL_write(ssl, too_large, strlen(too_large));
538 1 goto shutdown_and_close;
539 }
540
541 // 2) Parse the request
542 HttpRequest req;
543
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
5 if (parse_http_request(buffer, total_read, &req) != 0) {
544 2 const char *bad_response =
545 "HTTP/1.1 400 Bad Request\r\n"
546 "Content-Length: 0\r\n"
547 "\r\n";
548 2 SSL_write(ssl, bad_response, strlen(bad_response));
549 // malformed request → close connection
550 2 goto shutdown_and_close;
551 }
552
553 3 log_message(LOG_LEVEL_INFO, "Valid HTTP request received. Routing...");
554
555 // 3) Route & send response (static, proxy, etc.)
556 3 route_request_tls(&req, buffer, total_read, config, ssl, NULL);
557
558 // 4) Loop back to read the next request
559 // (do NOT shutdown/close here)
560 }
561
562 5 shutdown_and_close:
563 5 SSL_shutdown(ssl);
564 5 close(client_fd);
565 5 }
566
567 /* Worker task: uses thread-local io_uring for per-connection I/O */
568 8 void client_task(void *arg)
569 {
570 8 ClientTaskData *data = (ClientTaskData *)arg;
571 static __thread struct io_uring *local_ring = NULL;
572
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (!local_ring)
573 {
574 8 local_ring = malloc(sizeof(struct io_uring));
575
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (io_uring_queue_init(QUEUE_DEPTH, local_ring, 0) < 0)
576 {
577 log_message(LOG_LEVEL_ERROR, "Failed to initialize thread-local io_uring");
578 free(local_ring);
579 local_ring = NULL;
580 }
581 }
582 8 handle_client(data->client_fd, data->config, local_ring);
583 8 free(data);
584 8 }
585
586 /* Main per-connection handler; performs nonblocking TLS handshake before dispatching via ALPN */
587 8 void handle_client(int client_fd, ServerConfig *config, struct io_uring *ring)
588 {
589 struct timeval timeout;
590 8 timeout.tv_sec = 5;
591 8 timeout.tv_usec = 0;
592 8 setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
593 8 setsockopt(client_fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
594 8 SSL *ssl = SSL_new(ssl_ctx);
595
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!ssl)
596 {
597 close(client_fd);
598 return;
599 }
600 8 SSL_set_fd(ssl, client_fd);
601 8 SSL_set_app_data(ssl, config);
602
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (perform_nonblocking_ssl_accept(ssl, client_fd, ring) <= 0)
603 {
604 log_message(LOG_LEVEL_ERROR, "Nonblocking SSL handshake failed");
605 SSL_free(ssl);
606 close(client_fd);
607 return;
608 }
609 8 const unsigned char *alpn_proto = NULL;
610 8 unsigned int alpn_len = 0;
611 8 SSL_get0_alpn_selected(ssl, &alpn_proto, &alpn_len);
612
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
8 if (alpn_len == 2 && memcmp(alpn_proto, "h2", 2) == 0)
613 {
614 3 log_message(LOG_LEVEL_INFO, "Negotiated HTTP/2");
615 3 handle_http2_connection(ssl, client_fd, config, ring);
616 }
617 else
618 {
619 5 log_message(LOG_LEVEL_INFO, "Negotiated HTTP/1.1");
620 5 handle_http1_connection(ssl, client_fd, config);
621 }
622 8 SSL_shutdown(ssl);
623 8 SSL_free(ssl);
624 8 close(client_fd);
625 }
626
627 /* Main server accept loop using a global io_uring instance */
628 8 int start_server(ServerConfig *config)
629 {
630 int server_fd, client_fd;
631 struct sockaddr_in server_addr, client_addr;
632 int ring_ret;
633 size_t max_threads;
634 size_t initial_threads;
635
636 8 g_shutdown = 0;
637 8 memset(&server_addr, 0, sizeof(server_addr));
638 8 memset(&client_addr, 0, sizeof(client_addr));
639
640 8 ring_ret = io_uring_queue_init(QUEUE_DEPTH * 2, &global_ring, 0);
641
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (ring_ret != 0)
642 {
643 fprintf(stderr, "global io_uring_queue_init failed: %s\n", strerror(-ring_ret));
644 return 1;
645 }
646 8 server_fd = socket(AF_INET, SOCK_STREAM, 0);
647
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (server_fd == -1)
648 {
649 perror("Socket creation error");
650 io_uring_queue_exit(&global_ring);
651 return 1;
652 }
653 8 int enable = 1;
654
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) == -1)
655 {
656 perror("setsockopt(SO_REUSEADDR) failed");
657 close(server_fd);
658 io_uring_queue_exit(&global_ring);
659 return 1;
660 }
661 8 g_server_fd = server_fd;
662 8 signal(SIGTERM, handle_signal);
663 8 signal(SIGINT, handle_signal);
664 8 server_addr.sin_family = AF_INET;
665 8 server_addr.sin_addr.s_addr = INADDR_ANY;
666 8 server_addr.sin_port = htons(config->port);
667
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
668 {
669 perror("Bind error");
670 close(server_fd);
671 io_uring_queue_exit(&global_ring);
672 return 1;
673 }
674
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (listen(server_fd, 2048) == -1)
675 {
676 perror("Listen error");
677 close(server_fd);
678 io_uring_queue_exit(&global_ring);
679 return 1;
680 }
681 8 ssl_ctx = create_ssl_context(config->ssl.certificate, config->ssl.private_key);
682
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!ssl_ctx)
683 {
684 close(server_fd);
685 io_uring_queue_exit(&global_ring);
686 return 1;
687 }
688
689
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 max_threads = config->max_connections > 0 ? (size_t)config->max_connections : 32;
690 8 initial_threads = max_threads < 32 ? max_threads : 32;
691
692 8 ThreadPool *pool = thread_pool_create(initial_threads, max_threads);
693
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!pool)
694 {
695 fprintf(stderr, "Failed to create thread pool\n");
696 close(server_fd);
697 io_uring_queue_exit(&global_ring);
698 cleanup_ssl_context(ssl_ctx);
699 return 1;
700 }
701 8 printf("Emme listening on port %d...\n", config->port);
702
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 while (!g_shutdown)
703 {
704 16 struct io_uring_sqe *sqe = io_uring_get_sqe(&global_ring);
705 16 socklen_t client_len = sizeof(client_addr);
706
707
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (!sqe)
708 {
709 fprintf(stderr, "Failed to get SQE for accept\n");
710 8 break;
711 }
712 16 io_uring_prep_accept(sqe, server_fd, (struct sockaddr *)&client_addr, &client_len, 0);
713 16 int submit_ret = io_uring_submit(&global_ring);
714
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (submit_ret < 0)
715 {
716 fprintf(stderr, "io_uring_submit (accept) failed: %s\n", strerror(-submit_ret));
717 break;
718 }
719 struct io_uring_cqe *cqe;
720 16 int wait_ret = io_uring_wait_cqe(&global_ring, &cqe);
721
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
16 if (wait_ret < 0)
722 {
723
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
8 if (g_shutdown &&
724 (-wait_ret == EINTR || -wait_ret == EBADF || -wait_ret == ENXIO))
725 break;
726 fprintf(stderr, "io_uring_wait_cqe (accept) failed: %s\n", strerror(-wait_ret));
727 break;
728 }
729 8 client_fd = cqe->res;
730 8 io_uring_cqe_seen(&global_ring, cqe);
731
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (g_shutdown)
732 {
733 if (client_fd >= 0)
734 close(client_fd);
735 break;
736 }
737
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (client_fd < 0)
738 {
739 if (g_shutdown)
740 break;
741 continue;
742 }
743 8 int flags = fcntl(client_fd, F_GETFL, 0);
744 8 fcntl(client_fd, F_SETFL, flags & ~O_NONBLOCK);
745 8 ClientTaskData *task_data = malloc(sizeof(ClientTaskData));
746
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!task_data)
747 {
748 perror("Failed to allocate memory for client task");
749 close(client_fd);
750 continue;
751 }
752 8 task_data->client_fd = client_fd;
753 8 task_data->config = config;
754
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (!thread_pool_add_task(pool, client_task, task_data))
755 {
756 fprintf(stderr, "Failed to add task to thread pool\n");
757 free(task_data);
758 close(client_fd);
759 }
760 }
761 8 thread_pool_destroy(pool);
762 8 close(server_fd);
763 8 g_server_fd = -1;
764 8 io_uring_queue_exit(&global_ring);
765 8 cleanup_ssl_context(ssl_ctx);
766 8 return 0;
767 }
768