• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nickg / nvc / 6818275762

09 Nov 2023 10:39PM UTC coverage: 91.141% (-0.008%) from 91.149%
6818275762

push

github

nickg
Improve error when architecture name hides object

Fixes #789

12 of 12 new or added lines in 2 files covered. (100.0%)

150 existing lines in 3 files now uncovered.

50340 of 55233 relevant lines covered (91.14%)

591243.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.94
/src/server.c
1
//
2
//  Copyright (C) 2023  Nick Gasson
3
//
4
//  This program is free software: you can redistribute it and/or modify
5
//  it under the terms of the GNU General Public License as published by
6
//  the Free Software Foundation, either version 3 of the License, or
7
//  (at your option) any later version.
8
//
9
//  This program is distributed in the hope that it will be useful,
10
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
//  GNU General Public License for more details.
13
//
14
//  You should have received a copy of the GNU General Public License
15
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
//
17

18
#include "util.h"
19
#include "hash.h"
20
#include "ident.h"
21
#include "jit/jit.h"
22
#include "option.h"
23
#include "phase.h"
24
#include "rt/shell.h"
25
#include "server.h"
26
#include "sha1.h"
27
#include "thread.h"
28

29
#include <assert.h>
30
#include <errno.h>
31
#include <string.h>
32
#include <stdlib.h>
33
#include <sys/types.h>
34
#include <sys/stat.h>
35
#include <sys/fcntl.h>
36
#include <unistd.h>
37
#include <fcntl.h>
38
#include <time.h>
39

40
#ifdef __MINGW32__
41
#define WIN32_LEAN_AND_MEAN
42
#include <winsock2.h>
43
#else
44
#include <sys/select.h>
45
#include <sys/socket.h>
46
#include <netinet/in.h>
47
#endif
48

49
#define WS_UPGRADE_VALUE     "websocket"
50
#define WS_WEBSOCKET_VERSION "13"
51
#define WS_GUID              "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
52
#define WS_GUID_LEN          36
53
#define WS_KEY_LEN           24
54
#define WS_KEY_GUID_LEN      (WS_KEY_LEN + WS_GUID_LEN)
55

56
#define HTTP_SWITCHING_PROTOCOLS   101
57
#define HTTP_OK                    200
58
#define HTTP_BAD_REQUEST           400
59
#define HTTP_NOT_FOUND             404
60
#define HTTP_METHOD_NOT_ALLOWED    405
61
#define HTTP_UPGRADE_REQUIRED      426
62
#define HTTP_INTERNAL_SERVER_ERROR 500
63

64
#define WS_OPCODE_TEXT_FRAME   0x1
65
#define WS_OPCODE_BINARY_FRAME 0x2
66
#define WS_OPCODE_CLOSE_FRAME  0x8
67
#define WS_OPCODE_PING_FRAME   0x9
68
#define WS_OPCODE_PONG_FRAME   0xa
69

70
#define MAX_HTTP_REQUEST 1024
71

72
#ifndef __MINGW32__
73
#define closesocket close
74
#endif
75

76
typedef struct _web_socket {
77
   int           sock;
78
   bool          mask;
79
   bool          closing;
80
   ws_handler_t  handler;
81
   size_t        tx_size;
82
   size_t        tx_wptr;
83
   size_t        tx_rptr;
84
   uint8_t      *tx_buf;
85
   size_t        rx_size;
86
   size_t        rx_wptr;
87
   size_t        rx_rptr;
88
   uint8_t      *rx_buf;
89
} web_socket_t;
90

91
typedef struct _packet_buf {
92
   char  *buf;
93
   size_t alloc;
94
   size_t wptr;
95
   size_t rptr;
96
} packet_buf_t;
97

98
typedef struct {
99
   tcl_shell_t  *shell;
100
   bool          shutdown;
101
   bool          banner;
102
   web_socket_t *websocket;
103
   int           sock;
104
   tree_t        top;
105
   packet_buf_t *packetbuf;
106
   const char   *init_cmd;
107
} web_server_t;
108

109
////////////////////////////////////////////////////////////////////////////////
110
// WebSocket wrapper
111

112
web_socket_t *ws_new(int sock, const ws_handler_t *handler, bool mask)
14✔
113
{
114
   web_socket_t *ws = xcalloc(sizeof(web_socket_t));
14✔
115
   ws->sock    = sock;
14✔
116
   ws->mask    = mask;
14✔
117
   ws->handler = *handler;
14✔
118

119
   return ws;
14✔
120
}
121

122
void ws_free(web_socket_t *ws)
14✔
123
{
124
   free(ws->tx_buf);
14✔
125
   free(ws->rx_buf);
14✔
126
   free(ws);
14✔
127
}
14✔
128

129
static void ws_queue_buf(web_socket_t *ws, const void *data, size_t size)
649✔
130
{
131
   if (ws->tx_wptr + size > ws->tx_size) {
649✔
132
      ws->tx_size = MAX(ws->tx_size + size, 1024);
555✔
133
      ws->tx_buf = xrealloc(ws->tx_buf, ws->tx_size);
555✔
134
   }
135

136
   memcpy(ws->tx_buf + ws->tx_wptr, data, size);
649✔
137
   ws->tx_wptr += size;
649✔
138
}
649✔
139

140
static void ws_send(web_socket_t *ws, int opcode, const void *data, size_t size)
37✔
141
{
142
   const uint8_t size0 = (size < 126 ? size : (size <= UINT16_MAX ? 126 : 127));
37✔
143

144
   const uint8_t header[2] = {
37✔
145
      0x80 | opcode,
37✔
146
      (ws->mask ? 0x80 : 0x00) | (size0 & 0x7f),
37✔
147
   };
148
   ws_queue_buf(ws, header, sizeof(header));
37✔
149

150
   if (size0 == 126) {
37✔
151
      const uint8_t extlength[2] = { PACK_BE16(size) };
2✔
152
      ws_queue_buf(ws, extlength, sizeof(extlength));
2✔
153
   }
154
   else if (size0 == 127) {
35✔
155
      const uint8_t extlength[8] = { PACK_BE64(size) };
2✔
156
      ws_queue_buf(ws, extlength, sizeof(extlength));
2✔
157
   }
158

159
   if (ws->mask) {
37✔
160
      const int key = rand();
16✔
161
      const uint8_t masks[4] = { PACK_BE32(key) };
16✔
162
      ws_queue_buf(ws, masks, sizeof(masks));
16✔
163

164
      char xord[128];
16✔
165
      for (size_t i = 0; i < size; i += sizeof(xord)) {
593✔
166
         const size_t chunksz = MIN(sizeof(xord), size - i);
577✔
167
         for (int j = 0; j < chunksz; j++)
72,674✔
168
            xord[j] = ((const uint8_t *)data)[i + j] ^ masks[(i + j) % 4];
72,097✔
169

170
         ws_queue_buf(ws, xord, chunksz);
577✔
171
      }
172
   }
173
   else if (size > 0)
21✔
174
      ws_queue_buf(ws, data, size);
15✔
175
}
37✔
176

177
void ws_send_binary(web_socket_t *ws, const void *data, size_t size)
12✔
178
{
179
   ws_send(ws, WS_OPCODE_BINARY_FRAME, data, size);
12✔
180
}
12✔
181

182
void ws_send_packet(web_socket_t *ws, packet_buf_t *pb)
7✔
183
{
184
   ws_send_binary(ws, pb->buf, pb->wptr);
7✔
185
}
7✔
186

187
void ws_send_text(web_socket_t *ws, const char *text)
13✔
188
{
189
   ws_send(ws, WS_OPCODE_TEXT_FRAME, text, strlen(text));
13✔
190
}
13✔
191

192
void ws_send_close(web_socket_t *ws)
6✔
193
{
194
   ws_send(ws, WS_OPCODE_CLOSE_FRAME, NULL, 0);
6✔
195
}
6✔
196

197
void ws_send_ping(web_socket_t *ws, const void *data, size_t size)
3✔
198
{
199
   ws_send(ws, WS_OPCODE_PING_FRAME, data, size);
3✔
200
}
3✔
201

202
void ws_flush(web_socket_t *ws)
33✔
203
{
204
   while (ws->tx_wptr != ws->tx_rptr) {
66✔
205
      const size_t chunksz = ws->tx_wptr - ws->tx_rptr;
33✔
206
      const ssize_t nbytes =
33✔
207
         send(ws->sock, (char *)ws->tx_buf + ws->tx_rptr, chunksz, 0);
33✔
208

209
      if (nbytes == 0)
33✔
210
         break;
211
      else if (nbytes < 0) {
33✔
UNCOV
212
         ws->closing = true;
×
213
         break;
×
214
      }
215

216
      ws->tx_rptr += nbytes;
33✔
217
   }
218

219
   if (ws->tx_wptr == ws->tx_rptr)
33✔
220
      ws->tx_rptr = ws->tx_wptr = 0;
33✔
221
}
33✔
222

223
void ws_poll(web_socket_t *ws)
39✔
224
{
225
 read_more:
226
   if (ws->rx_size - ws->rx_wptr < 1024)
175✔
227
      ws->rx_buf = xrealloc(ws->rx_buf, (ws->rx_size += 1024));
150✔
228

229
   const ssize_t nbytes = recv(ws->sock, (char *)ws->rx_buf + ws->rx_wptr,
350✔
230
                               ws->rx_size - ws->rx_wptr - 1, 0);
175✔
231
   if (nbytes == -1 && errno == EAGAIN)
175✔
232
      return;
233
   else if (nbytes <= 0) {
175✔
234
      ws->closing = true;
6✔
235
      return;
6✔
236
   }
237

238
   ws->rx_wptr += nbytes;
169✔
239
   assert(ws->rx_wptr <= ws->rx_size);
169✔
240
   assert(ws->rx_rptr < ws->rx_wptr);
169✔
241

242
   do {
173✔
243
      const size_t rbytes = ws->rx_wptr - ws->rx_rptr;
173✔
244

245
      if (rbytes < 2)
173✔
UNCOV
246
         goto read_more;   // Not enough for WebSocket header
×
247

248
      uint8_t *frame = ws->rx_buf + ws->rx_rptr;
173✔
249

250
      // Frame format
251
      //
252
      //   0    1     2     3     4 5 6 7  8     9 A B C D E F
253
      //   FIN  RSV1  RSV2  RSV3  Opcode   Mask  Payload length
254
      //   Extended payload length (optional)
255
      //   Masking key (optional)
256
      //   Payload data
257

258
      const bool fin = frame[0] & 0x80;
173✔
259
      const int opcode = frame[0] & 0xf;
173✔
260
      const bool mask = frame[1] & 0x80;
173✔
261
      const int size0 = frame[1] & 0x7f;
173✔
262

263
      size_t headersz = 2 + (mask ? 4 : 0);
173✔
264
      if (size0 == 126)
173✔
265
         headersz += 2;
4✔
266
      else if (size0 == 127)
169✔
267
         headersz += 8;
136✔
268

269
      if (rbytes < headersz)
173✔
UNCOV
270
         goto read_more;   // Not enough for extended header
×
271

272
      int flength = size0;
173✔
273
      if (size0 == 127)
173✔
274
         flength = UNPACK_BE64(frame + 2);
136✔
275
      else if (size0 == 126)
37✔
276
         flength = UNPACK_BE16(frame + 2);
4✔
277

278
      if (rbytes < flength + headersz)
173✔
279
         goto read_more;   // Not enough for full frame
136✔
280

281
      assert(fin);
37✔
282
      (void)fin;
37✔
283

284
      if (mask) {
37✔
285
         for (int i = 0; i < flength; i++)
72,113✔
286
            frame[headersz + i] ^= frame[headersz - 4 + (i % 4)];
72,097✔
287
      }
288

289
      void *payload = frame + headersz;
37✔
290

291
      switch (opcode) {
37✔
292
      case WS_OPCODE_TEXT_FRAME:
13✔
293
         {
294
            char *text = payload;
13✔
295
            assert(text + flength < (char *)ws->rx_buf + ws->rx_size);
13✔
296
            text[flength] = '\0';
13✔
297

298
            if (ws->handler.text_frame != NULL)
13✔
299
               (*ws->handler.text_frame)(ws, text, ws->handler.context);
13✔
300
         }
301
         break;
302

303
      case WS_OPCODE_BINARY_FRAME:
12✔
304
         if (ws->handler.binary_frame != NULL)
12✔
305
            (*ws->handler.binary_frame)(ws, payload, flength,
12✔
306
                                        ws->handler.context);
307
         break;
308

309
      case WS_OPCODE_CLOSE_FRAME:
6✔
310
         ws->closing = true;
6✔
311
         break;
6✔
312

313
      case WS_OPCODE_PING_FRAME:
3✔
314
         ws_send(ws, WS_OPCODE_PONG_FRAME, payload, flength);
3✔
315
         break;
3✔
316

317
      case WS_OPCODE_PONG_FRAME:
3✔
318
         if (ws->handler.pong_frame != NULL)
3✔
319
            (*ws->handler.pong_frame)(ws, payload, flength,
3✔
320
                                      ws->handler.context);
321
         break;
322

UNCOV
323
      default:
×
324
         DEBUG_ONLY(fatal_trace("unhandled WebSocket opcode %02x", opcode));
×
325
         break;
37✔
326
      }
327

328
      ws->rx_rptr += flength + headersz;
37✔
329
   } while (ws->rx_rptr < ws->rx_wptr);
37✔
330

331
   ws->rx_rptr = ws->rx_wptr = 0;
33✔
332
}
333

334
bool ws_closing(web_socket_t *ws)
6✔
335
{
336
   return ws->closing;
6✔
337
}
338

339
////////////////////////////////////////////////////////////////////////////////
340
// Packet buffers
341

342
static packet_buf_t *pb_new(void)
5✔
343
{
344
   packet_buf_t *pb = xcalloc(sizeof(packet_buf_t));
5✔
345
   pb->alloc = 128;
5✔
346
   pb->buf = xmalloc(pb->alloc);
5✔
347

348
   return pb;
5✔
349
}
350

351
static void pb_free(packet_buf_t *pb)
5✔
352
{
353
   free(pb->buf);
5✔
354
   free(pb);
5✔
355
}
5✔
356

357
static void pb_grow(packet_buf_t *pb, size_t need)
20✔
358
{
359
   if (pb->wptr + need > pb->alloc) {
20✔
UNCOV
360
      pb->alloc = MAX(pb->wptr + need, pb->alloc * 2);
×
361
      pb->buf = xrealloc(pb->buf, pb->alloc);
×
362
   }
363
}
20✔
364

365
static void pb_pack_u8(packet_buf_t *pb, uint8_t value)
7✔
366
{
367
   pb_grow(pb, 1);
7✔
368
   pb->buf[pb->wptr++] = value;
7✔
369
}
7✔
370

371
static void pb_pack_u16(packet_buf_t *pb, uint16_t value)
6✔
372
{
373
   pb_grow(pb, 2);
6✔
374
   pb->buf[pb->wptr++] = value >> 8;
6✔
375
   pb->buf[pb->wptr++] = value & 0xff;
6✔
376
}
6✔
377

378
#if 0
379
static void pb_pack_u32(packet_buf_t *pb, uint32_t value)
380
{
381
   pb_grow(pb, 4);
382
   pb->buf[pb->wptr++] = (value >> 24) & 0xff;
383
   pb->buf[pb->wptr++] = (value >> 16) & 0xff;
384
   pb->buf[pb->wptr++] = (value >> 8) & 0xff;
385
   pb->buf[pb->wptr++] = value & 0xff;
386
}
387
#endif
388

389
static void pb_pack_u64(packet_buf_t *pb, uint64_t value)
1✔
390
{
391
   pb_grow(pb, 8);
1✔
392
   pb->buf[pb->wptr++] = (value >> 56) & 0xff;
1✔
393
   pb->buf[pb->wptr++] = (value >> 48) & 0xff;
1✔
394
   pb->buf[pb->wptr++] = (value >> 40) & 0xff;
1✔
395
   pb->buf[pb->wptr++] = (value >> 32) & 0xff;
1✔
396
   pb->buf[pb->wptr++] = (value >> 24) & 0xff;
1✔
397
   pb->buf[pb->wptr++] = (value >> 16) & 0xff;
1✔
398
   pb->buf[pb->wptr++] = (value >> 8) & 0xff;
1✔
399
   pb->buf[pb->wptr++] = value & 0xff;
1✔
400
}
1✔
401

402
static void pb_pack_bytes(packet_buf_t *pb, const void *data, size_t len)
6✔
403
{
404
   pb_grow(pb, len);
6✔
405
   memcpy(pb->buf + pb->wptr, data, len);
6✔
406
   pb->wptr += len;
6✔
407
}
6✔
408

409
static void pb_pack_str(packet_buf_t *pb, const char *str)
3✔
410
{
411
   const size_t len = strlen(str);
3✔
412
   assert(len < UINT16_MAX);
3✔
413

414
   pb_pack_u16(pb, len);
3✔
415
   pb_pack_bytes(pb, str, len);
3✔
416
}
3✔
417

418
static void pb_pack_ident(packet_buf_t *pb, ident_t ident)
3✔
419
{
420
   const size_t len = ident_len(ident);
3✔
421
   assert(len < UINT16_MAX);
3✔
422

423
   pb_pack_u16(pb, len);
3✔
424
   pb_pack_bytes(pb, istr(ident), len);
3✔
425
}
3✔
426

427
////////////////////////////////////////////////////////////////////////////////
428
// Web server
429

430
typedef enum {
431
   LOG_DEBUG,
432
   LOG_INFO,
433
   LOG_WARN,
434
   LOG_ERROR
435
} log_level_t;
436

437
__attribute__((format(printf, 2, 3)))
438
static void server_log(log_level_t level, const char *fmt, ...)
17✔
439
{
440
   if (opt_get_int(OPT_UNIT_TEST))
17✔
441
      return;
17✔
UNCOV
442
   else if (DEBUG_ONLY(false &&) level < LOG_INFO)
×
443
      return;
444

UNCOV
445
   va_list ap;
×
446
   va_start(ap, fmt);
×
447

UNCOV
448
   switch (level) {
×
449
   case LOG_DEBUG: color_printf("$#8$D: "); break;
×
450
   case LOG_INFO: printf("I: "); break;
×
451
   case LOG_WARN: color_printf("$yellow$W: "); break;
×
452
   case LOG_ERROR: color_printf("$red$E: "); break;
×
453
   }
454

UNCOV
455
   vprintf(fmt, ap);
×
456
   color_printf("$$\n");
×
457
   fflush(stdout);
×
458

UNCOV
459
   va_end(ap);
×
460
}
461

462
static void send_fully(int fd, const void *data, size_t len)
7✔
463
{
464
   while (len > 0) {
14✔
465
      ssize_t nbytes = send(fd, data, len, 0);
7✔
466
      if (nbytes <= 0) {
7✔
UNCOV
467
         server_log(LOG_ERROR, "send: %s", strerror(errno));
×
468
         return;
×
469
      }
470

471
      data += nbytes;
7✔
472
      len -= nbytes;
7✔
473
   }
474
}
475

476
static void send_http_headers(int fd, int status, const char *type, size_t len,
7✔
477
                              const char *headers)
478
{
479
   char date[128];
7✔
480
   time_t now = time(0);
7✔
481
   struct tm tm;
7✔
482
#ifdef __MINGW32__
483
   gmtime_s(&tm, &now);
484
#else
485
   gmtime_r(&now, &tm);
7✔
486
#endif
487
   strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", &tm);
7✔
488

489
   char buf[512];
7✔
490
   const int nbytes = checked_sprintf(buf, sizeof(buf),
7✔
491
                                      "HTTP/1.1 %d\r\n"
492
                                      "Date: %s\r\n"
493
                                      "Content-Type: %s; charset=UTF-8\r\n"
494
                                      "Content-Length: %zu\r\n"
495
                                      "%s\r\n",
496
                                      status, date, type, len, headers);
497

498
   send_fully(fd, buf, nbytes);
7✔
499
}
7✔
500

UNCOV
501
static void send_page(int fd, int status, const char *page)
×
502
{
UNCOV
503
   const size_t len = strlen(page);
×
504
   send_http_headers(fd, status, "text/html", len, "");
×
505
   send_fully(fd, page, len);
×
506
}
×
507

508
#ifdef ENABLE_GUI
UNCOV
509
static void send_file(int fd, const char *file, const char *mime)
×
510
{
UNCOV
511
   FILE *f = fopen(file, "rb");
×
512
   if (f == NULL) {
×
513
      send_page(fd, HTTP_NOT_FOUND, "File not found");
×
514
      return;
×
515
   }
516

UNCOV
517
   file_info_t info;
×
518
   if (!get_handle_info(fileno(f), &info)) {
×
519
      send_page(fd, HTTP_INTERNAL_SERVER_ERROR, "Cannot stat file");
×
520
      goto out_close;
×
521
   }
522

UNCOV
523
   send_http_headers(fd, HTTP_OK, mime, info.size, "");
×
524

UNCOV
525
   char buf[1024];
×
526
   for (ssize_t remain = info.size, nbytes; remain > 0; remain -= nbytes) {
×
527
      memset(buf, '\0', sizeof(buf));
×
528

UNCOV
529
      if ((nbytes = fread(buf, 1, MIN(remain, sizeof(buf)), f)) == 0) {
×
530
         server_log(LOG_ERROR, "fread: %s: %s", file, strerror(errno));
×
531
         goto out_close;
×
532
      }
533

UNCOV
534
      send_fully(fd, buf, nbytes);
×
535
   }
536

UNCOV
537
 out_close:
×
538
   fclose(f);
×
539
}
540
#endif
541

542
static void handle_text_frame(web_socket_t *ws, const char *text, void *context)
8✔
543
{
544
   web_server_t *server = context;
8✔
545

546
   const char *result = NULL;
8✔
547
   if (shell_eval(server->shell, text, &result) && *result != '\0')
8✔
548
      ws_send_text(ws, result);
5✔
549
}
8✔
550

551
static void handle_binary_frame(web_socket_t *ws, const void *data,
5✔
552
                                size_t length, void *context)
553
{
554
   web_server_t *server = context;
5✔
555

556
   if (length == 0) {
5✔
UNCOV
557
      server_log(LOG_WARN, "ignoring zero-length binary frame");
×
558
      return;
×
559
   }
560

561
   const c2s_opcode_t op = *(const uint8_t *)data;
5✔
562
   switch (op) {
5✔
563
   case C2S_SHUTDOWN:
5✔
564
      server->shutdown = true;
5✔
565
      break;
5✔
UNCOV
566
   default:
×
567
      server_log(LOG_ERROR, "unhandled client to server opcode %02x", op);
×
568
      break;
×
569
   }
570
}
571

572
static void kill_connection(web_server_t *server)
573
{
574
   diag_set_consumer(NULL, NULL);
575

576
   closesocket(server->websocket->sock);
577

578
   ws_free(server->websocket);
579
   server->websocket = NULL;
580
}
581

UNCOV
582
static void tunnel_diag(diag_t *d, void *context)
×
583
{
UNCOV
584
   web_server_t *server = context;
×
585

UNCOV
586
   if (server->websocket != NULL) {
×
587
      ws_send_text(server->websocket, diag_get_text(d));
×
588
   }
589
   else
UNCOV
590
      server_log(LOG_INFO, "%s", diag_get_text(d));
×
591
}
×
592

593
static packet_buf_t *fresh_packet_buffer(web_server_t *server)
594
{
595
   server->packetbuf->wptr = 0;
596
   return server->packetbuf;
597
}
598

599
static void add_wave_handler(ident_t path, const char *enc, void *user)
1✔
600
{
601
   web_server_t *server = user;
1✔
602

603
   packet_buf_t *pb = fresh_packet_buffer(server);
1✔
604
   pb_pack_u8(pb, S2C_ADD_WAVE);
1✔
605
   pb_pack_ident(pb, path);
1✔
606
   pb_pack_str(pb, enc);
1✔
607
   ws_send_packet(server->websocket, pb);
1✔
608
}
1✔
609

610
static void signal_update_handler(ident_t path, uint64_t now, rt_signal_t *s,
1✔
611
                                  const char *enc, void *user)
612
{
613
   web_server_t *server = user;
1✔
614

615
   packet_buf_t *pb = fresh_packet_buffer(server);
1✔
616
   pb_pack_u8(pb, S2C_SIGNAL_UPDATE);
1✔
617
   pb_pack_ident(pb, path);
1✔
618
   pb_pack_str(pb, enc);
1✔
619
   ws_send_packet(server->websocket, pb);
1✔
620
}
1✔
621

622
static void start_sim_handler(ident_t top, void *user)
1✔
623
{
624
   web_server_t *server = user;
1✔
625

626
   packet_buf_t *pb = fresh_packet_buffer(server);
1✔
627
   pb_pack_u8(pb, S2C_START_SIM);
1✔
628
   pb_pack_ident(pb, top);
1✔
629
   ws_send_packet(server->websocket, pb);
1✔
630
}
1✔
631

632
static void restart_sim_handler(void *user)
1✔
633
{
634
   web_server_t *server = user;
1✔
635

636
   packet_buf_t *pb = fresh_packet_buffer(server);
1✔
637
   pb_pack_u8(pb, S2C_RESTART_SIM);
1✔
638
   ws_send_packet(server->websocket, pb);
1✔
639
}
1✔
640

641
static void quit_sim_handler(void *user)
1✔
642
{
643
   web_server_t *server = user;
1✔
644

645
   packet_buf_t *pb = fresh_packet_buffer(server);
1✔
646
   pb_pack_u8(pb, S2C_QUIT_SIM);
1✔
647
   ws_send_packet(server->websocket, pb);
1✔
648
}
1✔
649

650
static void next_time_step_handler(uint64_t now, void *user)
1✔
651
{
652
   web_server_t *server = user;
1✔
653

654
   packet_buf_t *pb = fresh_packet_buffer(server);
1✔
655
   pb_pack_u8(pb, S2C_NEXT_TIME_STEP);
1✔
656
   pb_pack_u64(pb, now);
1✔
657
   ws_send_packet(server->websocket, pb);
1✔
658
}
1✔
659

660
static void open_websocket(web_server_t *server, int fd)
7✔
661
{
662
   if (server->websocket != NULL) {
7✔
663
      ws_send_close(server->websocket);
1✔
664
      ws_flush(server->websocket);
1✔
665
      kill_connection(server);
1✔
666
   }
667

668
   const ws_handler_t handler = {
7✔
669
      .text_frame   = handle_text_frame,
670
      .binary_frame = handle_binary_frame,
671
      .context      = server
672
   };
673

674
   server->websocket = ws_new(fd, &handler, false);
7✔
675

676
   diag_set_consumer(tunnel_diag, server);
7✔
677

678
   if (server->banner)
7✔
UNCOV
679
      shell_print_banner(server->shell);
×
680

681
   if (server->top != NULL)
7✔
682
      shell_reset(server->shell, server->top);
1✔
683

684
   if (server->init_cmd != NULL) {
7✔
685
      packet_buf_t *pb = fresh_packet_buffer(server);
1✔
686
      pb_pack_u8(pb, S2C_INIT_CMD);
1✔
687
      pb_pack_str(pb, server->init_cmd);
1✔
688
      ws_send_packet(server->websocket, pb);
1✔
689
   }
690
}
7✔
691

692
static void base64_encode(const void *in, size_t len, text_buf_t *tb)
7✔
693
{
694
   static const char map[] =
7✔
695
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
696

697
   const unsigned char *data = in;
7✔
698

699
   for (size_t i = 0; i < len; i++) {
56✔
700
      int c0 = (data[i] >> 2) & 0x3F;
49✔
701
      tb_append(tb, map[c0]);
49✔
702
      c0 = (data[i] << 4) & 0x3F;
49✔
703
      if (++i < len)
49✔
704
         c0 |= (data[i] >> 4) & 0x0F;
49✔
705
      tb_append(tb, map[c0]);
49✔
706

707
      if (i < len) {
49✔
708
         int c1 = (data[i] << 2) & 0x3F;
49✔
709
         if (++i < len)
49✔
710
            c1 |= (data[i] >> 6) & 0x03;
42✔
711
         tb_append(tb, map[c1]);
49✔
712
      }
713
      else {
UNCOV
714
         ++i;
×
715
         tb_append(tb, '=');
×
716
      }
717

718
      if (i < len) {
49✔
719
         int c2 = data[i] & 0x3F;
42✔
720
         tb_append(tb, map[c2]);
42✔
721
      }
722
      else
723
         tb_append(tb, '=');
7✔
724
   }
725
}
7✔
726

727
static bool get_websocket_accept_value(const char *key, text_buf_t *tb)
7✔
728
{
729
   if (key == NULL || strlen(key) != WS_KEY_LEN)
7✔
730
      return false;
731

732
   char *str LOCAL = xmalloc(WS_KEY_LEN + WS_GUID_LEN + 1);
7✔
733
   strncpy(str, key, (WS_KEY_LEN + 1));
7✔
734
   strncpy(str + WS_KEY_LEN, WS_GUID, WS_GUID_LEN + 1);
7✔
735

736
   SHA1_CTX ctx;
7✔
737
   SHA1Init(&ctx);
7✔
738
   SHA1Update(&ctx, (unsigned char *)str, WS_KEY_GUID_LEN);
7✔
739

740
   unsigned char hash[SHA1_LEN];
7✔
741
   SHA1Final(hash, &ctx);
7✔
742

743
   base64_encode(hash, SHA1_LEN, tb);
7✔
744
   return true;
7✔
745
}
746

747
static void websocket_upgrade(web_server_t *server, int fd, const char *method,
7✔
748
                              const char *version, shash_t *headers)
749
{
750
   LOCAL_TEXT_BUF tb = tb_new();
7✔
751

752
   if (strcmp(method, "GET") != 0 || strcmp(version, "HTTP/1.1") != 0) {
7✔
UNCOV
753
      send_page(fd, HTTP_BAD_REQUEST, "Bad request");
×
754
      goto out_close;
×
755
   }
756

757
   const char *ws_version_header = shash_get(headers, "sec-websocket-version");
7✔
758

759
   if (ws_version_header == NULL
7✔
760
       || strcasecmp(ws_version_header, WS_WEBSOCKET_VERSION) != 0) {
7✔
761

UNCOV
762
      static const char page[] = "Upgrade required";
×
763
      static const char header[] =
×
764
         "Sec-WebSocket-Version:" WS_WEBSOCKET_VERSION;
765

UNCOV
766
      send_http_headers(fd, HTTP_UPGRADE_REQUIRED, "text/html",
×
767
                        sizeof(page), header);
UNCOV
768
      send_fully(fd, page, sizeof(page));
×
769

UNCOV
770
      goto out_close;
×
771
   }
772

773
   const char *ws_key_header = shash_get(headers, "sec-websocket-key");
7✔
774

775
   if (ws_key_header == NULL || strlen(ws_key_header) != WS_KEY_LEN) {
7✔
UNCOV
776
      send_page(fd, HTTP_BAD_REQUEST, "Bad request");
×
777
      goto out_close;
×
778
   }
779

780
   tb_cat(tb, "Connection: upgrade\r\n"
7✔
781
          "Upgrade: websocket\r\n"
782
          "Sec-WebSocket-Accept: ");
783

784
   if (!get_websocket_accept_value(ws_key_header, tb))
7✔
UNCOV
785
      goto out_close;
×
786

787
   tb_cat(tb, "\r\n");
7✔
788

789
   send_http_headers(fd, HTTP_SWITCHING_PROTOCOLS, "text/html", 0, tb_get(tb));
7✔
790

791
   open_websocket(server, fd);
7✔
792

793
   return;   // Socket left open
7✔
794

UNCOV
795
 out_close:
×
796
   closesocket(fd);
×
797
}
798

799
static bool is_websocket_request(shash_t *headers)
7✔
800
{
801
   const char *upg_header = shash_get(headers, "upgrade");
7✔
802
   const char *con_header = shash_get(headers, "connection");
7✔
803

804
   return (upg_header != NULL && con_header != NULL)
7✔
805
          && (strcasecmp(upg_header, WS_UPGRADE_VALUE) == 0)
7✔
806
          && (strcasestr(con_header, "Upgrade") != NULL);
14✔
807
}
808

809
#ifdef ENABLE_GUI
UNCOV
810
static void serve_gui_static_files(int fd, const char *url)
×
811
{
UNCOV
812
   LOCAL_TEXT_BUF tb = tb_new();
×
813
   get_data_dir(tb);
×
814
   tb_cat(tb, "/gui");
×
815

UNCOV
816
   if (strcmp(url, "/") == 0) {
×
817
      tb_cat(tb, "/index.html");
×
818
      send_file(fd, tb_get(tb), "text/html");
×
819
      return;
×
820
   }
821

UNCOV
822
   const char *mime = "application/octet-stream";
×
823
   const char *dot = strrchr(url, '.');
×
824
   if (dot != NULL) {
×
825
      static const char *mime_map[][2] = {
826
         { ".js",  "text/javascript" },
827
         { ".css", "text/css" },
828
         { ".map", "application/json" },
829
      };
830

UNCOV
831
      for (int i = 0; i < ARRAY_LEN(mime_map); i++) {
×
832
         if (strcmp(dot, mime_map[i][0]) == 0) {
×
833
            mime = mime_map[i][1];
×
834
            break;
×
835
         }
836
      }
837
   }
838

UNCOV
839
   tb_cat(tb, url);
×
840
   send_file(fd, tb_get(tb), mime);
×
841
}
842
#endif
843

844
static void handle_http_request(web_server_t *server, int fd,
7✔
845
                                const char *method, const char *url,
846
                                const char *version, shash_t *headers)
847
{
848
   server_log(LOG_DEBUG, "%s %s", method, url);
7✔
849

850
   if (is_websocket_request(headers)) {
7✔
851
      websocket_upgrade(server, fd, method, version, headers);
7✔
852
      return;    // Socket left open
7✔
853
   }
UNCOV
854
   else if (strcmp(method, "GET") != 0) {
×
855
      send_page(fd, HTTP_METHOD_NOT_ALLOWED, "Method not allowed");
×
856
      goto out_close;
×
857
   }
858

859
#ifdef ENABLE_GUI
UNCOV
860
   serve_gui_static_files(fd, url);
×
861
#else
862
   send_page(fd, HTTP_NOT_FOUND, "Not found");
863
#endif
864

UNCOV
865
 out_close:
×
866
   closesocket(fd);
×
867
}
868

869
static void handle_new_connection(web_server_t *server)
7✔
870
{
871
   int fd = accept(server->sock, NULL, NULL);
7✔
872
   if (fd < 0) {
7✔
UNCOV
873
      server_log(LOG_ERROR, "accept: %s", last_os_error());
×
874
      return;
7✔
875
   }
876

877
#ifdef __MINGW32__
878
   if (ioctlsocket(fd, FIONBIO, &(unsigned long){1})) {
879
      server_log(LOG_ERROR, "ioctlsocket: %s", last_os_error());
880
      goto out_close;
881
   }
882
#else
883
   const int flags = fcntl(fd, F_GETFL, 0);
7✔
884
   if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
7✔
UNCOV
885
      server_log(LOG_ERROR, "fcntl: %s", last_os_error());
×
886
      goto out_close;
×
887
   }
888
#endif
889

890
   char buf[MAX_HTTP_REQUEST + 1];
7✔
891
   size_t reqlen = 0;
7✔
892
   do {
7✔
893
      ssize_t n = recv(fd, buf + reqlen, MAX_HTTP_REQUEST - reqlen, 0);
7✔
894

895
#ifdef __MINGW32__
896
      const bool would_block =
897
         (n == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK);
898
#else
899
      const bool would_block = (n == -1 && errno == EWOULDBLOCK);
7✔
900
#endif
901

902
      if (would_block) {
7✔
UNCOV
903
         fd_set rfd;
×
904
         FD_ZERO(&rfd);
×
905
         FD_SET(fd, &rfd);
×
906

UNCOV
907
         struct timeval tv = {
×
908
            .tv_sec = 1,
909
            .tv_usec = 0
910
         };
911

UNCOV
912
         if (select(fd + 1, &rfd, NULL, NULL, &tv) == -1) {
×
913
            server_log(LOG_ERROR, "select: %s", last_os_error());
×
914
            goto out_close;
×
915
         }
916

UNCOV
917
         if (FD_ISSET(fd, &rfd))
×
918
            continue;
×
919

UNCOV
920
         server_log(LOG_ERROR, "timeout waiting for HTTP request");
×
921
         goto out_close;
×
922
      }
923
      else if (n <= 0) {
7✔
UNCOV
924
         server_log(LOG_ERROR, "recv: %s", last_os_error());
×
925
         goto out_close;
×
926
      }
927

928
      reqlen += n;
7✔
929
      assert(reqlen <= MAX_HTTP_REQUEST);
7✔
930

931
      if (reqlen == MAX_HTTP_REQUEST) {
7✔
UNCOV
932
         server_log(LOG_ERROR, "HTTP request too big");
×
933
         goto out_close;
×
934
      }
935

936
      buf[reqlen] = '\0';
7✔
937
   } while (strstr(buf, "\r\n\r\n") == NULL);
7✔
938

939
   const char *method = "GET";
7✔
940
   const char *url = "/";
7✔
941
   const char *version = "";
7✔
942

943
   char *saveptr, *saveptr2;
7✔
944
   char *line = strtok_r(buf, "\r\n", &saveptr);
7✔
945
   if (line == NULL) {
7✔
UNCOV
946
      server_log(LOG_ERROR, "malformed HTTP request");
×
947
      goto out_close;
×
948
   }
949

950
   method = strtok_r(line, " ", &saveptr2);
7✔
951
   if (method == NULL)
7✔
UNCOV
952
      goto malformed;
×
953

954
   url = strtok_r(NULL, " ", &saveptr2);
7✔
955
   if (url == NULL)
7✔
UNCOV
956
      goto malformed;
×
957

958
   version = strtok_r(NULL, " ", &saveptr2);
7✔
959
   if (version == NULL)
7✔
UNCOV
960
      goto malformed;
×
961

962
   shash_t *headers = shash_new(64);
7✔
963

964
   while ((line = strtok_r(NULL, "\r\n", &saveptr)) && line[0] != '\0') {
70✔
965
      char *colon = strchr(line, ':');
56✔
966
      if (colon != NULL) {
56✔
967
         *colon = '\0';
56✔
968

969
         char *value = colon + 1;
56✔
970
         while (*value == ' ')
112✔
971
            value++;
56✔
972

973
         for (char *p = line; *p; p++)
623✔
974
            *p = tolower_iso88591(*p);
567✔
975

976
         shash_put(headers, line, value);
56✔
977
      }
978
   }
979

980
   handle_http_request(server, fd, method, url, version, headers);
7✔
981
   shash_free(headers);
7✔
982
   return;
7✔
983

UNCOV
984
 malformed:
×
985
   server_log(LOG_ERROR, "malformed HTTP request");
×
986

UNCOV
987
 out_close:
×
UNCOV
988
   closesocket(fd);
×
989
}
990

991
static void tunnel_output(const char *buf, size_t nchars, void *user)
×
992
{
993
   web_server_t *server = user;
×
UNCOV
994
   ws_send(server->websocket, WS_OPCODE_TEXT_FRAME, buf, nchars);
×
UNCOV
995
}
×
996

997
static int open_server_socket(void)
5✔
998
{
999
#ifdef __MINGW32__
1000
   WSADATA wsaData;
1001
   if (WSAStartup(MAKEWORD(2, 2), &wsaData))
1002
      fatal_errno("WSAStartup failed");
1003
#endif
1004

1005
   int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
5✔
1006
   if (sock < 0)
5✔
UNCOV
1007
      fatal_errno("socket");
×
1008

1009
   if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
5✔
1010
                  (char *)&(int){1}, sizeof(int)) < 0)
5✔
UNCOV
1011
      fatal_errno("setsockopt");
×
1012

1013
   const uint16_t port = opt_get_int(OPT_SERVER_PORT);
5✔
1014

1015
   struct sockaddr_in addr;
5✔
1016
   addr.sin_family = AF_INET;
5✔
1017
   addr.sin_port = htons(port);
5✔
1018
   addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
5✔
1019

1020
   if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
5✔
UNCOV
1021
      fatal_errno("bind");
×
1022

1023
   if (listen(sock, SOMAXCONN) < 0)
5✔
UNCOV
1024
      fatal_errno("listen");
×
1025

1026
   server_log(LOG_INFO, "listening on 127.0.0.1:%u", port);
5✔
1027

1028
   return sock;
5✔
1029
}
1030

1031
void start_server(jit_factory_t make_jit, tree_t top,
5✔
1032
                  server_ready_fn_t cb, void *arg, const char *init_cmd)
1033
{
1034
   web_server_t *server = xcalloc(sizeof(web_server_t));
5✔
1035
   server->shell     = shell_new(make_jit);
5✔
1036
   server->top       = top;
5✔
1037
   server->packetbuf = pb_new();
5✔
1038
   server->init_cmd  = init_cmd;
5✔
1039
   server->banner    = !opt_get_int(OPT_UNIT_TEST);
5✔
1040

1041
   shell_handler_t handler = {
5✔
1042
      .add_wave = add_wave_handler,
1043
      .signal_update = signal_update_handler,
1044
      .stderr_write = tunnel_output,
1045
      .stdout_write = tunnel_output,
1046
      .start_sim = start_sim_handler,
1047
      .restart_sim = restart_sim_handler,
1048
      .quit_sim = quit_sim_handler,
1049
      .next_time_step = next_time_step_handler,
1050
      .context = server
1051
   };
1052
   shell_set_handler(server->shell, &handler);
5✔
1053

1054
   server->sock = open_server_socket();
5✔
1055

1056
   if (cb != NULL)
5✔
1057
      (*cb)(arg);
5✔
1058

1059
   for (;;) {
44✔
1060
      fd_set rfd, wfd, efd;
49✔
1061
      FD_ZERO(&rfd);
49✔
1062
      FD_ZERO(&wfd);
49✔
1063
      FD_ZERO(&efd);
49✔
1064

1065
      int max_fd = -1;
49✔
1066

1067
      if (server->sock != -1) {
49✔
1068
         FD_SET(server->sock, &rfd);
34✔
1069
         max_fd = MAX(max_fd, server->sock);
34✔
1070
      }
1071

1072
      if (server->websocket != NULL) {
49✔
1073
         FD_SET(server->websocket->sock, &rfd);
38✔
1074

1075
         if (server->websocket->tx_wptr != server->websocket->tx_rptr)
38✔
1076
            FD_SET(server->websocket->sock, &wfd);
16✔
1077

1078
         max_fd = MAX(max_fd, server->websocket->sock);
38✔
1079
      }
1080

1081
      if (max_fd == -1)
49✔
1082
         break;
1083

1084
      struct timeval tv = {
44✔
1085
         .tv_sec = 1,
1086
         .tv_usec = 0
1087
      };
1088

1089
      if (select(max_fd + 1, &rfd, &wfd, &efd, &tv) == -1)
44✔
UNCOV
1090
         fatal_errno("select");
×
1091

1092
      if (server->sock != -1 && FD_ISSET(server->sock, &rfd))
44✔
1093
         handle_new_connection(server);
7✔
1094

1095
      if (server->websocket != NULL) {
44✔
1096
         if (FD_ISSET(server->websocket->sock, &rfd))
44✔
1097
            ws_poll(server->websocket);
22✔
1098

1099
         if (FD_ISSET(server->websocket->sock, &wfd))
44✔
1100
            ws_flush(server->websocket);
16✔
1101

1102
         if (server->websocket->closing)
44✔
1103
            kill_connection(server);
6✔
1104
      }
1105

1106
      if (server->shutdown && server->sock != -1) {
44✔
1107
         server_log(LOG_INFO, "stopping server");
5✔
1108

1109
         closesocket(server->sock);
5✔
1110
         server->sock = -1;
5✔
1111

1112
         if (server->websocket != NULL)
5✔
1113
            ws_send_close(server->websocket);
5✔
1114
      }
1115
   }
1116

1117
   assert(server->sock == -1);
5✔
1118

1119
   pb_free(server->packetbuf);
5✔
1120
   free(server);
5✔
1121
}
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc