libtest.c (18880B)
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 * How many rounds do we run the test? 213 */ 214 unsigned int rounds; 215 216 /** 217 * Signal for server termination. 218 */ 219 int finsig; 220 }; 221 222 223 /** 224 * A client has requested the given url using the given method 225 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 226 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). 227 * If @a upload_size is not zero and response action is provided by this 228 * callback, then upload will be discarded and the stream (the connection for 229 * HTTP/1.1) will be closed after sending the response. 230 * 231 * @param cls argument given together with the function 232 * pointer when the handler was registered with MHD 233 * @param request the request object 234 * @param path the requested uri (without arguments after "?") 235 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 236 * #MHD_HTTP_METHOD_PUT, etc.) 237 * @param upload_size the size of the message upload content payload, 238 * #MHD_SIZE_UNKNOWN for chunked uploads (if the 239 * final chunk has not been processed yet) 240 * @return action how to proceed, NULL 241 * if the request must be aborted due to a serious 242 * error while handling the request (implies closure 243 * of underling data stream, for HTTP/1.1 it means 244 * socket closure). 245 */ 246 static const struct MHD_Action * 247 server_req_cb (void *cls, 248 struct MHD_Request *MHD_RESTRICT request, 249 const struct MHD_String *MHD_RESTRICT path, 250 enum MHD_HTTP_Method method, 251 uint_fast64_t upload_size) 252 { 253 struct ServerContext *sc = (struct ServerContext *) cls; 254 255 if (NULL == sc->phase->label) 256 return NULL; 257 return sc->phase->server_cb (sc->phase->server_cb_cls, 258 request, 259 path, 260 method, 261 upload_size); 262 } 263 264 265 /** 266 * Closure for run_single_client() 267 */ 268 struct ClientContext 269 { 270 /** 271 * Test phase to run. 272 */ 273 const struct MHDT_Phase *phase; 274 275 /** 276 * Phase and client specific context. 277 */ 278 struct MHDT_PhaseContext pc; 279 280 /** 281 * Pipe to use to signal that the thread has 282 * finished. 283 */ 284 int p2; 285 286 /** 287 * Set to true on success. 288 */ 289 bool status; 290 }; 291 292 293 /** 294 * Runs the logic for a single client in a thread. 295 * 296 * @param cls a `struct ClientContext` 297 * @return NULL 298 */ 299 static void * 300 run_single_client (void *cls) 301 { 302 struct ClientContext *cc = (struct ClientContext *) cls; 303 const char *err; 304 305 fprintf (stderr, 306 "Client %u started in phase '%s'\n", 307 cc->pc.client_id, 308 cc->phase->label); 309 err = cc->phase->client_cb (cc->phase->client_cb_cls, 310 &cc->pc); 311 if (NULL != err) 312 { 313 fprintf (stderr, 314 "Client %u failed in phase '%s': %s\n", 315 cc->pc.client_id, 316 cc->phase->label, 317 err); 318 /* This is a blocking write, thus must succeed */ 319 test_check (1 == 320 write (cc->p2, 321 "e", 322 1)); 323 return NULL; 324 } 325 cc->status = true; 326 /* This is a blocking write, thus must succeed */ 327 test_check (1 == 328 write (cc->p2, 329 "s", 330 1)); 331 fprintf (stderr, 332 "Client %u finished in phase '%s'\n", 333 cc->pc.client_id, 334 cc->phase->label); 335 return NULL; 336 } 337 338 339 /** 340 * Creates a pipe with a non-blocking read end. 341 * 342 * @param p pipe to initialize 343 */ 344 static void 345 make_pipe (int p[2]) 346 { 347 int flags; 348 349 test_check (0 == 350 pipe (p)); 351 flags = fcntl (p[0], 352 F_GETFL); 353 flags |= O_NONBLOCK; 354 test_check (0 == 355 fcntl (p[0], 356 F_SETFL, 357 flags)); 358 } 359 360 361 /** 362 * Run client processes for the given test @a phase 363 * 364 * @param phase test phase to run 365 * @param pc context to give to clients 366 */ 367 static bool 368 run_client_phase (const struct MHDT_Phase *phase, 369 const struct MHDT_PhaseContext *pc) 370 { 371 unsigned int num_clients 372 = (0 == phase->num_clients) 373 ? 1 374 : phase->num_clients; 375 unsigned int clients_left = 0; 376 struct ClientContext cctxs[num_clients]; 377 pthread_t clients[num_clients]; 378 int p[2]; 379 unsigned int i; 380 uint_fast32_t te; 381 bool ret = true; 382 383 make_pipe (p); 384 fprintf (stderr, 385 "Starting phase '%s'\n", 386 phase->label); 387 for (i = 0; i<num_clients; i++) 388 { 389 cctxs[i].phase = phase; 390 cctxs[i].pc = *pc; 391 cctxs[i].pc.client_id = i; 392 cctxs[i].p2 = p[1]; 393 cctxs[i].status = false; 394 if (0 != 395 pthread_create (&clients[i], 396 NULL, 397 &run_single_client, 398 &cctxs[i])) 399 goto cleanup; 400 clients_left++; 401 } 402 403 /* 0 for timeout_ms means no timeout, we deliberately 404 underflow to MAX_UINT in this case... */ 405 for (te = phase->timeout_ms - 1; te>0; te--) 406 { 407 struct timespec ms = { 408 .tv_nsec = 1000 * 1000 409 }; 410 struct timespec rem; 411 char c; 412 413 if (0 != nanosleep (&ms, 414 &rem)) 415 { 416 fprintf (stderr, 417 "nanosleep() interrupted (%s), trying again\n", 418 strerror (errno)); 419 te++; 420 } 421 /* This is a non-blocking read */ 422 while (1 == read (p[0], 423 &c, 424 1)) 425 clients_left--; 426 if (0 == clients_left) 427 break; 428 } 429 if (0 != clients_left) 430 { 431 fprintf (stderr, 432 "Timeout (%lu ms) in phase '%s': %u clients still running\n", 433 (unsigned long) phase->timeout_ms, 434 phase->label, 435 clients_left); 436 exit (1); 437 } 438 cleanup: 439 for (i = 0; i<num_clients; i++) 440 { 441 void *res; 442 443 test_check (0 == 444 pthread_join (clients[i], 445 &res)); 446 if (! cctxs[i].status) 447 ret = false; 448 curl_slist_free_all (cctxs[i].pc.hosts); 449 } 450 test_check (0 == close (p[0])); 451 test_check (0 == close (p[1])); 452 fprintf (stderr, 453 "Finished phase '%s' with %s\n", 454 phase->label, 455 ret ? "success" : "FAILURE"); 456 return ret; 457 } 458 459 460 /** 461 * Thread that switches the server to the next phase 462 * as needed. 463 * 464 * @param cls a `struct ServerContext` 465 * @return NULL 466 */ 467 static void * 468 server_phase_logic (void *cls) 469 { 470 struct ServerContext *ctx = (struct ServerContext *) cls; 471 const struct MHDT_Phase *phases = ctx->phase; 472 unsigned int j; 473 474 for (j = 0; j < ctx->rounds; j++) 475 { 476 fprintf (stderr, 477 "Running server test round #%u/%u\n", 478 j + 1, 479 ctx->rounds); 480 ctx->phase = &phases[0]; 481 while (NULL != ctx->phase->label) 482 { 483 semaphore_down (&ctx->client_sem); 484 ctx->phase++; 485 semaphore_up (&ctx->server_sem); 486 } 487 } 488 fprintf (stderr, 489 "Server terminating\n"); 490 return NULL; 491 } 492 493 494 /** 495 * Thread that runs the MHD daemon. 496 * 497 * @param cls a `struct ServerContext` 498 * @return NULL 499 */ 500 static void * 501 server_run_logic (void *cls) 502 { 503 struct ServerContext *ctx = (struct ServerContext *) cls; 504 505 ctx->run_cb (ctx->run_cb_cls, 506 ctx->finsig, 507 ctx->d); 508 return NULL; 509 } 510 511 512 int 513 MHDT_test (MHDT_ServerSetup ss_cb, 514 void *ss_cb_cls, 515 MHDT_ServerRunner run_cb, 516 void *run_cb_cls, 517 struct MHDT_Phase *phases) 518 { 519 struct ServerContext ctx = { 520 .run_cb = run_cb, 521 .run_cb_cls = run_cb_cls, 522 .phase = &phases[0], 523 .rounds = 1 524 }; 525 struct MHD_Daemon *d; 526 int res; 527 const char *err; 528 pthread_t server_phase_thr; 529 pthread_t server_run_thr; 530 struct MHDT_PhaseContext pc_https; 531 struct MHDT_PhaseContext pc_http; 532 char base_http_url[128]; 533 char base_https_url[128]; 534 unsigned int i; 535 unsigned int j; 536 int p[2]; 537 bool fuzzing = false; 538 539 { 540 char *rstr = getenv ("MHD_TEST_FUZZING"); 541 542 if (NULL != rstr) 543 { 544 char dummy; 545 546 if (1 == 547 sscanf (rstr, 548 "%u%c", 549 &ctx.rounds, 550 &dummy)) 551 fuzzing = true; 552 } 553 } 554 make_pipe (p); 555 semaphore_create (&ctx.server_sem, 556 0); 557 semaphore_create (&ctx.client_sem, 558 0); 559 d = MHD_daemon_create (&server_req_cb, 560 &ctx); 561 if (NULL == d) 562 exit (77); 563 err = ss_cb (ss_cb_cls, 564 d); 565 if (NULL != err) 566 { 567 fprintf (stderr, 568 "Failed to setup server: %s\n", 569 err); 570 return 1; 571 } 572 { 573 enum MHD_StatusCode sc; 574 575 sc = MHD_daemon_start (d); 576 if (MHD_SC_OK != sc) 577 { 578 #ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED 579 fprintf (stderr, 580 "Failed to start server: %s\n", 581 MHD_status_code_to_string_lazy (sc)); 582 #else 583 fprintf (stderr, 584 "Failed to start server: %u\n", 585 (unsigned int) sc); 586 #endif 587 MHD_daemon_destroy (d); 588 return 1; 589 } 590 } 591 { 592 union MHD_DaemonInfoFixedData info; 593 enum MHD_StatusCode sc; 594 const char *portenv; 595 uint16_t port; 596 597 sc = MHD_daemon_get_info_fixed ( 598 d, 599 MHD_DAEMON_INFO_FIXED_BIND_PORT, 600 &info); 601 test_check (MHD_SC_OK == sc); 602 port = info.v_bind_port_uint16; 603 604 portenv = getenv ("MHD_TEST_FORCE_CLIENT_PORT"); 605 if (NULL != portenv) 606 { 607 unsigned int pn; 608 char dummy; 609 610 if ( (1 != sscanf (portenv, 611 "%u%c", 612 &pn, 613 &dummy)) || 614 (pn > 65535) ) 615 { 616 fprintf (stderr, 617 "Invalid port number specified in MHD_TEST_FORCE_CLIENT_PORT"); 618 MHD_daemon_destroy (d); 619 return 1; 620 } 621 port = (uint16_t) pn; 622 } 623 624 snprintf (base_http_url, 625 sizeof (base_http_url), 626 "http://localhost:%u/", 627 (unsigned int) port); 628 snprintf (base_https_url, 629 sizeof (base_https_url), 630 "https://localhost:%u/", 631 (unsigned int) port); 632 pc_http.base_url = base_http_url; 633 pc_https.base_url = base_https_url; 634 } 635 636 if (0 != pthread_create (&server_phase_thr, 637 NULL, 638 &server_phase_logic, 639 &ctx)) 640 { 641 fprintf (stderr, 642 "Failed to start server phase thread: %s\n", 643 strerror (errno)); 644 MHD_daemon_destroy (d); 645 return 77; 646 } 647 ctx.finsig = p[0]; 648 ctx.d = d; 649 if (0 != pthread_create (&server_run_thr, 650 NULL, 651 &server_run_logic, 652 &ctx)) 653 { 654 fprintf (stderr, 655 "Failed to start server run thread: %s\n", 656 strerror (errno)); 657 MHD_daemon_destroy (d); 658 return 77; 659 } 660 for (j = 0; j < ctx.rounds; j++) 661 { 662 fprintf (stderr, 663 "Running client test round #%u/%u\n", 664 j + 1, 665 ctx.rounds); 666 for (i = 0; NULL != phases[i].label; i++) 667 { 668 struct MHDT_Phase *pi = &phases[i]; 669 struct MHDT_PhaseContext *pc 670 = pi->use_tls 671 ? &pc_https 672 : &pc_http; 673 pc->phase = &phases[i]; 674 pc->hosts = NULL; 675 fprintf (stderr, 676 "Running test phase '%s'\n", 677 pi->label); 678 if ( (! run_client_phase (pi, 679 pc)) && 680 (! fuzzing) ) 681 { 682 res = 1; 683 goto cleanup; 684 } 685 pc->hosts = NULL; 686 /* client is done with phase */ 687 semaphore_up (&ctx.client_sem); 688 /* wait for server to have moved to new phase */ 689 semaphore_down (&ctx.server_sem); 690 } 691 } 692 res = 0; 693 cleanup: 694 /* stop thread that runs the actual server */ 695 { 696 void *pres; 697 698 test_check (1 == 699 write (p[1], 700 "e", 701 1)); 702 test_check (0 == 703 pthread_join (server_run_thr, 704 &pres)); 705 } 706 { 707 void *pres; 708 709 /* Unblock the #server_phase_logic() even if we had 710 an error */ 711 for (i = 0; NULL != phases[i].label; i++) 712 semaphore_up (&ctx.client_sem); 713 test_check (0 == 714 pthread_join (server_phase_thr, 715 &pres)); 716 } 717 718 MHD_daemon_destroy (d); 719 semaphore_destroy (&ctx.client_sem); 720 semaphore_destroy (&ctx.server_sem); 721 test_check (0 == close (p[0])); 722 test_check (0 == close (p[1])); 723 return res; 724 } 725 726 727 char * 728 MHDT_load_pem (const char *name) 729 { 730 char path[256]; 731 int fd; 732 struct stat s; 733 char *buf; 734 735 snprintf (path, 736 sizeof (path), 737 "data/%s", 738 name); 739 fd = open (path, 740 O_RDONLY); 741 if (-1 == fd) 742 { 743 fprintf (stderr, 744 "Failed to open %s: %s\n", 745 path, 746 strerror (errno)); 747 return NULL; 748 } 749 if (0 != 750 fstat (fd, 751 &s)) 752 { 753 fprintf (stderr, 754 "Failed to fstat %s: %s\n", 755 path, 756 strerror (errno)); 757 (void) close (fd); 758 return NULL; 759 } 760 if (((unsigned long long) s.st_size) >= (unsigned long long) SIZE_MAX) 761 { 762 fprintf (stderr, 763 "File %s too large (%llu >= %llu bytes) to malloc()\n", 764 path, 765 (unsigned long long) s.st_size, 766 (unsigned long long) SIZE_MAX); 767 (void) close (fd); 768 return NULL; 769 } 770 buf = (char *) malloc (((size_t) s.st_size + 1)); 771 if (NULL == buf) 772 { 773 fprintf (stderr, 774 "Failed to malloc(): %s\n", 775 strerror (errno)); 776 (void) close (fd); 777 return NULL; 778 } 779 if (-1 == 780 read (fd, // FIXME: read() should be called in loop to handle partial reads 781 buf, 782 (size_t) s.st_size)) 783 { 784 fprintf (stderr, 785 "Failed to read %s: %s\n", 786 path, 787 strerror (errno)); 788 free (buf); 789 (void) close (fd); 790 return NULL; 791 } 792 (void) close (fd); 793 buf[(size_t) s.st_size] = 0; 794 return buf; 795 }