LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_post-tips-ID-pickup.c (source / functions) Hit Total Coverage
Test: GNU Taler merchant coverage report Lines: 189 344 54.9 %
Date: 2021-08-30 06:54:17 Functions: 9 10 90.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   (C) 2017-2021 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU 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 General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU General Public License along with
      14             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             : */
      16             : /**
      17             :  * @file taler-merchant-httpd_post-tips-ID-pickup.c
      18             :  * @brief implementation of a POST /tips/ID/pickup handler
      19             :  * @author Christian Grothoff
      20             :  */
      21             : #include "platform.h"
      22             : #include <microhttpd.h>
      23             : #include <jansson.h>
      24             : #include <taler/taler_json_lib.h>
      25             : #include <taler/taler_signatures.h>
      26             : #include "taler-merchant-httpd.h"
      27             : #include "taler-merchant-httpd_mhd.h"
      28             : #include "taler-merchant-httpd_helper.h"
      29             : #include "taler-merchant-httpd_exchanges.h"
      30             : #include "taler-merchant-httpd_post-tips-ID-pickup.h"
      31             : 
      32             : 
      33             : /**
      34             :  * How often do we retry on serialization errors?
      35             :  */
      36             : #define MAX_RETRIES 3
      37             : 
      38             : /**
      39             :  * How long do we give the exchange operation to complete withdrawing
      40             :  * all of the planchets?
      41             :  */
      42             : #define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
      43             :     GNUNET_TIME_UNIT_SECONDS, 45)
      44             : 
      45             : 
      46             : /**
      47             :  * Active pickup operations.
      48             :  */
      49             : struct PickupContext;
      50             : 
      51             : 
      52             : /**
      53             :  * Handle for an individual planchet we are processing for a tip.
      54             :  */
      55             : struct PlanchetOperation
      56             : {
      57             :   /**
      58             :    * Active pickup operation this planchet belongs with.
      59             :    */
      60             :   struct PickupContext *pc;
      61             : 
      62             :   /**
      63             :    * Kept in a DLL.
      64             :    */
      65             :   struct PlanchetOperation *prev;
      66             : 
      67             :   /**
      68             :    * Kept in a DLL.
      69             :    */
      70             :   struct PlanchetOperation *next;
      71             : 
      72             :   /**
      73             :    * Find operation (while active), later NULL.
      74             :    */
      75             :   struct TMH_EXCHANGES_FindOperation *fo;
      76             : 
      77             :   /**
      78             :    * Withdraw handle (NULL while @e fo is active).
      79             :    */
      80             :   struct TALER_EXCHANGE_Withdraw2Handle *w2h;
      81             : 
      82             :   /**
      83             :    * Details about the planchet for withdrawing.
      84             :    */
      85             :   struct TALER_PlanchetDetail pd;
      86             : 
      87             :   /**
      88             :    * Offset of this planchet in the original request.
      89             :    */
      90             :   unsigned int offset;
      91             : };
      92             : 
      93             : 
      94             : /**
      95             :  * Active pickup operations.
      96             :  */
      97             : struct PickupContext
      98             : {
      99             :   /**
     100             :    * Kept in a DLL.
     101             :    */
     102             :   struct PickupContext *next;
     103             : 
     104             :   /**
     105             :    * Kept in a DLL.
     106             :    */
     107             :   struct PickupContext *prev;
     108             : 
     109             :   /**
     110             :    * The connection.
     111             :    */
     112             :   struct MHD_Connection *connection;
     113             : 
     114             :   /**
     115             :    * Timeout task.
     116             :    */
     117             :   struct GNUNET_SCHEDULER_Task *tt;
     118             : 
     119             :   /**
     120             :    * Head of DLL of exchange operations on planchets.
     121             :    */
     122             :   struct PlanchetOperation *po_head;
     123             : 
     124             :   /**
     125             :    * Tail of DLL of exchange operations on planchets.
     126             :    */
     127             :   struct PlanchetOperation *po_tail;
     128             : 
     129             :   /**
     130             :    * HTTP response to return (set on errors).
     131             :    */
     132             :   struct MHD_Response *response;
     133             : 
     134             :   /**
     135             :    * Find operation (while active), later NULL.
     136             :    */
     137             :   struct TMH_EXCHANGES_FindOperation *fo;
     138             : 
     139             :   /**
     140             :    * Which reserve are we draining?
     141             :    */
     142             :   struct TALER_ReservePrivateKeyP reserve_priv;
     143             : 
     144             :   /**
     145             :    * Which tip is being picked up?
     146             :    */
     147             :   struct GNUNET_HashCode tip_id;
     148             : 
     149             :   /**
     150             :    * What is the ID of the pickup operation? (Basically a
     151             :    * hash over the key inputs).
     152             :    */
     153             :   struct GNUNET_HashCode pickup_id;
     154             : 
     155             :   /**
     156             :    * Array of our planchets.
     157             :    */
     158             :   struct TALER_PlanchetDetail *planchets;
     159             : 
     160             :   /**
     161             :    * Length of the @e planchets array.
     162             :    */
     163             :   unsigned int planchets_length;
     164             : 
     165             :   /**
     166             :    * HTTP status to use (set on errors).
     167             :    */
     168             :   unsigned int http_status;
     169             : 
     170             :   /**
     171             :    * Total amount requested in the pick up operation. Computed by
     172             :    * totaling up the amounts of all the @e planchets.
     173             :    */
     174             :   struct TALER_Amount total_requested;
     175             : 
     176             :   /**
     177             :    * True if @e total_requested has been initialized.
     178             :    */
     179             :   bool tr_initialized;
     180             : };
     181             : 
     182             : 
     183             : /**
     184             :  * Head of DLL.
     185             :  */
     186             : static struct PickupContext *pc_head;
     187             : 
     188             : /**
     189             :  * Tail of DLL.
     190             :  */
     191             : static struct PickupContext *pc_tail;
     192             : 
     193             : 
     194             : /**
     195             :  * Stop all ongoing operations associated with @a pc.
     196             :  */
     197             : static void
     198          12 : stop_operations (struct PickupContext *pc)
     199             : {
     200             :   struct PlanchetOperation *po;
     201             : 
     202          12 :   if (NULL != pc->tt)
     203             :   {
     204           7 :     GNUNET_SCHEDULER_cancel (pc->tt);
     205           7 :     pc->tt = NULL;
     206             :   }
     207          12 :   if (NULL != pc->fo)
     208             :   {
     209           0 :     TMH_EXCHANGES_find_exchange_cancel (pc->fo);
     210           0 :     pc->fo = NULL;
     211             :   }
     212          12 :   while (NULL != (po = pc->po_head))
     213             :   {
     214           0 :     if (NULL != po->fo)
     215             :     {
     216           0 :       TMH_EXCHANGES_find_exchange_cancel (po->fo);
     217           0 :       po->fo = NULL;
     218             :     }
     219           0 :     if (NULL != po->w2h)
     220             :     {
     221           0 :       TALER_EXCHANGE_withdraw2_cancel (po->w2h);
     222           0 :       po->w2h = NULL;
     223             :     }
     224           0 :     GNUNET_CONTAINER_DLL_remove (pc->po_head,
     225             :                                  pc->po_tail,
     226             :                                  po);
     227           0 :     GNUNET_free (po);
     228             :   }
     229          12 : }
     230             : 
     231             : 
     232             : /**
     233             :  * Function called to clean up.
     234             :  *
     235             :  * @param cls a `struct PickupContext *` to clean up
     236             :  */
     237             : static void
     238           5 : pick_context_cleanup (void *cls)
     239             : {
     240           5 :   struct PickupContext *pc = cls;
     241             : 
     242           5 :   stop_operations (pc); /* should not be any... */
     243          10 :   for (unsigned int i = 0; i<pc->planchets_length; i++)
     244           5 :     GNUNET_free (pc->planchets[i].coin_ev);
     245           5 :   GNUNET_array_grow (pc->planchets,
     246             :                      pc->planchets_length,
     247             :                      0);
     248           5 :   GNUNET_free (pc);
     249           5 : }
     250             : 
     251             : 
     252             : /**
     253             :  * We are shutting down, force resuming all suspended pickup operations.
     254             :  */
     255             : void
     256          16 : TMH_force_tip_pickup_resume ()
     257             : {
     258          16 :   for (struct PickupContext *pc = pc_head;
     259             :        NULL != pc;
     260           0 :        pc = pc->next)
     261             :   {
     262           0 :     stop_operations (pc);
     263           0 :     MHD_resume_connection (pc->connection);
     264             :   }
     265          16 : }
     266             : 
     267             : 
     268             : /**
     269             :  * Callbacks of this type are used to serve the result of submitting a
     270             :  * withdraw request to a exchange without the (un)blinding factor.
     271             :  * We persist the result in the database and, if we were the last
     272             :  * planchet operation, resume HTTP processing.
     273             :  *
     274             :  * @param cls closure with a `struct PlanchetOperation *`
     275             :  * @param hr HTTP response data
     276             :  * @param blind_sig blind signature over the coin, NULL on error
     277             :  */
     278             : static void
     279           3 : withdraw_cb (void *cls,
     280             :              const struct TALER_EXCHANGE_HttpResponse *hr,
     281             :              const struct GNUNET_CRYPTO_RsaSignature *blind_sig)
     282             : {
     283           3 :   struct PlanchetOperation *po = cls;
     284           3 :   struct PickupContext *pc = po->pc;
     285             :   enum GNUNET_DB_QueryStatus qs;
     286             : 
     287           3 :   GNUNET_CONTAINER_DLL_remove (pc->po_head,
     288             :                                pc->po_tail,
     289             :                                po);
     290           3 :   if (NULL == blind_sig)
     291             :   {
     292           0 :     GNUNET_free (po);
     293           0 :     stop_operations (pc);
     294           0 :     pc->http_status = MHD_HTTP_BAD_GATEWAY;
     295           0 :     pc->response =
     296           0 :       TALER_MHD_MAKE_JSON_PACK (
     297             :         TALER_JSON_pack_ec (TALER_EC_MERCHANT_TIP_PICKUP_EXCHANGE_ERROR),
     298             :         TMH_pack_exchange_reply (hr));
     299           0 :     MHD_resume_connection (pc->connection);
     300           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     301           0 :     return;
     302             :   }
     303           3 :   qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls,
     304           3 :                                               &pc->pickup_id,
     305             :                                               po->offset,
     306             :                                               blind_sig);
     307           3 :   GNUNET_free (po);
     308           3 :   if (qs < 0)
     309             :   {
     310           0 :     stop_operations (pc);
     311           0 :     pc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
     312           0 :     pc->response = TALER_MHD_make_error (
     313             :       TALER_EC_GENERIC_DB_STORE_FAILED,
     314             :       "blind signature");
     315           0 :     MHD_resume_connection (pc->connection);
     316           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     317           0 :     return;
     318             :   }
     319           3 :   if (NULL == pc->po_head)
     320             :   {
     321           3 :     stop_operations (pc); /* stops timeout job */
     322           3 :     MHD_resume_connection (pc->connection);
     323           3 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     324             :   }
     325             : }
     326             : 
     327             : 
     328             : /**
     329             :  * Function called with the result of a #TMH_EXCHANGES_find_exchange()
     330             :  * operation as part of a withdraw objective.  If the exchange is ready,
     331             :  * withdraws the planchet from the exchange.
     332             :  *
     333             :  * @param cls closure, with our `struct PlanchetOperation *`
     334             :  * @param hr HTTP response details
     335             :  * @param eh handle to the exchange context
     336             :  * @param payto_uri payto://-URI of the exchange
     337             :  * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
     338             :  * @param exchange_trusted true if this exchange is trusted by config
     339             :  */
     340             : static void
     341           3 : do_withdraw (void *cls,
     342             :              const struct TALER_EXCHANGE_HttpResponse *hr,
     343             :              struct TALER_EXCHANGE_Handle *eh,
     344             :              const char *payto_uri,
     345             :              const struct TALER_Amount *wire_fee,
     346             :              bool exchange_trusted)
     347             : {
     348           3 :   struct PlanchetOperation *po = cls;
     349           3 :   struct PickupContext *pc = po->pc;
     350             : 
     351           3 :   po->fo = NULL;
     352           3 :   if (NULL == hr)
     353             :   {
     354           0 :     stop_operations (pc);
     355           0 :     GNUNET_CONTAINER_DLL_remove (pc->po_head,
     356             :                                  pc->po_tail,
     357             :                                  po);
     358           0 :     GNUNET_free (po);
     359           0 :     pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
     360           0 :     pc->response = TALER_MHD_MAKE_JSON_PACK (
     361             :       TALER_JSON_pack_ec (
     362             :         TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
     363           0 :     MHD_resume_connection (pc->connection);
     364           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     365           0 :     return;
     366             :   }
     367           3 :   if (NULL == eh)
     368             :   {
     369           0 :     stop_operations (pc);
     370           0 :     GNUNET_CONTAINER_DLL_remove (pc->po_head,
     371             :                                  pc->po_tail,
     372             :                                  po);
     373           0 :     GNUNET_free (po);
     374           0 :     pc->http_status = MHD_HTTP_BAD_GATEWAY;
     375           0 :     pc->response =
     376           0 :       TALER_MHD_MAKE_JSON_PACK (
     377             :         TALER_JSON_pack_ec (
     378             :           TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
     379             :         TMH_pack_exchange_reply (hr));
     380           0 :     MHD_resume_connection (pc->connection);
     381           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     382           0 :     return;
     383             :   }
     384           3 :   po->w2h = TALER_EXCHANGE_withdraw2 (eh,
     385           3 :                                       &po->pd,
     386           3 :                                       &pc->reserve_priv,
     387             :                                       &withdraw_cb,
     388             :                                       po);
     389             : }
     390             : 
     391             : 
     392             : /**
     393             :  * Withdraw @a planchet from @a exchange_url for @a pc operation at planchet
     394             :  * @a offset.  Sets up the respective operation and adds it @a pc's operation
     395             :  * list. Once the operation is complete, the resulting blind signature is
     396             :  * committed to the merchant's database. If all planchet operations are
     397             :  * completed, the HTTP processing is resumed.
     398             :  *
     399             :  * @param[in,out] pc a pending pickup operation that includes @a planchet
     400             :  * @param exchange_url identifies an exchange to do the pickup from
     401             :  * @param planchet details about the coin to pick up
     402             :  * @param offset offset of @a planchet in the list, needed to process the reply
     403             :  */
     404             : static void
     405           3 : try_withdraw (struct PickupContext *pc,
     406             :               const char *exchange_url,
     407             :               const struct TALER_PlanchetDetail *planchet,
     408             :               unsigned int offset)
     409             : {
     410             :   struct PlanchetOperation *po;
     411             : 
     412           3 :   po = GNUNET_new (struct PlanchetOperation);
     413           3 :   po->pc = pc;
     414           3 :   po->pd = *planchet;
     415           3 :   po->offset = offset;
     416           3 :   po->fo = TMH_EXCHANGES_find_exchange (exchange_url,
     417             :                                         NULL,
     418             :                                         GNUNET_NO,
     419             :                                         &do_withdraw,
     420             :                                         po);
     421           3 :   GNUNET_assert (NULL != po->fo);
     422           3 :   GNUNET_CONTAINER_DLL_insert (pc->po_head,
     423             :                                pc->po_tail,
     424             :                                po);
     425           3 : }
     426             : 
     427             : 
     428             : /**
     429             :  * Handle timeout for pickup.
     430             :  *
     431             :  * @param cls a `struct PickupContext *`
     432             :  */
     433             : static void
     434           0 : do_timeout (void *cls)
     435             : {
     436           0 :   struct PickupContext *pc = cls;
     437             : 
     438           0 :   pc->tt = NULL;
     439           0 :   stop_operations (pc);
     440           0 :   pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
     441           0 :   pc->response =  TALER_MHD_make_error (
     442             :     TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
     443             :     NULL);
     444           0 :   MHD_resume_connection (pc->connection);
     445           0 :   TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     446           0 : }
     447             : 
     448             : 
     449             : /**
     450             :  * Function called with the result of a #TMH_EXCHANGES_find_exchange()
     451             :  * operation as part of a withdraw objective.  Here, we initialize
     452             :  * the "total_requested" amount by adding up the cost of the planchets
     453             :  * provided by the client.
     454             :  *
     455             :  * @param cls closure, with our `struct PickupContext *`
     456             :  * @param hr HTTP response details
     457             :  * @param eh handle to the exchange context
     458             :  * @param payto_uri payto://-URI of the exchange
     459             :  * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
     460             :  * @param exchange_trusted true if this exchange is trusted by config
     461             :  */
     462             : static void
     463           4 : compute_total_requested (void *cls,
     464             :                          const struct TALER_EXCHANGE_HttpResponse *hr,
     465             :                          struct TALER_EXCHANGE_Handle *eh,
     466             :                          const char *payto_uri,
     467             :                          const struct TALER_Amount *wire_fee,
     468             :                          bool exchange_trusted)
     469             : {
     470           4 :   struct PickupContext *pc = cls;
     471             :   const struct TALER_EXCHANGE_Keys *keys;
     472             : 
     473           4 :   pc->fo = NULL;
     474           4 :   stop_operations (pc); /* stops timeout job */
     475           4 :   if (NULL == hr)
     476             :   {
     477           0 :     pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
     478           0 :     pc->response = TALER_MHD_MAKE_JSON_PACK (
     479             :       TALER_JSON_pack_ec (
     480             :         TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
     481           0 :     MHD_resume_connection (pc->connection);
     482           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     483           0 :     return;
     484             :   }
     485           4 :   if (NULL == eh)
     486             :   {
     487           0 :     pc->http_status = MHD_HTTP_BAD_GATEWAY;
     488           0 :     pc->response =
     489           0 :       TALER_MHD_MAKE_JSON_PACK (
     490             :         TALER_JSON_pack_ec (
     491             :           TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
     492             :         TMH_pack_exchange_reply (hr));
     493           0 :     MHD_resume_connection (pc->connection);
     494           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     495           0 :     return;
     496             :   }
     497           4 :   if (NULL == (keys = TALER_EXCHANGE_get_keys (eh)))
     498             :   {
     499           0 :     pc->http_status = MHD_HTTP_BAD_GATEWAY;
     500           0 :     pc->response =
     501           0 :       TALER_MHD_MAKE_JSON_PACK (
     502             :         TALER_JSON_pack_ec (
     503             :           TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE),
     504             :         TMH_pack_exchange_reply (hr));
     505           0 :     MHD_resume_connection (pc->connection);
     506           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     507           0 :     return;
     508             :   }
     509           4 :   GNUNET_assert (GNUNET_OK ==
     510             :                  TALER_amount_set_zero (TMH_currency,
     511             :                                         &pc->total_requested));
     512           8 :   for (unsigned int i = 0; i<pc->planchets_length; i++)
     513             :   {
     514           4 :     struct TALER_PlanchetDetail *pd = &pc->planchets[i];
     515             :     const struct TALER_EXCHANGE_DenomPublicKey *dpk;
     516             : 
     517           4 :     dpk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
     518           4 :                                                        &pd->denom_pub_hash);
     519           4 :     if (NULL == dpk)
     520             :     {
     521           0 :       pc->http_status = MHD_HTTP_CONFLICT;
     522           0 :       pc->response =
     523           0 :         TALER_MHD_MAKE_JSON_PACK (
     524             :           TALER_JSON_pack_ec (
     525             :             TALER_EC_MERCHANT_TIP_PICKUP_DENOMINATION_UNKNOWN),
     526             :           TMH_pack_exchange_reply (hr));
     527           0 :       MHD_resume_connection (pc->connection);
     528           0 :       TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     529           0 :       return;
     530             :     }
     531             : 
     532           4 :     if ( (GNUNET_YES !=
     533           4 :           TALER_amount_cmp_currency (&pc->total_requested,
     534           4 :                                      &dpk->value)) ||
     535             :          (0 >
     536           4 :           TALER_amount_add (&pc->total_requested,
     537           4 :                             &pc->total_requested,
     538             :                             &dpk->value)) )
     539             :     {
     540           0 :       pc->http_status = MHD_HTTP_BAD_REQUEST;
     541           0 :       pc->response =
     542           0 :         TALER_MHD_make_error (TALER_EC_MERCHANT_TIP_PICKUP_SUMMATION_FAILED,
     543             :                               "Could not add up values to compute pickup total");
     544           0 :       MHD_resume_connection (pc->connection);
     545           0 :       TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     546           0 :       return;
     547             :     }
     548             :   }
     549           4 :   pc->tr_initialized = true;
     550           4 :   MHD_resume_connection (pc->connection);
     551           4 :   TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     552             : }
     553             : 
     554             : 
     555             : /**
     556             :  * The tip lookup operation failed. Generate an error response based on the @a qs.
     557             :  *
     558             :  * @param connection connection to generate error for
     559             :  * @param qs DB status to base error creation on
     560             :  * @return MHD result code
     561             :  */
     562             : static MHD_RESULT
     563           1 : reply_lookup_tip_failed (struct MHD_Connection *connection,
     564             :                          enum GNUNET_DB_QueryStatus qs)
     565             : {
     566             :   unsigned int response_code;
     567             :   enum TALER_ErrorCode ec;
     568             : 
     569           1 :   TMH_db->rollback (TMH_db->cls);
     570           1 :   switch (qs)
     571             :   {
     572           1 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     573           1 :     ec = TALER_EC_MERCHANT_GENERIC_TIP_ID_UNKNOWN;
     574           1 :     response_code = MHD_HTTP_NOT_FOUND;
     575           1 :     break;
     576           0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     577           0 :     ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
     578           0 :     response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     579           0 :     break;
     580           0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     581           0 :     ec = TALER_EC_GENERIC_DB_COMMIT_FAILED;
     582           0 :     response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     583           0 :     break;
     584           0 :   default:
     585           0 :     GNUNET_break (0);
     586           0 :     ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     587           0 :     response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     588           0 :     break;
     589             :   }
     590           1 :   return TALER_MHD_reply_with_error (connection,
     591             :                                      response_code,
     592             :                                      ec,
     593             :                                      NULL);
     594             : }
     595             : 
     596             : 
     597             : /**
     598             :  * Manages a POST /tips/$ID/pickup call, checking that the tip is authorized,
     599             :  * and if so, returning the blind signatures.
     600             :  *
     601             :  * @param rh context of the handler
     602             :  * @param connection the MHD connection to handle
     603             :  * @param[in,out] hc context with further information about the request
     604             :  * @return MHD result code
     605             :  */
     606             : MHD_RESULT
     607          12 : TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
     608             :                          struct MHD_Connection *connection,
     609             :                          struct TMH_HandlerContext *hc)
     610             : {
     611          12 :   struct PickupContext *pc = hc->ctx;
     612             :   char *exchange_url;
     613             :   struct TALER_Amount total_authorized;
     614             :   struct TALER_Amount total_picked_up;
     615             :   struct TALER_Amount total_remaining;
     616             :   struct GNUNET_TIME_Absolute expiration;
     617             :   enum GNUNET_DB_QueryStatus qs;
     618             :   unsigned int num_retries;
     619             : 
     620          12 :   if (NULL == pc)
     621             :   {
     622             :     json_t *planchets;
     623             :     json_t *planchet;
     624             :     size_t index;
     625             : 
     626           5 :     pc = GNUNET_new (struct PickupContext);
     627           5 :     hc->ctx = pc;
     628           5 :     hc->cc = &pick_context_cleanup;
     629             : 
     630           5 :     GNUNET_assert (NULL != hc->infix);
     631           5 :     if (GNUNET_OK !=
     632           5 :         GNUNET_CRYPTO_hash_from_string (hc->infix,
     633             :                                         &pc->tip_id))
     634             :     {
     635             :       /* tip_id has wrong encoding */
     636           0 :       GNUNET_break_op (0);
     637           0 :       return TALER_MHD_reply_with_error (connection,
     638             :                                          MHD_HTTP_BAD_REQUEST,
     639             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
     640           0 :                                          hc->infix);
     641             :     }
     642             : 
     643             :     {
     644             :       struct GNUNET_JSON_Specification spec[] = {
     645           5 :         GNUNET_JSON_spec_json ("planchets",
     646             :                                &planchets),
     647           5 :         GNUNET_JSON_spec_end ()
     648             :       };
     649             :       {
     650             :         enum GNUNET_GenericReturnValue res;
     651             : 
     652           5 :         res = TALER_MHD_parse_json_data (connection,
     653           5 :                                          hc->request_body,
     654             :                                          spec);
     655           5 :         if (GNUNET_OK != res)
     656             :           return (GNUNET_NO == res)
     657             :                  ? MHD_YES
     658           0 :                  : MHD_NO;
     659             :       }
     660             :     }
     661           5 :     if (! json_is_array (planchets))
     662             :     {
     663           0 :       GNUNET_break_op (0);
     664           0 :       json_decref (planchets);
     665           0 :       return TALER_MHD_reply_with_error (connection,
     666             :                                          MHD_HTTP_BAD_REQUEST,
     667             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
     668             :                                          "planchets");
     669             :     }
     670             : 
     671           5 :     GNUNET_array_grow (pc->planchets,
     672             :                        pc->planchets_length,
     673             :                        json_array_size (planchets));
     674          10 :     json_array_foreach (planchets, index, planchet) {
     675           5 :       struct TALER_PlanchetDetail *pd = &pc->planchets[index];
     676             :       struct GNUNET_JSON_Specification spec[] = {
     677           5 :         GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
     678             :                                      &pd->denom_pub_hash),
     679           5 :         GNUNET_JSON_spec_varsize ("coin_ev",
     680             :                                   (void **) &pd->coin_ev,
     681             :                                   &pd->coin_ev_size),
     682           5 :         GNUNET_JSON_spec_end ()
     683             :       };
     684             :       {
     685             :         enum GNUNET_GenericReturnValue res;
     686             : 
     687           5 :         res = TALER_MHD_parse_json_data (connection,
     688             :                                          planchet,
     689             :                                          spec);
     690           5 :         if (GNUNET_OK != res)
     691             :         {
     692           0 :           json_decref (planchets);
     693             :           return (GNUNET_NO == res)
     694             :                  ? MHD_YES
     695           0 :                  : MHD_NO;
     696             :         }
     697             :       }
     698             :     }
     699           5 :     json_decref (planchets);
     700             :     {
     701             :       struct GNUNET_HashContext *hc;
     702             : 
     703           5 :       hc = GNUNET_CRYPTO_hash_context_start ();
     704           5 :       GNUNET_CRYPTO_hash_context_read (hc,
     705           5 :                                        &pc->tip_id,
     706             :                                        sizeof (pc->tip_id));
     707          10 :       for (unsigned int i = 0; i<pc->planchets_length; i++)
     708             :       {
     709           5 :         struct TALER_PlanchetDetail *pd = &pc->planchets[i];
     710             : 
     711           5 :         GNUNET_CRYPTO_hash_context_read (hc,
     712           5 :                                          &pd->denom_pub_hash,
     713             :                                          sizeof (pd->denom_pub_hash));
     714           5 :         GNUNET_CRYPTO_hash_context_read (hc,
     715           5 :                                          pd->coin_ev,
     716             :                                          pd->coin_ev_size);
     717             :       }
     718           5 :       GNUNET_CRYPTO_hash_context_finish (hc,
     719             :                                          &pc->pickup_id);
     720             :     }
     721             :   }
     722             : 
     723          12 :   if (NULL != pc->response)
     724             :   {
     725             :     MHD_RESULT ret;
     726             : 
     727           0 :     ret = MHD_queue_response (connection,
     728             :                               pc->http_status,
     729             :                               pc->response);
     730           0 :     pc->response = NULL;
     731           0 :     return ret;
     732             :   }
     733             : 
     734          12 :   if (! pc->tr_initialized)
     735             :   {
     736           5 :     qs = TMH_db->lookup_tip (TMH_db->cls,
     737           5 :                              hc->instance->settings.id,
     738           5 :                              &pc->tip_id,
     739             :                              &total_authorized,
     740             :                              &total_picked_up,
     741             :                              &expiration,
     742             :                              &exchange_url,
     743             :                              &pc->reserve_priv);
     744           5 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     745           1 :       return reply_lookup_tip_failed (connection,
     746             :                                       qs);
     747           4 :     MHD_suspend_connection (connection);
     748           4 :     pc->connection = connection;
     749           4 :     pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
     750             :                                            &do_timeout,
     751             :                                            pc);
     752           4 :     pc->fo = TMH_EXCHANGES_find_exchange (exchange_url,
     753             :                                           NULL,
     754             :                                           GNUNET_NO,
     755             :                                           &compute_total_requested,
     756             :                                           pc);
     757           4 :     GNUNET_free (exchange_url);
     758           4 :     return MHD_YES;
     759             :   }
     760             : 
     761             : 
     762           7 :   TMH_db->preflight (TMH_db->cls);
     763           7 :   num_retries = 0;
     764           7 : RETRY:
     765           7 :   num_retries++;
     766           7 :   if (num_retries > MAX_RETRIES)
     767             :   {
     768           0 :     GNUNET_break (0);
     769           0 :     return TALER_MHD_reply_with_error (connection,
     770             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     771             :                                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
     772             :                                        NULL);
     773             :   }
     774           7 :   if (GNUNET_OK !=
     775           7 :       TMH_db->start (TMH_db->cls,
     776             :                      "pickup tip"))
     777             :   {
     778           0 :     GNUNET_break (0);
     779           0 :     return TALER_MHD_reply_with_error (connection,
     780             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     781             :                                        TALER_EC_GENERIC_DB_START_FAILED,
     782             :                                        NULL);
     783             :   }
     784           7 :   {
     785           7 :     struct GNUNET_CRYPTO_RsaSignature *sigs[GNUNET_NZL (pc->planchets_length)];
     786             : 
     787           7 :     memset (sigs,
     788             :             0,
     789             :             sizeof (sigs));
     790           7 :     qs = TMH_db->lookup_pickup (TMH_db->cls,
     791           7 :                                 hc->instance->settings.id,
     792           7 :                                 &pc->tip_id,
     793           7 :                                 &pc->pickup_id,
     794             :                                 &exchange_url,
     795             :                                 &pc->reserve_priv,
     796             :                                 pc->planchets_length,
     797             :                                 sigs);
     798           7 :     if (qs > GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)
     799           0 :       qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     800           7 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     801             :     {
     802           3 :       bool rollback = false;
     803             : 
     804           6 :       for (unsigned int i = 0; i< pc->planchets_length; i++)
     805             :       {
     806           3 :         if (NULL != sigs[i])
     807           3 :           continue;
     808           0 :         if (! rollback)
     809             :         {
     810           0 :           TMH_db->rollback (TMH_db->cls);
     811           0 :           MHD_suspend_connection (connection);
     812           0 :           GNUNET_CONTAINER_DLL_insert (pc_head,
     813             :                                        pc_tail,
     814             :                                        pc);
     815           0 :           pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
     816             :                                                  &do_timeout,
     817             :                                                  pc);
     818           0 :           rollback = true;
     819             :         }
     820           0 :         try_withdraw (pc,
     821             :                       exchange_url,
     822           0 :                       &pc->planchets[i],
     823             :                       i);
     824             :       }
     825           3 :       GNUNET_free (exchange_url);
     826           3 :       if (rollback)
     827           0 :         return MHD_YES;
     828             :       /* we got _all_ signatures, can continue! */
     829             :     }
     830           7 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
     831             :     {
     832             :       unsigned int response_code;
     833             :       enum TALER_ErrorCode ec;
     834             : 
     835           3 :       TMH_db->rollback (TMH_db->cls);
     836           3 :       switch (qs)
     837             :       {
     838           3 :       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     839             :         {
     840             :           json_t *blind_sigs;
     841             : 
     842           3 :           blind_sigs = json_array ();
     843           3 :           GNUNET_assert (NULL != blind_sigs);
     844           6 :           for (unsigned int i = 0; i<pc->planchets_length; i++)
     845             :           {
     846           3 :             GNUNET_assert (0 ==
     847             :                            json_array_append_new (
     848             :                              blind_sigs,
     849             :                              GNUNET_JSON_PACK (
     850             :                                GNUNET_JSON_pack_rsa_signature ("blind_sig",
     851             :                                                                sigs[i]))));
     852           3 :             GNUNET_CRYPTO_rsa_signature_free (sigs[i]);
     853             :           }
     854           3 :           return TALER_MHD_REPLY_JSON_PACK (
     855             :             connection,
     856             :             MHD_HTTP_OK,
     857             :             GNUNET_JSON_pack_array_steal ("blind_sigs",
     858             :                                           blind_sigs));
     859             :         }
     860             :         break;
     861           0 :       case GNUNET_DB_STATUS_SOFT_ERROR:
     862           0 :         goto RETRY;
     863           0 :       case GNUNET_DB_STATUS_HARD_ERROR:
     864           0 :         ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
     865           0 :         response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     866           0 :         break;
     867           0 :       default:
     868           0 :         GNUNET_break (0);
     869           0 :         ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     870           0 :         response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     871           0 :         break;
     872             :       }
     873           0 :       return TALER_MHD_reply_with_error (connection,
     874             :                                          response_code,
     875             :                                          ec,
     876             :                                          NULL);
     877             :     }
     878             :   }
     879             : 
     880           4 :   qs = TMH_db->lookup_tip (TMH_db->cls,
     881           4 :                            hc->instance->settings.id,
     882           4 :                            &pc->tip_id,
     883             :                            &total_authorized,
     884             :                            &total_picked_up,
     885             :                            &expiration,
     886             :                            &exchange_url,
     887             :                            &pc->reserve_priv);
     888           4 :   if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     889             :   {
     890           0 :     TMH_db->rollback (TMH_db->cls);
     891           0 :     goto RETRY;
     892             :   }
     893           4 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     894             :   {
     895           0 :     TMH_db->rollback (TMH_db->cls);
     896           0 :     return reply_lookup_tip_failed (connection,
     897             :                                     qs);
     898             :   }
     899           4 :   if (GNUNET_TIME_absolute_is_past (expiration))
     900             :   {
     901           0 :     GNUNET_free (exchange_url);
     902           0 :     TMH_db->rollback (TMH_db->cls);
     903           0 :     return TALER_MHD_reply_with_error (connection,
     904             :                                        MHD_HTTP_GONE,
     905             :                                        TALER_EC_MERCHANT_TIP_PICKUP_HAS_EXPIRED,
     906           0 :                                        hc->infix);
     907             :   }
     908           4 :   if (0 >
     909           4 :       TALER_amount_subtract (&total_remaining,
     910             :                              &total_authorized,
     911             :                              &total_picked_up))
     912             :   {
     913           0 :     GNUNET_free (exchange_url);
     914           0 :     GNUNET_break_op (0);
     915           0 :     TMH_db->rollback (TMH_db->cls);
     916           0 :     return TALER_MHD_reply_with_error (connection,
     917             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     918             :                                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     919             :                                        "picked up amount exceeds authorized amount");
     920             :   }
     921             : 
     922           4 :   if (0 >
     923           4 :       TALER_amount_cmp (&total_remaining,
     924           4 :                         &pc->total_requested))
     925             :   {
     926             :     /* total_remaining < pc->total_requested */
     927           1 :     GNUNET_free (exchange_url);
     928           1 :     GNUNET_break_op (0);
     929           1 :     TMH_db->rollback (TMH_db->cls);
     930           1 :     return TALER_MHD_reply_with_error (connection,
     931             :                                        MHD_HTTP_BAD_REQUEST,
     932             :                                        TALER_EC_MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING,
     933           1 :                                        hc->infix);
     934             :   }
     935             : 
     936           3 :   GNUNET_assert (0 <
     937             :                  TALER_amount_add (&total_picked_up,
     938             :                                    &total_picked_up,
     939             :                                    &pc->total_requested));
     940           3 :   qs = TMH_db->insert_pickup (TMH_db->cls,
     941           3 :                               hc->instance->settings.id,
     942           3 :                               &pc->tip_id,
     943             :                               &total_picked_up,
     944           3 :                               &pc->pickup_id,
     945           3 :                               &pc->total_requested);
     946           3 :   if (qs < 0)
     947             :   {
     948           0 :     TMH_db->rollback (TMH_db->cls);
     949           0 :     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     950           0 :       goto RETRY;
     951           0 :     GNUNET_free (exchange_url);
     952           0 :     return TALER_MHD_reply_with_error (connection,
     953             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     954             :                                        TALER_EC_GENERIC_DB_STORE_FAILED,
     955             :                                        "pickup");
     956             :   }
     957           3 :   qs = TMH_db->commit (TMH_db->cls);
     958           3 :   if (qs < 0)
     959             :   {
     960           0 :     TMH_db->rollback (TMH_db->cls);
     961           0 :     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     962           0 :       goto RETRY;
     963           0 :     GNUNET_free (exchange_url);
     964           0 :     return TALER_MHD_reply_with_error (connection,
     965             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     966             :                                        TALER_EC_GENERIC_DB_COMMIT_FAILED,
     967             :                                        NULL);
     968             :   }
     969           3 :   MHD_suspend_connection (connection);
     970           3 :   pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
     971             :                                          &do_timeout,
     972             :                                          pc);
     973           6 :   for (unsigned int i = 0; i<pc->planchets_length; i++)
     974             :   {
     975           3 :     try_withdraw (pc,
     976             :                   exchange_url,
     977           3 :                   &pc->planchets[i],
     978             :                   i);
     979             :   }
     980           3 :   GNUNET_free (exchange_url);
     981           3 :   return MHD_YES;
     982             : }

Generated by: LCOV version 1.14