libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

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 }