libtest.c (17420B)
1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ 2 /* 3 This file is part of GNU libmicrohttpd. 4 Copyright (C) 2024 Christian Grothoff 5 6 GNU libmicrohttpd is free software; you can redistribute it and/or 7 modify it under the terms of the GNU Lesser General Public 8 License as published by the Free Software Foundation; either 9 version 2.1 of the License, or (at your option) any later version. 10 11 GNU libmicrohttpd is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 Lesser General Public License for more details. 15 16 Alternatively, you can redistribute GNU libmicrohttpd and/or 17 modify it under the terms of the GNU General Public License as 18 published by the Free Software Foundation; either version 2 of 19 the License, or (at your option) any later version, together 20 with the eCos exception, as follows: 21 22 As a special exception, if other files instantiate templates or 23 use macros or inline functions from this file, or you compile this 24 file and link it with other works to produce a work based on this 25 file, this file does not by itself cause the resulting work to be 26 covered by the GNU General Public License. However the source code 27 for this file must still be made available in accordance with 28 section (3) of the GNU General Public License v2. 29 30 This exception does not invalidate any other reasons why a work 31 based on this file might be covered by the GNU General Public 32 License. 33 34 You should have received copies of the GNU Lesser General Public 35 License and the GNU General Public License along with this library; 36 if not, see <https://www.gnu.org/licenses/>. 37 */ 38 39 /** 40 * @file libtest.c 41 * @brief testing harness with clients against server 42 * @author Christian Grothoff 43 */ 44 #include "libtest.h" 45 #include <pthread.h> 46 #include <stdbool.h> 47 #include <fcntl.h> 48 #include <unistd.h> 49 #include <errno.h> 50 #include <sys/stat.h> 51 #include <curl/curl.h> 52 53 /** 54 * A semaphore. 55 */ 56 struct Semaphore 57 { 58 /** 59 * Mutex for the semaphore. 60 */ 61 pthread_mutex_t mutex; 62 63 /** 64 * Condition variable for the semaphore. 65 */ 66 pthread_cond_t cv; 67 68 /** 69 * Counter of the semaphore. 70 */ 71 unsigned int ctr; 72 }; 73 74 75 /** 76 * Check that @a cond is true, otherwise abort(). 77 * 78 * @param cond condition to check 79 * @param filename filename to log 80 * @param line line number to log 81 */ 82 static void 83 test_check_ (bool cond, 84 const char *filename, 85 unsigned int line) 86 { 87 if (! cond) 88 { 89 fprintf (stderr, 90 "Assertion failed at %s:%u\n", 91 filename, 92 line); 93 abort (); 94 } 95 } 96 97 98 /** 99 * Checks that @a cond is true and otherwise aborts. 100 * 101 * @param cond condition to check 102 */ 103 #define test_check(cond) \ 104 test_check_ (cond, __FILE__, __LINE__) 105 106 107 /** 108 * Initialize a semaphore @a sem with a value of @a val. 109 * 110 * @param[out] sem semaphore to initialize 111 * @param val initial value of the semaphore 112 */ 113 static void 114 semaphore_create (struct Semaphore *sem, 115 unsigned int val) 116 { 117 test_check (0 == 118 pthread_mutex_init (&sem->mutex, 119 NULL)); 120 test_check (0 == 121 pthread_cond_init (&sem->cv, 122 NULL)); 123 sem->ctr = val; 124 } 125 126 127 /** 128 * Decrement semaphore, blocks until this is possible. 129 * 130 * @param[in,out] sem semaphore to decrement 131 */ 132 static void 133 semaphore_down (struct Semaphore *sem) 134 { 135 test_check (0 == pthread_mutex_lock (&sem->mutex)); 136 while (0 == sem->ctr) 137 { 138 pthread_cond_wait (&sem->cv, 139 &sem->mutex); 140 } 141 sem->ctr--; 142 test_check (0 == pthread_mutex_unlock (&sem->mutex)); 143 } 144 145 146 /** 147 * Increment semaphore, blocks until this is possible. 148 * 149 * @param[in,out] sem semaphore to decrement 150 */ 151 static void 152 semaphore_up (struct Semaphore *sem) 153 { 154 test_check (0 == pthread_mutex_lock (&sem->mutex)); 155 sem->ctr++; 156 test_check (0 == pthread_mutex_unlock (&sem->mutex)); 157 pthread_cond_signal (&sem->cv); 158 } 159 160 161 /** 162 * Release resources used by @a sem. 163 * 164 * @param[in] sem semaphore to release (except the memory itself) 165 */ 166 static void 167 semaphore_destroy (struct Semaphore *sem) 168 { 169 test_check (0 == pthread_cond_destroy (&sem->cv)); 170 test_check (0 == pthread_mutex_destroy (&sem->mutex)); 171 } 172 173 174 /** 175 * Context for the implementation of the HTTP server. 176 */ 177 struct ServerContext 178 { 179 /** 180 * Semaphore the client raises when it goes into the 181 * next phase. 182 */ 183 struct Semaphore client_sem; 184 185 /** 186 * Semaphore the server raises when it goes into the 187 * next phase. 188 */ 189 struct Semaphore server_sem; 190 191 /** 192 * Current phase of the server. 193 */ 194 const struct MHDT_Phase *phase; 195 196 /** 197 * Main function to run the server. 198 */ 199 MHDT_ServerRunner run_cb; 200 201 /** 202 * Closure for @e run_cb. 203 */ 204 void *run_cb_cls; 205 206 /** 207 * The daemon we are running. 208 */ 209 struct MHD_Daemon *d; 210 211 /** 212 * Signal for server termination. 213 */ 214 int finsig; 215 }; 216 217 218 /** 219 * A client has requested the given url using the given method 220 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 221 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). 222 * If @a upload_size is not zero and response action is provided by this 223 * callback, then upload will be discarded and the stream (the connection for 224 * HTTP/1.1) will be closed after sending the response. 225 * 226 * @param cls argument given together with the function 227 * pointer when the handler was registered with MHD 228 * @param request the request object 229 * @param path the requested uri (without arguments after "?") 230 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 231 * #MHD_HTTP_METHOD_PUT, etc.) 232 * @param upload_size the size of the message upload content payload, 233 * #MHD_SIZE_UNKNOWN for chunked uploads (if the 234 * final chunk has not been processed yet) 235 * @return action how to proceed, NULL 236 * if the request must be aborted due to a serious 237 * error while handling the request (implies closure 238 * of underling data stream, for HTTP/1.1 it means 239 * socket closure). 240 */ 241 static const struct MHD_Action * 242 server_req_cb (void *cls, 243 struct MHD_Request *MHD_RESTRICT request, 244 const struct MHD_String *MHD_RESTRICT path, 245 enum MHD_HTTP_Method method, 246 uint_fast64_t upload_size) 247 { 248 struct ServerContext *sc = cls; 249 250 if (NULL == sc->phase->label) 251 return NULL; 252 return sc->phase->server_cb (sc->phase->server_cb_cls, 253 request, 254 path, 255 method, 256 upload_size); 257 } 258 259 260 /** 261 * Closure for run_single_client() 262 */ 263 struct ClientContext 264 { 265 /** 266 * Test phase to run. 267 */ 268 const struct MHDT_Phase *phase; 269 270 /** 271 * Phase and client specific context. 272 */ 273 struct MHDT_PhaseContext pc; 274 275 /** 276 * Pipe to use to signal that the thread has 277 * finished. 278 */ 279 int p2; 280 281 /** 282 * Set to true on success. 283 */ 284 bool status; 285 }; 286 287 288 /** 289 * Runs the logic for a single client in a thread. 290 * 291 * @param cls a `struct ClientContext` 292 * @return NULL 293 */ 294 static void * 295 run_single_client (void *cls) 296 { 297 struct ClientContext *cc = cls; 298 const char *err; 299 300 fprintf (stderr, 301 "Client %u started in phase '%s'\n", 302 cc->pc.client_id, 303 cc->phase->label); 304 err = cc->phase->client_cb (cc->phase->client_cb_cls, 305 &cc->pc); 306 if (NULL != err) 307 { 308 fprintf (stderr, 309 "Client %u failed in phase '%s': %s\n", 310 cc->pc.client_id, 311 cc->phase->label, 312 err); 313 /* This is a blocking write, thus must succeed */ 314 test_check (1 == 315 write (cc->p2, 316 "e", 317 1)); 318 return NULL; 319 } 320 cc->status = true; 321 /* This is a blocking write, thus must succeed */ 322 test_check (1 == 323 write (cc->p2, 324 "s", 325 1)); 326 fprintf (stderr, 327 "Client %u finished in phase '%s'\n", 328 cc->pc.client_id, 329 cc->phase->label); 330 return NULL; 331 } 332 333 334 /** 335 * Creates a pipe with a non-blocking read end. 336 * 337 * @param p pipe to initialize 338 */ 339 static void 340 make_pipe (int p[2]) 341 { 342 int flags; 343 344 test_check (0 == 345 pipe (p)); 346 flags = fcntl (p[0], 347 F_GETFL); 348 flags |= O_NONBLOCK; 349 test_check (0 == 350 fcntl (p[0], 351 F_SETFL, 352 flags)); 353 } 354 355 356 /** 357 * Run client processes for the given test @a phase 358 * 359 * @param phase test phase to run 360 * @param pc context to give to clients 361 */ 362 static bool 363 run_client_phase (const struct MHDT_Phase *phase, 364 const struct MHDT_PhaseContext *pc) 365 { 366 unsigned int num_clients 367 = (0 == phase->num_clients) 368 ? 1 369 : phase->num_clients; 370 unsigned int clients_left = 0; 371 struct ClientContext cctxs[num_clients]; 372 pthread_t clients[num_clients]; 373 int p[2]; 374 unsigned int i; 375 bool ret = true; 376 377 make_pipe (p); 378 fprintf (stderr, 379 "Starting phase '%s'\n", 380 phase->label); 381 for (i = 0; i<num_clients; i++) 382 { 383 cctxs[i].phase = phase; 384 cctxs[i].pc = *pc; 385 cctxs[i].pc.client_id = i; 386 cctxs[i].p2 = p[1]; 387 cctxs[i].status = false; 388 if (0 != 389 pthread_create (&clients[i], 390 NULL, 391 &run_single_client, 392 &cctxs[i])) 393 goto cleanup; 394 clients_left++; 395 } 396 397 /* 0 for timeout_ms means no timeout, we deliberately 398 underflow to MAX_UINT in this case... */ 399 for (i = phase->timeout_ms - 1; i>0; i--) 400 { 401 struct timespec ms = { 402 .tv_nsec = 1000 * 1000 403 }; 404 struct timespec rem; 405 char c; 406 407 if (0 != nanosleep (&ms, 408 &rem)) 409 { 410 fprintf (stderr, 411 "nanosleep() interrupted (%s), trying again\n", 412 strerror (errno)); 413 i++; 414 } 415 /* This is a non-blocking read */ 416 while (1 == read (p[0], 417 &c, 418 1)) 419 clients_left--; 420 if (0 == clients_left) 421 break; 422 } 423 if (0 != clients_left) 424 { 425 fprintf (stderr, 426 "Timeout (%u ms) in phase '%s': %u clients still running\n", 427 phase->timeout_ms, 428 phase->label, 429 clients_left); 430 exit (1); 431 } 432 cleanup: 433 for (i = 0; i<num_clients; i++) 434 { 435 void *res; 436 437 test_check (0 == 438 pthread_join (clients[i], 439 &res)); 440 if (! cctxs[i].status) 441 ret = false; 442 curl_slist_free_all (cctxs[i].pc.hosts); 443 } 444 test_check (0 == close (p[0])); 445 test_check (0 == close (p[1])); 446 fprintf (stderr, 447 "Finished phase '%s' with %s\n", 448 phase->label, 449 ret ? "success" : "FAILURE"); 450 return ret; 451 } 452 453 454 /** 455 * Thread that switches the server to the next phase 456 * as needed. 457 * 458 * @param cls a `struct ServerContext` 459 * @return NULL 460 */ 461 static void * 462 server_phase_logic (void *cls) 463 { 464 struct ServerContext *ctx = cls; 465 unsigned int i; 466 467 for (i = 0; NULL != ctx->phase->label; i++) 468 { 469 fprintf (stderr, 470 "Running server phase '%s'\n", 471 ctx->phase->label); 472 semaphore_down (&ctx->client_sem); 473 ctx->phase++; 474 semaphore_up (&ctx->server_sem); 475 } 476 fprintf (stderr, 477 "Server terminating\n"); 478 return NULL; 479 } 480 481 482 /** 483 * Thread that runs the MHD daemon. 484 * 485 * @param cls a `struct ServerContext` 486 * @return NULL 487 */ 488 static void * 489 server_run_logic (void *cls) 490 { 491 struct ServerContext *ctx = cls; 492 493 ctx->run_cb (ctx->run_cb_cls, 494 ctx->finsig, 495 ctx->d); 496 return NULL; 497 } 498 499 500 int 501 MHDT_test (MHDT_ServerSetup ss_cb, 502 void *ss_cb_cls, 503 MHDT_ServerRunner run_cb, 504 void *run_cb_cls, 505 struct MHDT_Phase *phases) 506 { 507 struct ServerContext ctx = { 508 .run_cb = run_cb, 509 .run_cb_cls = run_cb_cls, 510 .phase = &phases[0] 511 }; 512 struct MHD_Daemon *d; 513 int res; 514 const char *err; 515 pthread_t server_phase_thr; 516 pthread_t server_run_thr; 517 struct MHDT_PhaseContext pc_https; 518 struct MHDT_PhaseContext pc_http; 519 char base_http_url[128]; 520 char base_https_url[128]; 521 unsigned int i; 522 int p[2]; 523 524 make_pipe (p); 525 semaphore_create (&ctx.server_sem, 526 0); 527 semaphore_create (&ctx.client_sem, 528 0); 529 d = MHD_daemon_create (&server_req_cb, 530 &ctx); 531 if (NULL == d) 532 exit (77); 533 err = ss_cb (ss_cb_cls, 534 d); 535 if (NULL != err) 536 { 537 fprintf (stderr, 538 "Failed to setup server: %s\n", 539 err); 540 return 1; 541 } 542 { 543 enum MHD_StatusCode sc; 544 545 sc = MHD_daemon_start (d); 546 if (MHD_SC_OK != sc) 547 { 548 #ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED 549 fprintf (stderr, 550 "Failed to start server: %s\n", 551 MHD_status_code_to_string_lazy (sc)); 552 #else 553 fprintf (stderr, 554 "Failed to start server: %u\n", 555 (unsigned int) sc); 556 #endif 557 MHD_daemon_destroy (d); 558 return 1; 559 } 560 } 561 { 562 union MHD_DaemonInfoFixedData info; 563 enum MHD_StatusCode sc; 564 565 sc = MHD_daemon_get_info_fixed ( 566 d, 567 MHD_DAEMON_INFO_FIXED_BIND_PORT, 568 &info); 569 test_check (MHD_SC_OK == sc); 570 snprintf (base_http_url, 571 sizeof (base_http_url), 572 "http://localhost:%u/", 573 (unsigned int) info.v_bind_port_uint16); 574 snprintf (base_https_url, 575 sizeof (base_https_url), 576 "https://localhost:%u/", 577 (unsigned int) info.v_bind_port_uint16); 578 pc_http.base_url = base_http_url; 579 pc_https.base_url = base_https_url; 580 } 581 if (0 != pthread_create (&server_phase_thr, 582 NULL, 583 &server_phase_logic, 584 &ctx)) 585 { 586 fprintf (stderr, 587 "Failed to start server phase thread: %s\n", 588 strerror (errno)); 589 MHD_daemon_destroy (d); 590 return 77; 591 } 592 ctx.finsig = p[0]; 593 ctx.d = d; 594 if (0 != pthread_create (&server_run_thr, 595 NULL, 596 &server_run_logic, 597 &ctx)) 598 { 599 fprintf (stderr, 600 "Failed to start server run thread: %s\n", 601 strerror (errno)); 602 MHD_daemon_destroy (d); 603 return 77; 604 } 605 for (i = 0; NULL != phases[i].label; i++) 606 { 607 struct MHDT_Phase *pi = &phases[i]; 608 struct MHDT_PhaseContext *pc 609 = pi->use_tls 610 ? &pc_https 611 : &pc_http; 612 pc->phase = &phases[i]; 613 pc->hosts = NULL; 614 fprintf (stderr, 615 "Running test phase '%s'\n", 616 pi->label); 617 if (! run_client_phase (pi, 618 pc)) 619 { 620 res = 1; 621 goto cleanup; 622 } 623 pc->hosts = NULL; 624 /* client is done with phase */ 625 semaphore_up (&ctx.client_sem); 626 /* wait for server to have moved to new phase */ 627 semaphore_down (&ctx.server_sem); 628 } 629 res = 0; 630 cleanup: 631 /* stop thread that runs the actual server */ 632 { 633 void *pres; 634 635 test_check (1 == 636 write (p[1], 637 "e", 638 1)); 639 test_check (0 == 640 pthread_join (server_run_thr, 641 &pres)); 642 } 643 { 644 void *pres; 645 646 /* Unblock the #server_phase_logic() even if we had 647 an error */ 648 for (i = 0; NULL != phases[i].label; i++) 649 semaphore_up (&ctx.client_sem); 650 test_check (0 == 651 pthread_join (server_phase_thr, 652 &pres)); 653 } 654 MHD_daemon_destroy (d); 655 semaphore_destroy (&ctx.client_sem); 656 semaphore_destroy (&ctx.server_sem); 657 test_check (0 == close (p[0])); 658 test_check (0 == close (p[1])); 659 return res; 660 } 661 662 663 char * 664 MHDT_load_pem (const char *name) 665 { 666 char path[256]; 667 int fd; 668 struct stat s; 669 char *buf; 670 671 snprintf (path, 672 sizeof (path), 673 "data/%s", 674 name); 675 fd = open (path, 676 O_RDONLY); 677 if (-1 == fd) 678 { 679 fprintf (stderr, 680 "Failed to open %s: %s\n", 681 path, 682 strerror (errno)); 683 return NULL; 684 } 685 if (0 != 686 fstat (fd, 687 &s)) 688 { 689 fprintf (stderr, 690 "Failed to fstat %s: %s\n", 691 path, 692 strerror (errno)); 693 (void) close (fd); 694 return NULL; 695 } 696 if (((unsigned long long) s.st_size) >= (unsigned long long) SIZE_MAX) 697 { 698 fprintf (stderr, 699 "File %s too large (%llu >= %llu bytes) to malloc()\n", 700 path, 701 (unsigned long long) s.st_size, 702 (unsigned long long) SIZE_MAX); 703 (void) close (fd); 704 return NULL; 705 } 706 buf = malloc (((size_t) s.st_size + 1)); 707 if (NULL == buf) 708 { 709 fprintf (stderr, 710 "Failed to malloc(): %s\n", 711 strerror (errno)); 712 (void) close (fd); 713 return NULL; 714 } 715 if (-1 == 716 read (fd, // FIXME: read() should be called in loop to handle partial reads 717 buf, 718 (size_t) s.st_size)) 719 { 720 fprintf (stderr, 721 "Failed to read %s: %s\n", 722 path, 723 strerror (errno)); 724 free (buf); 725 (void) close (fd); 726 return NULL; 727 } 728 (void) close (fd); 729 buf[(size_t) s.st_size] = 0; 730 return buf; 731 }