exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-httpd_reserves_get.c (7808B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2024 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_reserves_get.c
     18  * @brief Handle /reserves/$RESERVE_PUB GET requests
     19  * @author Florian Dold
     20  * @author Benedikt Mueller
     21  * @author Christian Grothoff
     22  */
     23 #include "taler/platform.h"
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <jansson.h>
     26 #include "taler/taler_mhd_lib.h"
     27 #include "taler/taler_json_lib.h"
     28 #include "taler/taler_dbevents.h"
     29 #include "taler-exchange-httpd_keys.h"
     30 #include "taler-exchange-httpd_reserves_get.h"
     31 #include "taler-exchange-httpd_responses.h"
     32 
     33 
     34 /**
     35  * Reserve GET request that is long-polling.
     36  */
     37 struct ReservePoller
     38 {
     39   /**
     40    * Kept in a DLL.
     41    */
     42   struct ReservePoller *next;
     43 
     44   /**
     45    * Kept in a DLL.
     46    */
     47   struct ReservePoller *prev;
     48 
     49   /**
     50    * Connection we are handling.
     51    */
     52   struct MHD_Connection *connection;
     53 
     54   /**
     55    * Our request context.
     56    */
     57   struct TEH_RequestContext *rc;
     58 
     59   /**
     60    * Subscription for the database event we are waiting for.
     61    */
     62   struct GNUNET_DB_EventHandler *eh;
     63 
     64   /**
     65    * When will this request time out?
     66    */
     67   struct GNUNET_TIME_Absolute timeout;
     68 
     69   /**
     70    * Public key of the reserve the inquiry is about.
     71    */
     72   struct TALER_ReservePublicKeyP reserve_pub;
     73 
     74   /**
     75    * Balance of the reserve, set in the callback.
     76    */
     77   struct TALER_Amount balance;
     78 
     79   /**
     80    * Last origin account of the reserve, NULL if only
     81    * P2P payments were made.
     82    */
     83   struct TALER_FullPayto origin_account;
     84 
     85   /**
     86    * True if we are still suspended.
     87    */
     88   bool suspended;
     89 
     90 };
     91 
     92 
     93 /**
     94  * Head of list of requests in long polling.
     95  */
     96 static struct ReservePoller *rp_head;
     97 
     98 /**
     99  * Tail of list of requests in long polling.
    100  */
    101 static struct ReservePoller *rp_tail;
    102 
    103 
    104 void
    105 TEH_reserves_get_cleanup ()
    106 {
    107   for (struct ReservePoller *rp = rp_head;
    108        NULL != rp;
    109        rp = rp->next)
    110   {
    111     if (rp->suspended)
    112     {
    113       rp->suspended = false;
    114       MHD_resume_connection (rp->connection);
    115     }
    116   }
    117 }
    118 
    119 
    120 /**
    121  * Function called once a connection is done to
    122  * clean up the `struct ReservePoller` state.
    123  *
    124  * @param rc context to clean up for
    125  */
    126 static void
    127 rp_cleanup (struct TEH_RequestContext *rc)
    128 {
    129   struct ReservePoller *rp = rc->rh_ctx;
    130 
    131   GNUNET_assert (! rp->suspended);
    132   if (NULL != rp->eh)
    133   {
    134     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    135                 "Cancelling DB event listening on cleanup (odd unless during shutdown)\n");
    136     TEH_plugin->event_listen_cancel (TEH_plugin->cls,
    137                                      rp->eh);
    138     rp->eh = NULL;
    139   }
    140   GNUNET_CONTAINER_DLL_remove (rp_head,
    141                                rp_tail,
    142                                rp);
    143   GNUNET_free (rp->origin_account.full_payto);
    144   GNUNET_free (rp);
    145 }
    146 
    147 
    148 /**
    149  * Function called on events received from Postgres.
    150  * Wakes up long pollers.
    151  *
    152  * @param cls the `struct TEH_RequestContext *`
    153  * @param extra additional event data provided
    154  * @param extra_size number of bytes in @a extra
    155  */
    156 static void
    157 db_event_cb (void *cls,
    158              const void *extra,
    159              size_t extra_size)
    160 {
    161   struct ReservePoller *rp = cls;
    162   struct GNUNET_AsyncScopeSave old_scope;
    163 
    164   (void) extra;
    165   (void) extra_size;
    166   if (! rp->suspended)
    167     return; /* might get multiple wake-up events */
    168   GNUNET_async_scope_enter (&rp->rc->async_scope_id,
    169                             &old_scope);
    170   TEH_check_invariants ();
    171   rp->suspended = false;
    172   MHD_resume_connection (rp->connection);
    173   TALER_MHD_daemon_trigger ();
    174   TEH_check_invariants ();
    175   GNUNET_async_scope_restore (&old_scope);
    176 }
    177 
    178 
    179 MHD_RESULT
    180 TEH_handler_reserves_get (
    181   struct TEH_RequestContext *rc,
    182   const struct TALER_ReservePublicKeyP *reserve_pub)
    183 {
    184   struct ReservePoller *rp = rc->rh_ctx;
    185 
    186   if (NULL == rp)
    187   {
    188     rp = GNUNET_new (struct ReservePoller);
    189     rp->connection = rc->connection;
    190     rp->rc = rc;
    191     rc->rh_ctx = rp;
    192     rc->rh_cleaner = &rp_cleanup;
    193     GNUNET_CONTAINER_DLL_insert (rp_head,
    194                                  rp_tail,
    195                                  rp);
    196     rp->reserve_pub = *reserve_pub;
    197     TALER_MHD_parse_request_timeout (rc->connection,
    198                                      &rp->timeout);
    199   }
    200 
    201   if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) &&
    202        (NULL == rp->eh) )
    203   {
    204     struct TALER_ReserveEventP rep = {
    205       .header.size = htons (sizeof (rep)),
    206       .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
    207       .reserve_pub = rp->reserve_pub
    208     };
    209 
    210     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    211                 "Starting DB event listening until %s\n",
    212                 GNUNET_TIME_absolute2s (rp->timeout));
    213     rp->eh = TEH_plugin->event_listen (
    214       TEH_plugin->cls,
    215       GNUNET_TIME_absolute_get_remaining (rp->timeout),
    216       &rep.header,
    217       &db_event_cb,
    218       rp);
    219   }
    220   {
    221     enum GNUNET_DB_QueryStatus qs;
    222 
    223     GNUNET_free (rp->origin_account.full_payto);
    224     qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
    225                                           &rp->reserve_pub,
    226                                           &rp->balance,
    227                                           &rp->origin_account);
    228     switch (qs)
    229     {
    230     case GNUNET_DB_STATUS_SOFT_ERROR:
    231       GNUNET_break (0); /* single-shot query should never have soft-errors */
    232       return TALER_MHD_reply_with_error (rc->connection,
    233                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    234                                          TALER_EC_GENERIC_DB_SOFT_FAILURE,
    235                                          "get_reserve_balance");
    236     case GNUNET_DB_STATUS_HARD_ERROR:
    237       GNUNET_break (0);
    238       return TALER_MHD_reply_with_error (rc->connection,
    239                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    240                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    241                                          "get_reserve_balance");
    242     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    243       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    244                   "Got reserve balance of %s\n",
    245                   TALER_amount2s (&rp->balance));
    246       return TALER_MHD_REPLY_JSON_PACK (
    247         rc->connection,
    248         MHD_HTTP_OK,
    249         GNUNET_JSON_pack_allow_null (
    250           GNUNET_JSON_pack_string (
    251             "last_origin",
    252             rp->origin_account.full_payto)),
    253         TALER_JSON_pack_amount ("balance",
    254                                 &rp->balance));
    255     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    256       if (! GNUNET_TIME_absolute_is_future (rp->timeout))
    257       {
    258         return TALER_MHD_reply_with_error (rc->connection,
    259                                            MHD_HTTP_NOT_FOUND,
    260                                            TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
    261                                            NULL);
    262       }
    263       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    264                   "Long-polling on reserve for %s\n",
    265                   GNUNET_STRINGS_relative_time_to_string (
    266                     GNUNET_TIME_absolute_get_remaining (rp->timeout),
    267                     true));
    268       rp->suspended = true;
    269       MHD_suspend_connection (rc->connection);
    270       return MHD_YES;
    271     }
    272   }
    273   GNUNET_break (0);
    274   return MHD_NO;
    275 }
    276 
    277 
    278 /* end of taler-exchange-httpd_reserves_get.c */