LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_purses_get.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 0 123 0.0 %
Date: 2022-08-25 06:15:09 Functions: 0 4 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2022 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU Affero General Public License as published by the Free Software
       7             :   Foundation; either version 3, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU Affero General Public License along with
      14             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             : */
      16             : /**
      17             :  * @file taler-exchange-httpd_purses_get.c
      18             :  * @brief Handle GET /purses/$PID/$TARGET requests
      19             :  * @author Christian Grothoff
      20             :  */
      21             : #include "platform.h"
      22             : #include <gnunet/gnunet_util_lib.h>
      23             : #include <jansson.h>
      24             : #include <microhttpd.h>
      25             : #include "taler_mhd_lib.h"
      26             : #include "taler_dbevents.h"
      27             : #include "taler-exchange-httpd_keys.h"
      28             : #include "taler-exchange-httpd_purses_get.h"
      29             : #include "taler-exchange-httpd_mhd.h"
      30             : #include "taler-exchange-httpd_responses.h"
      31             : 
      32             : 
      33             : /**
      34             :  * Information about an ongoing /purses GET operation.
      35             :  */
      36             : struct GetContext
      37             : {
      38             :   /**
      39             :    * Kept in a DLL.
      40             :    */
      41             :   struct GetContext *next;
      42             : 
      43             :   /**
      44             :    * Kept in a DLL.
      45             :    */
      46             :   struct GetContext *prev;
      47             : 
      48             :   /**
      49             :    * Connection we are handling.
      50             :    */
      51             :   struct MHD_Connection *connection;
      52             : 
      53             :   /**
      54             :    * Subscription for the database event we are
      55             :    * waiting for.
      56             :    */
      57             :   struct GNUNET_DB_EventHandler *eh;
      58             : 
      59             :   /**
      60             :    * Public key of our purse.
      61             :    */
      62             :   struct TALER_PurseContractPublicKeyP purse_pub;
      63             : 
      64             :   /**
      65             :    * When does this purse expire?
      66             :    */
      67             :   struct GNUNET_TIME_Timestamp purse_expiration;
      68             : 
      69             :   /**
      70             :    * When was this purse merged?
      71             :    */
      72             :   struct GNUNET_TIME_Timestamp merge_timestamp;
      73             : 
      74             :   /**
      75             :    * How much is the purse (supposed) to be worth?
      76             :    */
      77             :   struct TALER_Amount amount;
      78             : 
      79             :   /**
      80             :    * How much was deposited into the purse so far?
      81             :    */
      82             :   struct TALER_Amount deposited;
      83             : 
      84             :   /**
      85             :    * Hash over the contract of the purse.
      86             :    */
      87             :   struct TALER_PrivateContractHashP h_contract;
      88             : 
      89             :   /**
      90             :    * When will this request time out?
      91             :    */
      92             :   struct GNUNET_TIME_Absolute timeout;
      93             : 
      94             :   /**
      95             :    * true to wait for merge, false to wait for deposit.
      96             :    */
      97             :   bool wait_for_merge;
      98             : 
      99             :   /**
     100             :    * True if we are still suspended.
     101             :    */
     102             :   bool suspended;
     103             : };
     104             : 
     105             : 
     106             : /**
     107             :  * Head of DLL of suspended GET requests.
     108             :  */
     109             : static struct GetContext *gc_head;
     110             : 
     111             : /**
     112             :  * Tail of DLL of suspended GET requests.
     113             :  */
     114             : static struct GetContext *gc_tail;
     115             : 
     116             : 
     117             : void
     118           0 : TEH_purses_get_cleanup ()
     119             : {
     120             :   struct GetContext *gc;
     121             : 
     122           0 :   while (NULL != (gc = gc_head))
     123             :   {
     124           0 :     GNUNET_CONTAINER_DLL_remove (gc_head,
     125             :                                  gc_tail,
     126             :                                  gc);
     127           0 :     if (gc->suspended)
     128             :     {
     129           0 :       gc->suspended = false;
     130           0 :       MHD_resume_connection (gc->connection);
     131             :     }
     132             :   }
     133           0 : }
     134             : 
     135             : 
     136             : /**
     137             :  * Function called once a connection is done to
     138             :  * clean up the `struct GetContext` state.
     139             :  *
     140             :  * @param rc context to clean up for
     141             :  */
     142             : static void
     143           0 : gc_cleanup (struct TEH_RequestContext *rc)
     144             : {
     145           0 :   struct GetContext *gc = rc->rh_ctx;
     146             : 
     147           0 :   GNUNET_assert (! gc->suspended);
     148           0 :   if (NULL != gc->eh)
     149             :   {
     150           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     151             :                 "Cancelling DB event listening\n");
     152           0 :     TEH_plugin->event_listen_cancel (TEH_plugin->cls,
     153             :                                      gc->eh);
     154           0 :     gc->eh = NULL;
     155             :   }
     156           0 :   GNUNET_free (gc);
     157           0 : }
     158             : 
     159             : 
     160             : /**
     161             :  * Function called on events received from Postgres.
     162             :  * Wakes up long pollers.
     163             :  *
     164             :  * @param cls the `struct TEH_RequestContext *`
     165             :  * @param extra additional event data provided
     166             :  * @param extra_size number of bytes in @a extra
     167             :  */
     168             : static void
     169           0 : db_event_cb (void *cls,
     170             :              const void *extra,
     171             :              size_t extra_size)
     172             : {
     173           0 :   struct TEH_RequestContext *rc = cls;
     174           0 :   struct GetContext *gc = rc->rh_ctx;
     175             :   struct GNUNET_AsyncScopeSave old_scope;
     176             : 
     177             :   (void) extra;
     178             :   (void) extra_size;
     179           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     180             :               "Waking up on %p - %p - %s\n",
     181             :               rc,
     182             :               gc,
     183             :               gc->suspended ? "suspended" : "active");
     184           0 :   if (NULL == gc)
     185           0 :     return; /* event triggered while main transaction
     186             :                was still running */
     187           0 :   if (! gc->suspended)
     188           0 :     return; /* might get multiple wake-up events */
     189           0 :   gc->suspended = false;
     190           0 :   GNUNET_async_scope_enter (&rc->async_scope_id,
     191             :                             &old_scope);
     192           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     193             :               "Resuming from long-polling on purse\n");
     194           0 :   TEH_check_invariants ();
     195           0 :   GNUNET_CONTAINER_DLL_remove (gc_head,
     196             :                                gc_tail,
     197             :                                gc);
     198           0 :   MHD_resume_connection (gc->connection);
     199           0 :   TALER_MHD_daemon_trigger ();
     200           0 :   TEH_check_invariants ();
     201           0 :   GNUNET_async_scope_restore (&old_scope);
     202             : }
     203             : 
     204             : 
     205             : MHD_RESULT
     206           0 : TEH_handler_purses_get (struct TEH_RequestContext *rc,
     207             :                         const char *const args[2])
     208             : {
     209           0 :   struct GetContext *gc = rc->rh_ctx;
     210             :   MHD_RESULT res;
     211             : 
     212           0 :   if (NULL == gc)
     213             :   {
     214           0 :     gc = GNUNET_new (struct GetContext);
     215           0 :     rc->rh_ctx = gc;
     216           0 :     rc->rh_cleaner = &gc_cleanup;
     217           0 :     gc->connection = rc->connection;
     218           0 :     if (GNUNET_OK !=
     219           0 :         GNUNET_STRINGS_string_to_data (args[0],
     220             :                                        strlen (args[0]),
     221           0 :                                        &gc->purse_pub,
     222             :                                        sizeof (gc->purse_pub)))
     223             :     {
     224           0 :       GNUNET_break_op (0);
     225           0 :       return TALER_MHD_reply_with_error (rc->connection,
     226             :                                          MHD_HTTP_BAD_REQUEST,
     227             :                                          TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
     228             :                                          args[0]);
     229             :     }
     230           0 :     if (0 == strcmp (args[1],
     231             :                      "merge"))
     232           0 :       gc->wait_for_merge = true;
     233           0 :     else if (0 == strcmp (args[1],
     234             :                           "deposit"))
     235           0 :       gc->wait_for_merge = false;
     236             :     else
     237             :     {
     238           0 :       GNUNET_break_op (0);
     239           0 :       return TALER_MHD_reply_with_error (rc->connection,
     240             :                                          MHD_HTTP_BAD_REQUEST,
     241             :                                          TALER_EC_EXCHANGE_PURSES_INVALID_WAIT_TARGET,
     242           0 :                                          args[1]);
     243             :     }
     244             : 
     245             :     {
     246             :       const char *long_poll_timeout_ms;
     247             : 
     248             :       long_poll_timeout_ms
     249           0 :         = MHD_lookup_connection_value (rc->connection,
     250             :                                        MHD_GET_ARGUMENT_KIND,
     251             :                                        "timeout_ms");
     252           0 :       if (NULL != long_poll_timeout_ms)
     253             :       {
     254             :         unsigned int timeout_ms;
     255             :         char dummy;
     256             :         struct GNUNET_TIME_Relative timeout;
     257             : 
     258           0 :         if (1 != sscanf (long_poll_timeout_ms,
     259             :                          "%u%c",
     260             :                          &timeout_ms,
     261             :                          &dummy))
     262             :         {
     263           0 :           GNUNET_break_op (0);
     264           0 :           return TALER_MHD_reply_with_error (rc->connection,
     265             :                                              MHD_HTTP_BAD_REQUEST,
     266             :                                              TALER_EC_GENERIC_PARAMETER_MALFORMED,
     267             :                                              "timeout_ms (must be non-negative number)");
     268             :         }
     269           0 :         timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
     270             :                                                  timeout_ms);
     271           0 :         gc->timeout = GNUNET_TIME_relative_to_absolute (timeout);
     272             :       }
     273             :     }
     274             : 
     275           0 :     if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) &&
     276           0 :          (NULL == gc->eh) )
     277             :     {
     278           0 :       struct TALER_PurseEventP rep = {
     279           0 :         .header.size = htons (sizeof (rep)),
     280           0 :         .header.type = htons (
     281           0 :           gc->wait_for_merge
     282             :           ? TALER_DBEVENT_EXCHANGE_PURSE_MERGED
     283             :           : TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
     284             :         .purse_pub = gc->purse_pub
     285             :       };
     286             : 
     287           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     288             :                   "Starting DB event listening on purse %s\n",
     289             :                   TALER_B2S (&gc->purse_pub));
     290           0 :       gc->eh = TEH_plugin->event_listen (
     291           0 :         TEH_plugin->cls,
     292             :         GNUNET_TIME_absolute_get_remaining (gc->timeout),
     293             :         &rep.header,
     294             :         &db_event_cb,
     295             :         rc);
     296           0 :       if (NULL == gc->eh)
     297             :       {
     298           0 :         GNUNET_break (0);
     299           0 :         gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
     300             :       }
     301             :     }
     302             :   } /* end first-time initialization */
     303             : 
     304             :   {
     305             :     enum GNUNET_DB_QueryStatus qs;
     306             : 
     307           0 :     qs = TEH_plugin->select_purse (TEH_plugin->cls,
     308           0 :                                    &gc->purse_pub,
     309             :                                    &gc->purse_expiration,
     310             :                                    &gc->amount,
     311             :                                    &gc->deposited,
     312             :                                    &gc->h_contract,
     313             :                                    &gc->merge_timestamp);
     314           0 :     switch (qs)
     315             :     {
     316           0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     317           0 :       GNUNET_break (0);
     318           0 :       return TALER_MHD_reply_with_error (rc->connection,
     319             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     320             :                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
     321             :                                          "select_purse");
     322           0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
     323           0 :       GNUNET_break (0);
     324           0 :       return TALER_MHD_reply_with_error (rc->connection,
     325             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     326             :                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
     327             :                                          "select_purse");
     328           0 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     329           0 :       return TALER_MHD_reply_with_error (rc->connection,
     330             :                                          MHD_HTTP_NOT_FOUND,
     331             :                                          TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
     332             :                                          NULL);
     333           0 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     334           0 :       break; /* handled below */
     335             :     }
     336           0 :     if (GNUNET_TIME_absolute_cmp (gc->timeout,
     337             :                                   >,
     338             :                                   gc->purse_expiration.abs_time))
     339             :     {
     340             :       /* Timeout too high, need to replace event handler */
     341           0 :       struct TALER_PurseEventP rep = {
     342           0 :         .header.size = htons (sizeof (rep)),
     343           0 :         .header.type = htons (
     344           0 :           gc->wait_for_merge
     345             :           ? TALER_DBEVENT_EXCHANGE_PURSE_MERGED
     346             :           : TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
     347             :         .purse_pub = gc->purse_pub
     348             :       };
     349             :       struct GNUNET_DB_EventHandler *eh2;
     350             : 
     351           0 :       gc->timeout = gc->purse_expiration.abs_time;
     352           0 :       eh2 = TEH_plugin->event_listen (
     353           0 :         TEH_plugin->cls,
     354             :         GNUNET_TIME_absolute_get_remaining (gc->timeout),
     355             :         &rep.header,
     356             :         &db_event_cb,
     357             :         rc);
     358           0 :       if (NULL == eh2)
     359             :       {
     360           0 :         GNUNET_break (0);
     361           0 :         gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
     362             :       }
     363           0 :       TEH_plugin->event_listen_cancel (TEH_plugin->cls,
     364             :                                        gc->eh);
     365           0 :       gc->eh = eh2;
     366             :     }
     367             :   }
     368           0 :   if (GNUNET_TIME_absolute_is_past (gc->purse_expiration.abs_time))
     369             :   {
     370           0 :     return TALER_MHD_reply_with_error (rc->connection,
     371             :                                        MHD_HTTP_GONE,
     372             :                                        TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
     373             :                                        GNUNET_TIME_timestamp2s (
     374             :                                          gc->purse_expiration));
     375             :   }
     376             : 
     377           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     378             :               "Deposited amount is %s\n",
     379             :               TALER_amount2s (&gc->deposited));
     380           0 :   if (GNUNET_TIME_absolute_is_future (gc->timeout) &&
     381           0 :       ( ((gc->wait_for_merge) &&
     382           0 :          GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time)) ||
     383           0 :         ((! gc->wait_for_merge) &&
     384             :          (0 <
     385           0 :           TALER_amount_cmp (&gc->amount,
     386           0 :                             &gc->deposited))) ) )
     387             :   {
     388           0 :     gc->suspended = true;
     389           0 :     GNUNET_CONTAINER_DLL_insert (gc_head,
     390             :                                  gc_tail,
     391             :                                  gc);
     392           0 :     MHD_suspend_connection (gc->connection);
     393           0 :     return MHD_YES;
     394             :   }
     395             : 
     396             :   {
     397           0 :     struct GNUNET_TIME_Timestamp dt = GNUNET_TIME_timestamp_get ();
     398             :     struct TALER_ExchangePublicKeyP exchange_pub;
     399             :     struct TALER_ExchangeSignatureP exchange_sig;
     400             :     enum TALER_ErrorCode ec;
     401             : 
     402           0 :     if (GNUNET_TIME_timestamp_cmp (dt,
     403             :                                    >,
     404             :                                    gc->purse_expiration))
     405           0 :       dt = gc->purse_expiration;
     406           0 :     if (0 <
     407           0 :         TALER_amount_cmp (&gc->amount,
     408           0 :                           &gc->deposited))
     409           0 :       dt = GNUNET_TIME_UNIT_ZERO_TS;
     410           0 :     if (TALER_EC_NONE !=
     411           0 :         (ec = TALER_exchange_online_purse_status_sign (
     412             :            &TEH_keys_exchange_sign_,
     413             :            gc->merge_timestamp,
     414             :            dt,
     415           0 :            &gc->deposited,
     416             :            &exchange_pub,
     417             :            &exchange_sig)))
     418           0 :       res = TALER_MHD_reply_with_ec (rc->connection,
     419             :                                      ec,
     420             :                                      NULL);
     421             :     else
     422           0 :       res = TALER_MHD_REPLY_JSON_PACK (
     423             :         rc->connection,
     424             :         MHD_HTTP_OK,
     425             :         TALER_JSON_pack_amount ("balance",
     426             :                                 &gc->deposited),
     427             :         GNUNET_JSON_pack_data_auto ("exchange_sig",
     428             :                                     &exchange_sig),
     429             :         GNUNET_JSON_pack_data_auto ("exchange_pub",
     430             :                                     &exchange_pub),
     431             :         GNUNET_JSON_pack_allow_null (
     432             :           GNUNET_JSON_pack_timestamp ("merge_timestamp",
     433             :                                       gc->merge_timestamp)),
     434             :         GNUNET_JSON_pack_allow_null (
     435             :           GNUNET_JSON_pack_timestamp ("deposit_timestamp",
     436             :                                       dt))
     437             :         );
     438             :   }
     439           0 :   return res;
     440             : }
     441             : 
     442             : 
     443             : /* end of taler-exchange-httpd_purses_get.c */

Generated by: LCOV version 1.14