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: 3 353 0.8 %
Date: 2022-06-30 06:15:34 Functions: 1 10 10.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 TALER_TipIdentifierP tip_id;
     148             : 
     149             :   /**
     150             :    * What is the ID of the pickup operation? (Basically a
     151             :    * hash over the key inputs).
     152             :    */
     153             :   struct TALER_PickupIdentifierP 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           0 : stop_operations (struct PickupContext *pc)
     199             : {
     200             :   struct PlanchetOperation *po;
     201             : 
     202           0 :   if (NULL != pc->tt)
     203             :   {
     204           0 :     GNUNET_SCHEDULER_cancel (pc->tt);
     205           0 :     pc->tt = NULL;
     206             :   }
     207           0 :   if (NULL != pc->fo)
     208             :   {
     209           0 :     TMH_EXCHANGES_find_exchange_cancel (pc->fo);
     210           0 :     pc->fo = NULL;
     211             :   }
     212           0 :   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           0 : }
     230             : 
     231             : 
     232             : /**
     233             :  * Function called to clean up.
     234             :  *
     235             :  * @param cls a `struct PickupContext *` to clean up
     236             :  */
     237             : static void
     238           0 : pick_context_cleanup (void *cls)
     239             : {
     240           0 :   struct PickupContext *pc = cls;
     241             : 
     242           0 :   stop_operations (pc); /* should not be any... */
     243           0 :   for (unsigned int i = 0; i<pc->planchets_length; i++)
     244           0 :     TALER_planchet_detail_free (&pc->planchets[i]);
     245           0 :   GNUNET_array_grow (pc->planchets,
     246             :                      pc->planchets_length,
     247             :                      0);
     248           0 :   GNUNET_free (pc);
     249           0 : }
     250             : 
     251             : 
     252             : void
     253           3 : TMH_force_tip_pickup_resume ()
     254             : {
     255             :   struct PickupContext *nxt;
     256             : 
     257           3 :   for (struct PickupContext *pc = pc_head;
     258             :        NULL != pc;
     259           0 :        pc = nxt)
     260             :   {
     261           0 :     nxt = pc->next;
     262           0 :     stop_operations (pc);
     263           0 :     GNUNET_CONTAINER_DLL_remove (pc_head,
     264             :                                  pc_tail,
     265             :                                  pc);
     266           0 :     MHD_resume_connection (pc->connection);
     267             :   }
     268           3 : }
     269             : 
     270             : 
     271             : /**
     272             :  * Callbacks of this type are used to serve the result of submitting a
     273             :  * withdraw request to a exchange without the (un)blinding factor.
     274             :  * We persist the result in the database and, if we were the last
     275             :  * planchet operation, resume HTTP processing.
     276             :  *
     277             :  * @param cls closure with a `struct PlanchetOperation *`
     278             :  * @param hr HTTP response data
     279             :  * @param blind_sig blind signature over the coin, NULL on error
     280             :  */
     281             : static void
     282           0 : withdraw_cb (void *cls,
     283             :              const struct TALER_EXCHANGE_HttpResponse *hr,
     284             :              const struct TALER_BlindedDenominationSignature *blind_sig)
     285             : {
     286           0 :   struct PlanchetOperation *po = cls;
     287           0 :   struct PickupContext *pc = po->pc;
     288             :   enum GNUNET_DB_QueryStatus qs;
     289             : 
     290           0 :   GNUNET_CONTAINER_DLL_remove (pc->po_head,
     291             :                                pc->po_tail,
     292             :                                po);
     293           0 :   if (NULL == blind_sig)
     294             :   {
     295           0 :     GNUNET_free (po);
     296           0 :     stop_operations (pc);
     297           0 :     pc->http_status = MHD_HTTP_BAD_GATEWAY;
     298           0 :     pc->response =
     299           0 :       TALER_MHD_MAKE_JSON_PACK (
     300             :         TALER_JSON_pack_ec (TALER_EC_MERCHANT_TIP_PICKUP_EXCHANGE_ERROR),
     301             :         TMH_pack_exchange_reply (hr));
     302           0 :     GNUNET_CONTAINER_DLL_remove (pc_head,
     303             :                                  pc_tail,
     304             :                                  pc);
     305           0 :     MHD_resume_connection (pc->connection);
     306           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     307           0 :     return;
     308             :   }
     309           0 :   qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls,
     310           0 :                                               &pc->pickup_id,
     311             :                                               po->offset,
     312             :                                               blind_sig);
     313           0 :   GNUNET_free (po);
     314           0 :   if (qs < 0)
     315             :   {
     316           0 :     stop_operations (pc);
     317           0 :     pc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
     318           0 :     pc->response = TALER_MHD_make_error (
     319             :       TALER_EC_GENERIC_DB_STORE_FAILED,
     320             :       "blind signature");
     321           0 :     GNUNET_CONTAINER_DLL_remove (pc_head,
     322             :                                  pc_tail,
     323             :                                  pc);
     324           0 :     MHD_resume_connection (pc->connection);
     325           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     326           0 :     return;
     327             :   }
     328           0 :   if (NULL == pc->po_head)
     329             :   {
     330           0 :     stop_operations (pc); /* stops timeout job */
     331           0 :     GNUNET_CONTAINER_DLL_remove (pc_head,
     332             :                                  pc_tail,
     333             :                                  pc);
     334           0 :     MHD_resume_connection (pc->connection);
     335           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     336             :   }
     337             : }
     338             : 
     339             : 
     340             : /**
     341             :  * Function called with the result of a #TMH_EXCHANGES_find_exchange()
     342             :  * operation as part of a withdraw objective.  If the exchange is ready,
     343             :  * withdraws the planchet from the exchange.
     344             :  *
     345             :  * @param cls closure, with our `struct PlanchetOperation *`
     346             :  * @param hr HTTP response details
     347             :  * @param eh handle to the exchange context
     348             :  * @param payto_uri payto://-URI of the exchange
     349             :  * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
     350             :  * @param exchange_trusted true if this exchange is trusted by config
     351             :  */
     352             : static void
     353           0 : do_withdraw (void *cls,
     354             :              const struct TALER_EXCHANGE_HttpResponse *hr,
     355             :              struct TALER_EXCHANGE_Handle *eh,
     356             :              const char *payto_uri,
     357             :              const struct TALER_Amount *wire_fee,
     358             :              bool exchange_trusted)
     359             : {
     360           0 :   struct PlanchetOperation *po = cls;
     361           0 :   struct PickupContext *pc = po->pc;
     362             : 
     363           0 :   po->fo = NULL;
     364           0 :   if (NULL == hr)
     365             :   {
     366           0 :     stop_operations (pc);
     367           0 :     GNUNET_CONTAINER_DLL_remove (pc->po_head,
     368             :                                  pc->po_tail,
     369             :                                  po);
     370           0 :     GNUNET_free (po);
     371           0 :     pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
     372           0 :     pc->response = TALER_MHD_MAKE_JSON_PACK (
     373             :       TALER_JSON_pack_ec (
     374             :         TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
     375           0 :     MHD_resume_connection (pc->connection);
     376           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     377           0 :     return;
     378             :   }
     379           0 :   if (NULL == eh)
     380             :   {
     381           0 :     stop_operations (pc);
     382           0 :     GNUNET_CONTAINER_DLL_remove (pc->po_head,
     383             :                                  pc->po_tail,
     384             :                                  po);
     385           0 :     GNUNET_free (po);
     386           0 :     pc->http_status = MHD_HTTP_BAD_GATEWAY;
     387           0 :     pc->response =
     388           0 :       TALER_MHD_MAKE_JSON_PACK (
     389             :         TALER_JSON_pack_ec (
     390             :           TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
     391             :         TMH_pack_exchange_reply (hr));
     392           0 :     MHD_resume_connection (pc->connection);
     393           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     394           0 :     return;
     395             :   }
     396           0 :   po->w2h = TALER_EXCHANGE_withdraw2 (eh,
     397           0 :                                       &po->pd,
     398           0 :                                       &pc->reserve_priv,
     399             :                                       &withdraw_cb,
     400             :                                       po);
     401             : }
     402             : 
     403             : 
     404             : /**
     405             :  * Withdraw @a planchet from @a exchange_url for @a pc operation at planchet
     406             :  * @a offset.  Sets up the respective operation and adds it @a pc's operation
     407             :  * list. Once the operation is complete, the resulting blind signature is
     408             :  * committed to the merchant's database. If all planchet operations are
     409             :  * completed, the HTTP processing is resumed.
     410             :  *
     411             :  * @param[in,out] pc a pending pickup operation that includes @a planchet
     412             :  * @param exchange_url identifies an exchange to do the pickup from
     413             :  * @param planchet details about the coin to pick up
     414             :  * @param offset offset of @a planchet in the list, needed to process the reply
     415             :  */
     416             : static void
     417           0 : try_withdraw (struct PickupContext *pc,
     418             :               const char *exchange_url,
     419             :               const struct TALER_PlanchetDetail *planchet,
     420             :               unsigned int offset)
     421             : {
     422             :   struct PlanchetOperation *po;
     423             : 
     424           0 :   po = GNUNET_new (struct PlanchetOperation);
     425           0 :   po->pc = pc;
     426           0 :   po->pd = *planchet;
     427           0 :   po->offset = offset;
     428           0 :   po->fo = TMH_EXCHANGES_find_exchange (exchange_url,
     429             :                                         NULL,
     430             :                                         GNUNET_NO,
     431             :                                         &do_withdraw,
     432             :                                         po);
     433           0 :   GNUNET_assert (NULL != po->fo);
     434           0 :   GNUNET_CONTAINER_DLL_insert (pc->po_head,
     435             :                                pc->po_tail,
     436             :                                po);
     437           0 : }
     438             : 
     439             : 
     440             : /**
     441             :  * Handle timeout for pickup.
     442             :  *
     443             :  * @param cls a `struct PickupContext *`
     444             :  */
     445             : static void
     446           0 : do_timeout (void *cls)
     447             : {
     448           0 :   struct PickupContext *pc = cls;
     449             : 
     450           0 :   pc->tt = NULL;
     451           0 :   stop_operations (pc);
     452           0 :   pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
     453           0 :   pc->response =  TALER_MHD_make_error (
     454             :     TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
     455             :     NULL);
     456           0 :   GNUNET_CONTAINER_DLL_remove (pc_head,
     457             :                                pc_tail,
     458             :                                pc);
     459           0 :   MHD_resume_connection (pc->connection);
     460           0 :   TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     461           0 : }
     462             : 
     463             : 
     464             : /**
     465             :  * Function called with the result of a #TMH_EXCHANGES_find_exchange()
     466             :  * operation as part of a withdraw objective.  Here, we initialize
     467             :  * the "total_requested" amount by adding up the cost of the planchets
     468             :  * provided by the client.
     469             :  *
     470             :  * @param cls closure, with our `struct PickupContext *`
     471             :  * @param hr HTTP response details
     472             :  * @param eh handle to the exchange context
     473             :  * @param payto_uri payto://-URI of the exchange
     474             :  * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
     475             :  * @param exchange_trusted true if this exchange is trusted by config
     476             :  */
     477             : static void
     478           0 : compute_total_requested (void *cls,
     479             :                          const struct TALER_EXCHANGE_HttpResponse *hr,
     480             :                          struct TALER_EXCHANGE_Handle *eh,
     481             :                          const char *payto_uri,
     482             :                          const struct TALER_Amount *wire_fee,
     483             :                          bool exchange_trusted)
     484             : {
     485           0 :   struct PickupContext *pc = cls;
     486             :   const struct TALER_EXCHANGE_Keys *keys;
     487             : 
     488           0 :   pc->fo = NULL;
     489           0 :   stop_operations (pc); /* stops timeout job */
     490           0 :   if (NULL == hr)
     491             :   {
     492           0 :     pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
     493           0 :     pc->response = TALER_MHD_MAKE_JSON_PACK (
     494             :       TALER_JSON_pack_ec (
     495             :         TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
     496           0 :     MHD_resume_connection (pc->connection);
     497           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     498           0 :     return;
     499             :   }
     500           0 :   if (NULL == eh)
     501             :   {
     502           0 :     pc->http_status = MHD_HTTP_BAD_GATEWAY;
     503           0 :     pc->response =
     504           0 :       TALER_MHD_MAKE_JSON_PACK (
     505             :         TALER_JSON_pack_ec (
     506             :           TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
     507             :         TMH_pack_exchange_reply (hr));
     508           0 :     MHD_resume_connection (pc->connection);
     509           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     510           0 :     return;
     511             :   }
     512           0 :   if (NULL == (keys = TALER_EXCHANGE_get_keys (eh)))
     513             :   {
     514           0 :     pc->http_status = MHD_HTTP_BAD_GATEWAY;
     515           0 :     pc->response =
     516           0 :       TALER_MHD_MAKE_JSON_PACK (
     517             :         TALER_JSON_pack_ec (
     518             :           TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE),
     519             :         TMH_pack_exchange_reply (hr));
     520           0 :     MHD_resume_connection (pc->connection);
     521           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     522           0 :     return;
     523             :   }
     524           0 :   GNUNET_assert (GNUNET_OK ==
     525             :                  TALER_amount_set_zero (TMH_currency,
     526             :                                         &pc->total_requested));
     527           0 :   for (unsigned int i = 0; i<pc->planchets_length; i++)
     528             :   {
     529           0 :     struct TALER_PlanchetDetail *pd = &pc->planchets[i];
     530             :     const struct TALER_EXCHANGE_DenomPublicKey *dpk;
     531             : 
     532           0 :     dpk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
     533           0 :                                                        &pd->denom_pub_hash);
     534           0 :     if (NULL == dpk)
     535             :     {
     536           0 :       pc->http_status = MHD_HTTP_CONFLICT;
     537           0 :       pc->response =
     538           0 :         TALER_MHD_MAKE_JSON_PACK (
     539             :           TALER_JSON_pack_ec (
     540             :             TALER_EC_MERCHANT_TIP_PICKUP_DENOMINATION_UNKNOWN),
     541             :           TMH_pack_exchange_reply (hr));
     542           0 :       MHD_resume_connection (pc->connection);
     543           0 :       TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     544           0 :       return;
     545             :     }
     546             : 
     547           0 :     if ( (GNUNET_YES !=
     548           0 :           TALER_amount_cmp_currency (&pc->total_requested,
     549           0 :                                      &dpk->value)) ||
     550             :          (0 >
     551           0 :           TALER_amount_add (&pc->total_requested,
     552           0 :                             &pc->total_requested,
     553             :                             &dpk->value)) )
     554             :     {
     555           0 :       pc->http_status = MHD_HTTP_BAD_REQUEST;
     556           0 :       pc->response =
     557           0 :         TALER_MHD_make_error (TALER_EC_MERCHANT_TIP_PICKUP_SUMMATION_FAILED,
     558             :                               "Could not add up values to compute pickup total");
     559           0 :       MHD_resume_connection (pc->connection);
     560           0 :       TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     561           0 :       return;
     562             :     }
     563             :   }
     564           0 :   pc->tr_initialized = true;
     565           0 :   MHD_resume_connection (pc->connection);
     566           0 :   TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     567             : }
     568             : 
     569             : 
     570             : /**
     571             :  * The tip lookup operation failed. Generate an error response based on the @a qs.
     572             :  *
     573             :  * @param connection connection to generate error for
     574             :  * @param qs DB status to base error creation on
     575             :  * @return MHD result code
     576             :  */
     577             : static MHD_RESULT
     578           0 : reply_lookup_tip_failed (struct MHD_Connection *connection,
     579             :                          enum GNUNET_DB_QueryStatus qs)
     580             : {
     581             :   unsigned int response_code;
     582             :   enum TALER_ErrorCode ec;
     583             : 
     584           0 :   TMH_db->rollback (TMH_db->cls);
     585           0 :   switch (qs)
     586             :   {
     587           0 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     588           0 :     ec = TALER_EC_MERCHANT_GENERIC_TIP_ID_UNKNOWN;
     589           0 :     response_code = MHD_HTTP_NOT_FOUND;
     590           0 :     break;
     591           0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     592           0 :     ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
     593           0 :     response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     594           0 :     break;
     595           0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     596           0 :     ec = TALER_EC_GENERIC_DB_COMMIT_FAILED;
     597           0 :     response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     598           0 :     break;
     599           0 :   default:
     600           0 :     GNUNET_break (0);
     601           0 :     ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     602           0 :     response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     603           0 :     break;
     604             :   }
     605           0 :   return TALER_MHD_reply_with_error (connection,
     606             :                                      response_code,
     607             :                                      ec,
     608             :                                      NULL);
     609             : }
     610             : 
     611             : 
     612             : MHD_RESULT
     613           0 : TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
     614             :                          struct MHD_Connection *connection,
     615             :                          struct TMH_HandlerContext *hc)
     616             : {
     617           0 :   struct PickupContext *pc = hc->ctx;
     618             :   char *exchange_url;
     619             :   struct TALER_Amount total_authorized;
     620             :   struct TALER_Amount total_picked_up;
     621             :   struct TALER_Amount total_remaining;
     622             :   struct GNUNET_TIME_Timestamp expiration;
     623             :   enum GNUNET_DB_QueryStatus qs;
     624             :   unsigned int num_retries;
     625             : 
     626           0 :   if (NULL == pc)
     627             :   {
     628             :     json_t *planchets;
     629             :     json_t *planchet;
     630             :     size_t index;
     631             : 
     632           0 :     pc = GNUNET_new (struct PickupContext);
     633           0 :     hc->ctx = pc;
     634           0 :     hc->cc = &pick_context_cleanup;
     635             : 
     636           0 :     GNUNET_assert (NULL != hc->infix);
     637           0 :     if (GNUNET_OK !=
     638           0 :         GNUNET_CRYPTO_hash_from_string (hc->infix,
     639             :                                         &pc->tip_id.hash))
     640             :     {
     641             :       /* tip_id has wrong encoding */
     642           0 :       GNUNET_break_op (0);
     643           0 :       return TALER_MHD_reply_with_error (connection,
     644             :                                          MHD_HTTP_BAD_REQUEST,
     645             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
     646           0 :                                          hc->infix);
     647             :     }
     648             : 
     649             :     {
     650             :       struct GNUNET_JSON_Specification spec[] = {
     651           0 :         GNUNET_JSON_spec_json ("planchets",
     652             :                                &planchets),
     653           0 :         GNUNET_JSON_spec_end ()
     654             :       };
     655             :       {
     656             :         enum GNUNET_GenericReturnValue res;
     657             : 
     658           0 :         res = TALER_MHD_parse_json_data (connection,
     659           0 :                                          hc->request_body,
     660             :                                          spec);
     661           0 :         if (GNUNET_OK != res)
     662             :           return (GNUNET_NO == res)
     663             :                  ? MHD_YES
     664           0 :                  : MHD_NO;
     665             :       }
     666             :     }
     667           0 :     if (! json_is_array (planchets))
     668             :     {
     669           0 :       GNUNET_break_op (0);
     670           0 :       json_decref (planchets);
     671           0 :       return TALER_MHD_reply_with_error (connection,
     672             :                                          MHD_HTTP_BAD_REQUEST,
     673             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
     674             :                                          "planchets");
     675             :     }
     676             : 
     677           0 :     GNUNET_array_grow (pc->planchets,
     678             :                        pc->planchets_length,
     679             :                        json_array_size (planchets));
     680           0 :     json_array_foreach (planchets, index, planchet) {
     681           0 :       struct TALER_PlanchetDetail *pd = &pc->planchets[index];
     682             :       struct GNUNET_JSON_Specification spec[] = {
     683           0 :         GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
     684             :                                      &pd->denom_pub_hash),
     685           0 :         TALER_JSON_spec_blinded_planchet ("coin_ev",
     686             :                                           &pd->blinded_planchet),
     687           0 :         GNUNET_JSON_spec_end ()
     688             :       };
     689             :       {
     690             :         enum GNUNET_GenericReturnValue res;
     691             : 
     692           0 :         res = TALER_MHD_parse_json_data (connection,
     693             :                                          planchet,
     694             :                                          spec);
     695           0 :         if (GNUNET_OK != res)
     696             :         {
     697           0 :           json_decref (planchets);
     698             :           return (GNUNET_NO == res)
     699             :                  ? MHD_YES
     700           0 :                  : MHD_NO;
     701             :         }
     702             :       }
     703             :     }
     704           0 :     json_decref (planchets);
     705             :     {
     706             :       struct GNUNET_HashContext *hc;
     707             : 
     708           0 :       hc = GNUNET_CRYPTO_hash_context_start ();
     709           0 :       GNUNET_CRYPTO_hash_context_read (hc,
     710           0 :                                        &pc->tip_id,
     711             :                                        sizeof (pc->tip_id));
     712           0 :       for (unsigned int i = 0; i<pc->planchets_length; i++)
     713             :       {
     714           0 :         struct TALER_PlanchetDetail *pd = &pc->planchets[i];
     715             : 
     716           0 :         GNUNET_CRYPTO_hash_context_read (hc,
     717           0 :                                          &pd->denom_pub_hash,
     718             :                                          sizeof (pd->denom_pub_hash));
     719           0 :         TALER_blinded_planchet_hash_ (&pd->blinded_planchet,
     720             :                                       hc);
     721             :       }
     722           0 :       GNUNET_CRYPTO_hash_context_finish (hc,
     723             :                                          &pc->pickup_id.hash);
     724             :     }
     725             :   }
     726             : 
     727           0 :   if (NULL != pc->response)
     728             :   {
     729             :     MHD_RESULT ret;
     730             : 
     731           0 :     ret = MHD_queue_response (connection,
     732             :                               pc->http_status,
     733             :                               pc->response);
     734           0 :     pc->response = NULL;
     735           0 :     return ret;
     736             :   }
     737             : 
     738           0 :   if (! pc->tr_initialized)
     739             :   {
     740           0 :     qs = TMH_db->lookup_tip (TMH_db->cls,
     741           0 :                              hc->instance->settings.id,
     742           0 :                              &pc->tip_id,
     743             :                              &total_authorized,
     744             :                              &total_picked_up,
     745             :                              &expiration,
     746             :                              &exchange_url,
     747             :                              &pc->reserve_priv);
     748           0 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     749           0 :       return reply_lookup_tip_failed (connection,
     750             :                                       qs);
     751           0 :     MHD_suspend_connection (connection);
     752           0 :     pc->connection = connection;
     753           0 :     pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
     754             :                                            &do_timeout,
     755             :                                            pc);
     756           0 :     pc->fo = TMH_EXCHANGES_find_exchange (exchange_url,
     757             :                                           NULL,
     758             :                                           GNUNET_NO,
     759             :                                           &compute_total_requested,
     760             :                                           pc);
     761           0 :     GNUNET_free (exchange_url);
     762           0 :     return MHD_YES;
     763             :   }
     764             : 
     765             : 
     766           0 :   TMH_db->preflight (TMH_db->cls);
     767           0 :   num_retries = 0;
     768           0 : RETRY:
     769           0 :   num_retries++;
     770           0 :   if (num_retries > MAX_RETRIES)
     771             :   {
     772           0 :     GNUNET_break (0);
     773           0 :     return TALER_MHD_reply_with_error (connection,
     774             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     775             :                                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
     776             :                                        NULL);
     777             :   }
     778           0 :   if (GNUNET_OK !=
     779           0 :       TMH_db->start (TMH_db->cls,
     780             :                      "pickup tip"))
     781             :   {
     782           0 :     GNUNET_break (0);
     783           0 :     return TALER_MHD_reply_with_error (connection,
     784             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     785             :                                        TALER_EC_GENERIC_DB_START_FAILED,
     786             :                                        NULL);
     787             :   }
     788           0 :   {
     789           0 :     struct TALER_BlindedDenominationSignature sigs[
     790           0 :       GNUNET_NZL (pc->planchets_length)];
     791             : 
     792           0 :     memset (sigs,
     793             :             0,
     794             :             sizeof (sigs));
     795           0 :     qs = TMH_db->lookup_pickup (TMH_db->cls,
     796           0 :                                 hc->instance->settings.id,
     797           0 :                                 &pc->tip_id,
     798           0 :                                 &pc->pickup_id,
     799             :                                 &exchange_url,
     800             :                                 &pc->reserve_priv,
     801             :                                 pc->planchets_length,
     802             :                                 sigs);
     803           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     804             :                 "Lookup pickup `%s' resulted in %d\n",
     805             :                 GNUNET_h2s (&pc->pickup_id.hash),
     806             :                 qs);
     807           0 :     if (qs > GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)
     808           0 :       qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     809           0 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     810             :     {
     811           0 :       bool rollback = false;
     812             : 
     813           0 :       for (unsigned int i = 0; i< pc->planchets_length; i++)
     814             :       {
     815           0 :         if (TALER_DENOMINATION_INVALID != sigs[i].cipher)
     816           0 :           continue;
     817           0 :         if (! rollback)
     818             :         {
     819           0 :           TMH_db->rollback (TMH_db->cls);
     820           0 :           MHD_suspend_connection (connection);
     821           0 :           GNUNET_CONTAINER_DLL_insert (pc_head,
     822             :                                        pc_tail,
     823             :                                        pc);
     824           0 :           pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
     825             :                                                  &do_timeout,
     826             :                                                  pc);
     827           0 :           rollback = true;
     828             :         }
     829           0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     830             :                     "Lookup pickup `%s' initiated withdraw #%u\n",
     831             :                     GNUNET_h2s (&pc->pickup_id.hash),
     832             :                     i);
     833           0 :         try_withdraw (pc,
     834             :                       exchange_url,
     835           0 :                       &pc->planchets[i],
     836             :                       i);
     837             :       }
     838           0 :       GNUNET_free (exchange_url);
     839           0 :       if (rollback)
     840           0 :         return MHD_YES;
     841             :       /* we got _all_ signatures, can continue! */
     842             :     }
     843           0 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
     844             :     {
     845             :       unsigned int response_code;
     846             :       enum TALER_ErrorCode ec;
     847             : 
     848           0 :       TMH_db->rollback (TMH_db->cls);
     849           0 :       switch (qs)
     850             :       {
     851           0 :       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     852             :         {
     853             :           json_t *blind_sigs;
     854             : 
     855           0 :           blind_sigs = json_array ();
     856           0 :           GNUNET_assert (NULL != blind_sigs);
     857           0 :           for (unsigned int i = 0; i<pc->planchets_length; i++)
     858             :           {
     859           0 :             GNUNET_assert (0 ==
     860             :                            json_array_append_new (
     861             :                              blind_sigs,
     862             :                              GNUNET_JSON_PACK (
     863             :                                TALER_JSON_pack_blinded_denom_sig ("blind_sig",
     864             :                                                                   &sigs[i]))));
     865           0 :             TALER_blinded_denom_sig_free (&sigs[i]);
     866             :           }
     867           0 :           return TALER_MHD_REPLY_JSON_PACK (
     868             :             connection,
     869             :             MHD_HTTP_OK,
     870             :             GNUNET_JSON_pack_array_steal ("blind_sigs",
     871             :                                           blind_sigs));
     872             :         }
     873             :         break;
     874           0 :       case GNUNET_DB_STATUS_SOFT_ERROR:
     875           0 :         goto RETRY;
     876           0 :       case GNUNET_DB_STATUS_HARD_ERROR:
     877           0 :         ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
     878           0 :         response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     879           0 :         break;
     880           0 :       default:
     881           0 :         GNUNET_break (0);
     882           0 :         ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     883           0 :         response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     884           0 :         break;
     885             :       }
     886           0 :       return TALER_MHD_reply_with_error (connection,
     887             :                                          response_code,
     888             :                                          ec,
     889             :                                          NULL);
     890             :     }
     891             :   }
     892             : 
     893           0 :   qs = TMH_db->lookup_tip (TMH_db->cls,
     894           0 :                            hc->instance->settings.id,
     895           0 :                            &pc->tip_id,
     896             :                            &total_authorized,
     897             :                            &total_picked_up,
     898             :                            &expiration,
     899             :                            &exchange_url,
     900             :                            &pc->reserve_priv);
     901           0 :   if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     902             :   {
     903           0 :     TMH_db->rollback (TMH_db->cls);
     904           0 :     goto RETRY;
     905             :   }
     906           0 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     907             :   {
     908           0 :     TMH_db->rollback (TMH_db->cls);
     909           0 :     return reply_lookup_tip_failed (connection,
     910             :                                     qs);
     911             :   }
     912           0 :   if (GNUNET_TIME_absolute_is_past (expiration.abs_time))
     913             :   {
     914           0 :     GNUNET_free (exchange_url);
     915           0 :     TMH_db->rollback (TMH_db->cls);
     916           0 :     return TALER_MHD_reply_with_error (connection,
     917             :                                        MHD_HTTP_GONE,
     918             :                                        TALER_EC_MERCHANT_TIP_PICKUP_HAS_EXPIRED,
     919           0 :                                        hc->infix);
     920             :   }
     921           0 :   if (0 >
     922           0 :       TALER_amount_subtract (&total_remaining,
     923             :                              &total_authorized,
     924             :                              &total_picked_up))
     925             :   {
     926           0 :     GNUNET_free (exchange_url);
     927           0 :     GNUNET_break_op (0);
     928           0 :     TMH_db->rollback (TMH_db->cls);
     929           0 :     return TALER_MHD_reply_with_error (connection,
     930             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     931             :                                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     932             :                                        "picked up amount exceeds authorized amount");
     933             :   }
     934             : 
     935           0 :   if (0 >
     936           0 :       TALER_amount_cmp (&total_remaining,
     937           0 :                         &pc->total_requested))
     938             :   {
     939             :     /* total_remaining < pc->total_requested */
     940           0 :     GNUNET_free (exchange_url);
     941           0 :     GNUNET_break_op (0);
     942           0 :     TMH_db->rollback (TMH_db->cls);
     943           0 :     return TALER_MHD_reply_with_error (connection,
     944             :                                        MHD_HTTP_BAD_REQUEST,
     945             :                                        TALER_EC_MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING,
     946           0 :                                        hc->infix);
     947             :   }
     948             : 
     949           0 :   GNUNET_assert (0 <
     950             :                  TALER_amount_add (&total_picked_up,
     951             :                                    &total_picked_up,
     952             :                                    &pc->total_requested));
     953           0 :   qs = TMH_db->insert_pickup (TMH_db->cls,
     954           0 :                               hc->instance->settings.id,
     955           0 :                               &pc->tip_id,
     956             :                               &total_picked_up,
     957           0 :                               &pc->pickup_id,
     958           0 :                               &pc->total_requested);
     959           0 :   if (qs < 0)
     960             :   {
     961           0 :     TMH_db->rollback (TMH_db->cls);
     962           0 :     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     963           0 :       goto RETRY;
     964           0 :     GNUNET_free (exchange_url);
     965           0 :     return TALER_MHD_reply_with_error (connection,
     966             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     967             :                                        TALER_EC_GENERIC_DB_STORE_FAILED,
     968             :                                        "pickup");
     969             :   }
     970           0 :   qs = TMH_db->commit (TMH_db->cls);
     971           0 :   if (qs < 0)
     972             :   {
     973           0 :     TMH_db->rollback (TMH_db->cls);
     974           0 :     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     975           0 :       goto RETRY;
     976           0 :     GNUNET_free (exchange_url);
     977           0 :     return TALER_MHD_reply_with_error (connection,
     978             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     979             :                                        TALER_EC_GENERIC_DB_COMMIT_FAILED,
     980             :                                        NULL);
     981             :   }
     982           0 :   MHD_suspend_connection (connection);
     983           0 :   GNUNET_CONTAINER_DLL_insert (pc_head,
     984             :                                pc_tail,
     985             :                                pc);
     986           0 :   pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
     987             :                                          &do_timeout,
     988             :                                          pc);
     989           0 :   for (unsigned int i = 0; i<pc->planchets_length; i++)
     990             :   {
     991           0 :     try_withdraw (pc,
     992             :                   exchange_url,
     993           0 :                   &pc->planchets[i],
     994             :                   i);
     995             :   }
     996           0 :   GNUNET_free (exchange_url);
     997           0 :   return MHD_YES;
     998             : }

Generated by: LCOV version 1.14