LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_private-get-orders.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 53.0 % 321 170
Test Date: 2025-10-31 14:20:14 Functions: 77.8 % 9 7

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (C) 2019--2025 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU Affero General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU 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_private-get-orders.c
      18              :  * @brief implement GET /orders
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include "platform.h"
      22              : #include "taler-merchant-httpd_private-get-orders.h"
      23              : #include <taler_merchant_util.h>
      24              : #include <taler/taler_json_lib.h>
      25              : #include <taler/taler_dbevents.h>
      26              : 
      27              : 
      28              : /**
      29              :  * Sensible bound on TALER_MERCHANTDB_OrderFilter.delta
      30              :  */
      31              : #define MAX_DELTA 1024
      32              : 
      33              : 
      34              : /**
      35              :  * A pending GET /orders request.
      36              :  */
      37              : struct TMH_PendingOrder
      38              : {
      39              : 
      40              :   /**
      41              :    * Kept in a DLL.
      42              :    */
      43              :   struct TMH_PendingOrder *prev;
      44              : 
      45              :   /**
      46              :    * Kept in a DLL.
      47              :    */
      48              :   struct TMH_PendingOrder *next;
      49              : 
      50              :   /**
      51              :    * Which connection was suspended.
      52              :    */
      53              :   struct MHD_Connection *con;
      54              : 
      55              :   /**
      56              :    * Associated heap node.
      57              :    */
      58              :   struct GNUNET_CONTAINER_HeapNode *hn;
      59              : 
      60              :   /**
      61              :    * Which instance is this client polling? This also defines
      62              :    * which DLL this struct is part of.
      63              :    */
      64              :   struct TMH_MerchantInstance *mi;
      65              : 
      66              :   /**
      67              :    * At what time does this request expire? If set in the future, we
      68              :    * may wait this long for a payment to arrive before responding.
      69              :    */
      70              :   struct GNUNET_TIME_Absolute long_poll_timeout;
      71              : 
      72              :   /**
      73              :    * Filter to apply.
      74              :    */
      75              :   struct TALER_MERCHANTDB_OrderFilter of;
      76              : 
      77              :   /**
      78              :    * The array of orders.
      79              :    */
      80              :   json_t *pa;
      81              : 
      82              :   /**
      83              :    * The name of the instance we are querying for.
      84              :    */
      85              :   const char *instance_id;
      86              : 
      87              :   /**
      88              :    * Alias of @a of.summary_filter, but with memory to be released (owner).
      89              :    */
      90              :   char *summary_filter;
      91              : 
      92              :   /**
      93              :    * The result after adding the orders (#TALER_EC_NONE for okay, anything else for an error).
      94              :    */
      95              :   enum TALER_ErrorCode result;
      96              : 
      97              :   /**
      98              :    * Is the structure in the DLL
      99              :    */
     100              :   bool in_dll;
     101              : };
     102              : 
     103              : 
     104              : /**
     105              :  * Task to timeout pending orders.
     106              :  */
     107              : static struct GNUNET_SCHEDULER_Task *order_timeout_task;
     108              : 
     109              : /**
     110              :  * Heap for orders in long polling awaiting timeout.
     111              :  */
     112              : static struct GNUNET_CONTAINER_Heap *order_timeout_heap;
     113              : 
     114              : 
     115              : /**
     116              :  * We are shutting down (or an instance is being deleted), force resume of all
     117              :  * GET /orders requests.
     118              :  *
     119              :  * @param mi instance to force resuming for
     120              :  */
     121              : void
     122          102 : TMH_force_get_orders_resume (struct TMH_MerchantInstance *mi)
     123              : {
     124              :   struct TMH_PendingOrder *po;
     125              : 
     126          102 :   while (NULL != (po = mi->po_head))
     127              :   {
     128            0 :     GNUNET_assert (po->in_dll);
     129            0 :     GNUNET_CONTAINER_DLL_remove (mi->po_head,
     130              :                                  mi->po_tail,
     131              :                                  po);
     132            0 :     GNUNET_assert (po ==
     133              :                    GNUNET_CONTAINER_heap_remove_root (order_timeout_heap));
     134            0 :     MHD_resume_connection (po->con);
     135            0 :     po->in_dll = false;
     136              :   }
     137          102 :   if (NULL != mi->po_eh)
     138              :   {
     139            0 :     TMH_db->event_listen_cancel (mi->po_eh);
     140            0 :     mi->po_eh = NULL;
     141              :   }
     142          102 :   if (NULL != order_timeout_task)
     143              :   {
     144            2 :     GNUNET_SCHEDULER_cancel (order_timeout_task);
     145            2 :     order_timeout_task = NULL;
     146              :   }
     147          102 :   if (NULL != order_timeout_heap)
     148              :   {
     149            2 :     GNUNET_CONTAINER_heap_destroy (order_timeout_heap);
     150            2 :     order_timeout_heap = NULL;
     151              :   }
     152          102 : }
     153              : 
     154              : 
     155              : /**
     156              :  * Task run to trigger timeouts on GET /orders requests with long polling.
     157              :  *
     158              :  * @param cls unused
     159              :  */
     160              : static void
     161            0 : order_timeout (void *cls)
     162              : {
     163              :   struct TMH_PendingOrder *po;
     164              :   struct TMH_MerchantInstance *mi;
     165              : 
     166              :   (void) cls;
     167            0 :   order_timeout_task = NULL;
     168              :   while (1)
     169              :   {
     170            0 :     po = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
     171            0 :     if (NULL == po)
     172              :     {
     173              :       /* release data structure, we don't need it right now */
     174            0 :       GNUNET_CONTAINER_heap_destroy (order_timeout_heap);
     175            0 :       order_timeout_heap = NULL;
     176            0 :       return;
     177              :     }
     178            0 :     if (GNUNET_TIME_absolute_is_future (po->long_poll_timeout))
     179            0 :       break;
     180            0 :     GNUNET_assert (po ==
     181              :                    GNUNET_CONTAINER_heap_remove_root (order_timeout_heap));
     182            0 :     po->hn = NULL;
     183            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     184              :                 "Resuming long polled job due to timeout\n");
     185            0 :     mi = po->mi;
     186            0 :     GNUNET_assert (po->in_dll);
     187            0 :     GNUNET_CONTAINER_DLL_remove (mi->po_head,
     188              :                                  mi->po_tail,
     189              :                                  po);
     190            0 :     if ( (NULL == mi->po_head) &&
     191            0 :          (NULL != mi->po_eh) )
     192              :     {
     193            0 :       TMH_db->event_listen_cancel (mi->po_eh);
     194            0 :       mi->po_eh = NULL;
     195              :     }
     196            0 :     po->in_dll = false;
     197            0 :     MHD_resume_connection (po->con);
     198            0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     199              :   }
     200            0 :   order_timeout_task = GNUNET_SCHEDULER_add_at (po->long_poll_timeout,
     201              :                                                 &order_timeout,
     202              :                                                 NULL);
     203              : }
     204              : 
     205              : 
     206              : /**
     207              :  * Cleanup our "context", where we stored the JSON array
     208              :  * we are building for the response.
     209              :  *
     210              :  * @param ctx context to clean up, must be a `struct AddOrderState *`
     211              :  */
     212              : static void
     213           14 : cleanup (void *ctx)
     214              : {
     215           14 :   struct TMH_PendingOrder *po = ctx;
     216              : 
     217           14 :   if (po->in_dll)
     218              :   {
     219            0 :     struct TMH_MerchantInstance *mi = po->mi;
     220              : 
     221            0 :     GNUNET_CONTAINER_DLL_remove (mi->po_head,
     222              :                                  mi->po_tail,
     223              :                                  po);
     224              :   }
     225           14 :   if (NULL != po->hn)
     226            0 :     GNUNET_assert (po ==
     227              :                    GNUNET_CONTAINER_heap_remove_node (po->hn));
     228           14 :   json_decref (po->pa);
     229           14 :   GNUNET_free (po->summary_filter);
     230           14 :   GNUNET_free (po);
     231           14 : }
     232              : 
     233              : 
     234              : /**
     235              :  * Closure for #process_refunds_cb().
     236              :  */
     237              : struct ProcessRefundsClosure
     238              : {
     239              :   /**
     240              :    * Place where we accumulate the refunds.
     241              :    */
     242              :   struct TALER_Amount total_refund_amount;
     243              : 
     244              :   /**
     245              :    * Set to an error code if something goes wrong.
     246              :    */
     247              :   enum TALER_ErrorCode ec;
     248              : };
     249              : 
     250              : 
     251              : /**
     252              :  * Function called with information about a refund.
     253              :  * It is responsible for summing up the refund amount.
     254              :  *
     255              :  * @param cls closure
     256              :  * @param refund_serial unique serial number of the refund
     257              :  * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
     258              :  * @param coin_pub public coin from which the refund comes from
     259              :  * @param exchange_url URL of the exchange that issued @a coin_pub
     260              :  * @param rtransaction_id identificator of the refund
     261              :  * @param reason human-readable explanation of the refund
     262              :  * @param refund_amount refund amount which is being taken from @a coin_pub
     263              :  * @param pending true if the this refund was not yet processed by the wallet/exchange
     264              :  */
     265              : static void
     266            0 : process_refunds_cb (void *cls,
     267              :                     uint64_t refund_serial,
     268              :                     struct GNUNET_TIME_Timestamp timestamp,
     269              :                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
     270              :                     const char *exchange_url,
     271              :                     uint64_t rtransaction_id,
     272              :                     const char *reason,
     273              :                     const struct TALER_Amount *refund_amount,
     274              :                     bool pending)
     275              : {
     276            0 :   struct ProcessRefundsClosure *prc = cls;
     277              : 
     278            0 :   if (GNUNET_OK !=
     279            0 :       TALER_amount_cmp_currency (&prc->total_refund_amount,
     280              :                                  refund_amount))
     281              :   {
     282              :     /* Database error, refunds in mixed currency in DB. Not OK! */
     283            0 :     prc->ec = TALER_EC_GENERIC_DB_INVARIANT_FAILURE;
     284            0 :     GNUNET_break (0);
     285            0 :     return;
     286              :   }
     287            0 :   GNUNET_assert (0 <=
     288              :                  TALER_amount_add (&prc->total_refund_amount,
     289              :                                    &prc->total_refund_amount,
     290              :                                    refund_amount));
     291              : }
     292              : 
     293              : 
     294              : /**
     295              :  * Add order details to our JSON array.
     296              :  *
     297              :  * @param cls some closure
     298              :  * @param orig_order_id the order this is about
     299              :  * @param order_serial serial ID of the order
     300              :  * @param creation_time when was the order created
     301              :  */
     302              : static void
     303           15 : add_order (void *cls,
     304              :            const char *orig_order_id,
     305              :            uint64_t order_serial,
     306              :            struct GNUNET_TIME_Timestamp creation_time)
     307              : {
     308           15 :   struct TMH_PendingOrder *po = cls;
     309           15 :   json_t *contract_terms = NULL;
     310              :   struct TALER_PrivateContractHashP h_contract_terms;
     311              :   enum GNUNET_DB_QueryStatus qs;
     312           15 :   char *order_id = NULL;
     313           15 :   bool refundable = false;
     314              :   bool paid;
     315              :   bool wired;
     316           15 :   struct TALER_MERCHANT_Contract *contract = NULL;
     317           15 :   int16_t choice_index = -1;
     318              : 
     319           15 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     320              :               "Adding order `%s' (%llu) to result set at instance `%s'\n",
     321              :               orig_order_id,
     322              :               (unsigned long long) order_serial,
     323              :               po->instance_id);
     324           15 :   qs = TMH_db->lookup_order_status_by_serial (TMH_db->cls,
     325              :                                               po->instance_id,
     326              :                                               order_serial,
     327              :                                               &order_id,
     328              :                                               &h_contract_terms,
     329              :                                               &paid);
     330           15 :   if (qs < 0)
     331              :   {
     332            0 :     GNUNET_break (0);
     333            0 :     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     334            2 :     return;
     335              :   }
     336           15 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     337              :   {
     338              :     /* Contract terms don't exist, so the order cannot be paid. */
     339            3 :     paid = false;
     340            3 :     if (NULL == orig_order_id)
     341              :     {
     342              :       /* Got a DB trigger about a new proposal, but it
     343              :          was already deleted again. Just ignore the event. */
     344            2 :       return;
     345              :     }
     346            1 :     order_id = GNUNET_strdup (orig_order_id);
     347              :   }
     348              : 
     349              :   {
     350              :     /* First try to find the order in the contracts */
     351              :     uint64_t os;
     352              :     bool session_matches;
     353              : 
     354           13 :     qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
     355              :                                          po->instance_id,
     356              :                                          order_id,
     357              :                                          NULL,
     358              :                                          &contract_terms,
     359              :                                          &os,
     360              :                                          &paid,
     361              :                                          &wired,
     362              :                                          &session_matches,
     363              :                                          NULL,
     364              :                                          &choice_index);
     365           13 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     366           12 :       GNUNET_break (os == order_serial);
     367              :   }
     368           13 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     369              :   {
     370              :     /* Might still be unclaimed, so try order table */
     371              :     struct TALER_MerchantPostDataHashP unused;
     372              : 
     373            1 :     paid = false;
     374            1 :     wired = false;
     375            1 :     qs = TMH_db->lookup_order (TMH_db->cls,
     376              :                                po->instance_id,
     377              :                                order_id,
     378              :                                NULL,
     379              :                                &unused,
     380              :                                &contract_terms);
     381              :   }
     382           13 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     383              :   {
     384            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     385              :                 "Order %llu disappeared during iteration. Skipping.\n",
     386              :                 (unsigned long long) order_serial);
     387            0 :     goto cleanup;
     388              :   }
     389           13 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     390              :   {
     391            0 :     GNUNET_break (0);
     392            0 :     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     393            0 :     goto cleanup;
     394              :   }
     395              : 
     396           13 :   contract = TALER_MERCHANT_contract_parse (contract_terms,
     397              :                                             true);
     398           13 :   if (NULL == contract)
     399              :   {
     400            0 :     GNUNET_break (0);
     401            0 :     po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
     402            0 :     goto cleanup;
     403              :   }
     404              : 
     405           13 :   if (GNUNET_TIME_absolute_is_future (
     406            0 :         contract->refund_deadline.abs_time) &&
     407              :       paid)
     408              :   {
     409            0 :     struct ProcessRefundsClosure prc = {
     410              :       .ec = TALER_EC_NONE
     411              :     };
     412              :     const struct TALER_Amount *brutto;
     413              : 
     414            0 :     switch (contract->version)
     415              :     {
     416            0 :     case TALER_MERCHANT_CONTRACT_VERSION_0:
     417            0 :       brutto = &contract->details.v0.brutto;
     418            0 :       break;
     419            0 :     case TALER_MERCHANT_CONTRACT_VERSION_1:
     420              :       {
     421            0 :         struct TALER_MERCHANT_ContractChoice *choice
     422            0 :           = &contract->details.v1.choices[choice_index];
     423              : 
     424            0 :         GNUNET_assert (choice_index < contract->details.v1.choices_len);
     425            0 :         brutto = &choice->amount;
     426              :       }
     427            0 :       break;
     428            0 :     default:
     429            0 :       GNUNET_break (0);
     430            0 :       goto cleanup;
     431              :     }
     432              : 
     433            0 :     GNUNET_assert (GNUNET_OK ==
     434              :                    TALER_amount_set_zero (brutto->currency,
     435              :                                           &prc.total_refund_amount));
     436            0 :     qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
     437              :                                           po->instance_id,
     438              :                                           &h_contract_terms,
     439              :                                           &process_refunds_cb,
     440              :                                           &prc);
     441            0 :     if (0 > qs)
     442              :     {
     443            0 :       GNUNET_break (0);
     444            0 :       po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     445            0 :       goto cleanup;
     446              :     }
     447            0 :     if (TALER_EC_NONE != prc.ec)
     448              :     {
     449            0 :       GNUNET_break (0);
     450            0 :       po->result = prc.ec;
     451            0 :       goto cleanup;
     452              :     }
     453            0 :     if (0 > TALER_amount_cmp (&prc.total_refund_amount,
     454              :                               brutto))
     455            0 :       refundable = true;
     456              :   }
     457              : 
     458           13 :   switch (contract->version)
     459              :   {
     460           13 :   case TALER_MERCHANT_CONTRACT_VERSION_0:
     461           13 :     if (TALER_amount_is_zero (&contract->details.v0.brutto) &&
     462            0 :         (po->of.wired != TALER_EXCHANGE_YNA_ALL) )
     463              :     {
     464              :       /* If we are actually filtering by wire status,
     465              :          and the order was over an amount of zero,
     466              :          do not return it as wire status is not
     467              :          exactly meaningful for orders over zero. */
     468            0 :       goto cleanup;
     469              :     }
     470           13 :     GNUNET_assert (
     471              :       0 ==
     472              :       json_array_append_new (
     473              :         po->pa,
     474              :         GNUNET_JSON_PACK (
     475              :           GNUNET_JSON_pack_string ("order_id",
     476              :                                    contract->order_id),
     477              :           GNUNET_JSON_pack_uint64 ("row_id",
     478              :                                    order_serial),
     479              :           GNUNET_JSON_pack_timestamp ("timestamp",
     480              :                                       creation_time),
     481              :           TALER_JSON_pack_amount (
     482              :             "amount",
     483              :             &contract->details.v0.brutto),
     484              :           GNUNET_JSON_pack_string ("summary",
     485              :                                    contract->summary),
     486              :           GNUNET_JSON_pack_bool ("refundable",
     487              :                                  refundable),
     488              :           GNUNET_JSON_pack_bool ("paid",
     489              :                                  paid))));
     490           13 :     break;
     491            0 :   case TALER_MERCHANT_CONTRACT_VERSION_1:
     492            0 :     if (-1 == choice_index)
     493            0 :       choice_index = 0; /* default choice */
     494            0 :     GNUNET_assert (choice_index < contract->details.v1.choices_len);
     495              :     {
     496            0 :       struct TALER_MERCHANT_ContractChoice *choice
     497            0 :         = &contract->details.v1.choices[choice_index];
     498              : 
     499            0 :       GNUNET_assert (
     500              :         0 ==
     501              :         json_array_append_new (
     502              :           po->pa,
     503              :           GNUNET_JSON_PACK (
     504              :             GNUNET_JSON_pack_string ("order_id",
     505              :                                      contract->order_id),
     506              :             GNUNET_JSON_pack_uint64 ("row_id",
     507              :                                      order_serial),
     508              :             GNUNET_JSON_pack_timestamp ("timestamp",
     509              :                                         creation_time),
     510              :             TALER_JSON_pack_amount ("amount",
     511              :                                     &choice->amount),
     512              :             GNUNET_JSON_pack_string ("summary",
     513              :                                      contract->summary),
     514              :             GNUNET_JSON_pack_bool ("refundable",
     515              :                                    refundable),
     516              :             GNUNET_JSON_pack_bool ("paid",
     517              :                                    paid))));
     518              :     }
     519            0 :     break;
     520            0 :   default:
     521            0 :     GNUNET_break (0);
     522            0 :     goto cleanup;
     523              :   }
     524              : 
     525           13 : cleanup:
     526           13 :   json_decref (contract_terms);
     527           13 :   GNUNET_free (order_id);
     528           13 :   if (NULL != contract)
     529              :   {
     530           13 :     TALER_MERCHANT_contract_free (contract);
     531           13 :     contract = NULL;
     532              :   }
     533              : }
     534              : 
     535              : 
     536              : /**
     537              :  * We have received a trigger from the database
     538              :  * that we should (possibly) resume some requests.
     539              :  *
     540              :  * @param cls a `struct TMH_MerchantInstance`
     541              :  * @param extra a `struct TMH_OrderChangeEventP`
     542              :  * @param extra_size number of bytes in @a extra
     543              :  */
     544              : static void
     545            2 : resume_by_event (void *cls,
     546              :                  const void *extra,
     547              :                  size_t extra_size)
     548              : {
     549            2 :   struct TMH_MerchantInstance *mi = cls;
     550            2 :   const struct TMH_OrderChangeEventDetailsP *oce = extra;
     551              :   struct TMH_PendingOrder *pn;
     552              :   enum TMH_OrderStateFlags osf;
     553              :   uint64_t order_serial_id;
     554              :   struct GNUNET_TIME_Timestamp date;
     555              : 
     556            2 :   if (sizeof (*oce) != extra_size)
     557              :   {
     558            0 :     GNUNET_break (0);
     559            0 :     return;
     560              :   }
     561            2 :   osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state);
     562            2 :   order_serial_id = GNUNET_ntohll (oce->order_serial_id);
     563            2 :   date = GNUNET_TIME_timestamp_ntoh (oce->execution_date);
     564            2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     565              :               "Received notification about new order %llu\n",
     566              :               (unsigned long long) order_serial_id);
     567            2 :   for (struct TMH_PendingOrder *po = mi->po_head;
     568            4 :        NULL != po;
     569            2 :        po = pn)
     570              :   {
     571            2 :     pn = po->next;
     572            4 :     if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) ==
     573            2 :                (0 != (osf & TMH_OSF_PAID))) ||
     574            0 :               (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) &&
     575            2 :             ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) ==
     576            2 :                (0 != (osf & TMH_OSF_REFUNDED))) ||
     577            0 :               (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) &&
     578            2 :             ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) ==
     579            2 :                (0 != (osf & TMH_OSF_WIRED))) ||
     580            0 :               (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) )
     581              :     {
     582            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     583              :                   "Client %p waits on different order type\n",
     584              :                   po);
     585            0 :       continue;
     586              :     }
     587            2 :     if (po->of.delta > 0)
     588              :     {
     589            2 :       if (order_serial_id < po->of.start_row)
     590              :       {
     591            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     592              :                     "Client %p waits on different order row\n",
     593              :                     po);
     594            0 :         continue;
     595              :       }
     596            2 :       if (GNUNET_TIME_timestamp_cmp (date,
     597              :                                      <,
     598              :                                      po->of.date))
     599              :       {
     600            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     601              :                     "Client %p waits on different order date\n",
     602              :                     po);
     603            0 :         continue;
     604              :       }
     605            2 :       po->of.delta--;
     606              :     }
     607              :     else
     608              :     {
     609            0 :       if (order_serial_id > po->of.start_row)
     610              :       {
     611            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     612              :                     "Client %p waits on different order row\n",
     613              :                     po);
     614            0 :         continue;
     615              :       }
     616            0 :       if (GNUNET_TIME_timestamp_cmp (date,
     617              :                                      >,
     618              :                                      po->of.date))
     619              :       {
     620            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     621              :                     "Client %p waits on different order date\n",
     622              :                     po);
     623            0 :         continue;
     624              :       }
     625            0 :       po->of.delta++;
     626              :     }
     627            2 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     628              :                 "Waking up client %p!\n",
     629              :                 po);
     630            2 :     add_order (po,
     631              :                NULL,
     632              :                order_serial_id,
     633              :                date);
     634            2 :     GNUNET_assert (po->in_dll);
     635            2 :     GNUNET_CONTAINER_DLL_remove (mi->po_head,
     636              :                                  mi->po_tail,
     637              :                                  po);
     638            2 :     po->in_dll = false;
     639            2 :     GNUNET_assert (po ==
     640              :                    GNUNET_CONTAINER_heap_remove_node (po->hn));
     641            2 :     po->hn = NULL;
     642            2 :     MHD_resume_connection (po->con);
     643            2 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     644              :   }
     645            2 :   if (NULL == mi->po_head)
     646              :   {
     647            2 :     TMH_db->event_listen_cancel (mi->po_eh);
     648            2 :     mi->po_eh = NULL;
     649              :   }
     650              : }
     651              : 
     652              : 
     653              : /**
     654              :  * There has been a change or addition of a new @a order_id.  Wake up
     655              :  * long-polling clients that may have been waiting for this event.
     656              :  *
     657              :  * @param mi the instance where the order changed
     658              :  * @param osf order state flags
     659              :  * @param date execution date of the order
     660              :  * @param order_serial_id serial ID of the order in the database
     661              :  */
     662              : void
     663          148 : TMH_notify_order_change (struct TMH_MerchantInstance *mi,
     664              :                          enum TMH_OrderStateFlags osf,
     665              :                          struct GNUNET_TIME_Timestamp date,
     666              :                          uint64_t order_serial_id)
     667              : {
     668          296 :   struct TMH_OrderChangeEventDetailsP oce = {
     669          148 :     .order_serial_id = GNUNET_htonll (order_serial_id),
     670          148 :     .execution_date = GNUNET_TIME_timestamp_hton (date),
     671          148 :     .order_state = htonl (osf)
     672              :   };
     673          148 :   struct TMH_OrderChangeEventP eh = {
     674          148 :     .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
     675          148 :     .header.size = htons (sizeof (eh)),
     676              :     .merchant_pub = mi->merchant_pub
     677              :   };
     678              : 
     679          148 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     680              :               "Notifying clients of new order %llu at %s\n",
     681              :               (unsigned long long) order_serial_id,
     682              :               TALER_B2S (&mi->merchant_pub));
     683          148 :   TMH_db->event_notify (TMH_db->cls,
     684              :                         &eh.header,
     685              :                         &oce,
     686              :                         sizeof (oce));
     687          148 : }
     688              : 
     689              : 
     690              : /**
     691              :  * Transforms an (untrusted) input filter into a Postgresql LIKE filter.
     692              :  * Escapes "%" and "_" in the @a input and adds "%" at the beginning
     693              :  * and the end to turn the @a input into a suitable Postgresql argument.
     694              :  *
     695              :  * @param input text to turn into a substring match expression, or NULL
     696              :  * @return NULL if @a input was NULL, otherwise transformed @a input
     697              :  */
     698              : static char *
     699           14 : tr (const char *input)
     700              : {
     701              :   char *out;
     702              :   size_t slen;
     703              :   size_t wpos;
     704              : 
     705           14 :   if (NULL == input)
     706           14 :     return NULL;
     707            0 :   slen = strlen (input);
     708            0 :   out = GNUNET_malloc (slen * 2 + 3);
     709            0 :   wpos = 0;
     710            0 :   out[wpos++] = '%';
     711            0 :   for (size_t i = 0; i<slen; i++)
     712              :   {
     713            0 :     char c = input[i];
     714              : 
     715            0 :     if ( (c == '%') ||
     716              :          (c == '_') )
     717            0 :       out[wpos++] = '\\';
     718            0 :     out[wpos++] = c;
     719              :   }
     720            0 :   out[wpos++] = '%';
     721            0 :   GNUNET_assert (wpos < slen * 2 + 3);
     722            0 :   return out;
     723              : }
     724              : 
     725              : 
     726              : /**
     727              :  * Handle a GET "/orders" request.
     728              :  *
     729              :  * @param rh context of the handler
     730              :  * @param connection the MHD connection to handle
     731              :  * @param[in,out] hc context with further information about the request
     732              :  * @return MHD result code
     733              :  */
     734              : MHD_RESULT
     735           16 : TMH_private_get_orders (const struct TMH_RequestHandler *rh,
     736              :                         struct MHD_Connection *connection,
     737              :                         struct TMH_HandlerContext *hc)
     738              : {
     739           16 :   struct TMH_PendingOrder *po = hc->ctx;
     740              :   enum GNUNET_DB_QueryStatus qs;
     741              : 
     742           16 :   if (NULL != po)
     743              :   {
     744              :     /* resumed from long-polling, return answer we already have
     745              :        in 'hc->ctx' */
     746            2 :     if (TALER_EC_NONE != po->result)
     747              :     {
     748            0 :       GNUNET_break (0);
     749            0 :       return TALER_MHD_reply_with_error (connection,
     750              :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     751              :                                          po->result,
     752              :                                          NULL);
     753              :     }
     754            2 :     return TALER_MHD_REPLY_JSON_PACK (
     755              :       connection,
     756              :       MHD_HTTP_OK,
     757              :       GNUNET_JSON_pack_array_incref ("orders",
     758              :                                      po->pa));
     759              :   }
     760           14 :   po = GNUNET_new (struct TMH_PendingOrder);
     761           14 :   hc->ctx = po;
     762           14 :   hc->cc = &cleanup;
     763           14 :   po->con = connection;
     764           14 :   po->pa = json_array ();
     765           14 :   GNUNET_assert (NULL != po->pa);
     766           14 :   po->instance_id = hc->instance->settings.id;
     767           14 :   po->mi = hc->instance;
     768              : 
     769           14 :   if (! (TALER_MHD_arg_to_yna (connection,
     770              :                                "paid",
     771              :                                TALER_EXCHANGE_YNA_ALL,
     772              :                                &po->of.paid)) )
     773              :   {
     774            0 :     GNUNET_break_op (0);
     775            0 :     return TALER_MHD_reply_with_error (connection,
     776              :                                        MHD_HTTP_BAD_REQUEST,
     777              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     778              :                                        "paid");
     779              :   }
     780           14 :   if (! (TALER_MHD_arg_to_yna (connection,
     781              :                                "refunded",
     782              :                                TALER_EXCHANGE_YNA_ALL,
     783              :                                &po->of.refunded)) )
     784              :   {
     785            0 :     GNUNET_break_op (0);
     786            0 :     return TALER_MHD_reply_with_error (connection,
     787              :                                        MHD_HTTP_BAD_REQUEST,
     788              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     789              :                                        "refunded");
     790              :   }
     791           14 :   if (! (TALER_MHD_arg_to_yna (connection,
     792              :                                "wired",
     793              :                                TALER_EXCHANGE_YNA_ALL,
     794              :                                &po->of.wired)) )
     795              :   {
     796            0 :     GNUNET_break_op (0);
     797            0 :     return TALER_MHD_reply_with_error (connection,
     798              :                                        MHD_HTTP_BAD_REQUEST,
     799              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     800              :                                        "wired");
     801              :   }
     802           14 :   po->of.delta = -20;
     803              :   /* deprecated in protocol v12 */
     804           14 :   TALER_MHD_parse_request_snumber (connection,
     805              :                                    "delta",
     806              :                                    &po->of.delta);
     807              :   /* since protocol v12 */
     808           14 :   TALER_MHD_parse_request_snumber (connection,
     809              :                                    "limit",
     810              :                                    &po->of.delta);
     811           14 :   if ( (-MAX_DELTA > po->of.delta) ||
     812           14 :        (po->of.delta > MAX_DELTA) )
     813              :   {
     814            0 :     GNUNET_break_op (0);
     815            0 :     return TALER_MHD_reply_with_error (connection,
     816              :                                        MHD_HTTP_BAD_REQUEST,
     817              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     818              :                                        "limit");
     819              :   }
     820              :   {
     821              :     const char *date_s_str;
     822              : 
     823           14 :     date_s_str = MHD_lookup_connection_value (connection,
     824              :                                               MHD_GET_ARGUMENT_KIND,
     825              :                                               "date_s");
     826           14 :     if (NULL == date_s_str)
     827              :     {
     828           14 :       if (po->of.delta > 0)
     829            2 :         po->of.date = GNUNET_TIME_UNIT_ZERO_TS;
     830              :       else
     831           12 :         po->of.date = GNUNET_TIME_UNIT_FOREVER_TS;
     832              :     }
     833              :     else
     834              :     {
     835              :       char dummy;
     836              :       unsigned long long ll;
     837              : 
     838            0 :       if (1 !=
     839            0 :           sscanf (date_s_str,
     840              :                   "%llu%c",
     841              :                   &ll,
     842              :                   &dummy))
     843              :       {
     844            0 :         GNUNET_break_op (0);
     845            0 :         return TALER_MHD_reply_with_error (connection,
     846              :                                            MHD_HTTP_BAD_REQUEST,
     847              :                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
     848              :                                            "date_s");
     849              :       }
     850              : 
     851            0 :       po->of.date = GNUNET_TIME_absolute_to_timestamp (
     852              :         GNUNET_TIME_absolute_from_s (ll));
     853            0 :       if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time))
     854              :       {
     855            0 :         GNUNET_break_op (0);
     856            0 :         return TALER_MHD_reply_with_error (connection,
     857              :                                            MHD_HTTP_BAD_REQUEST,
     858              :                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
     859              :                                            "date_s");
     860              :       }
     861              :     }
     862              :   }
     863           14 :   if (po->of.delta > 0)
     864            2 :     po->of.start_row = 0;
     865              :   else
     866           12 :     po->of.start_row = INT64_MAX;
     867              :   /* deprecated in protocol v12 */
     868           14 :   TALER_MHD_parse_request_number (connection,
     869              :                                   "start",
     870              :                                   &po->of.start_row);
     871              :   /* since protocol v12 */
     872           14 :   TALER_MHD_parse_request_number (connection,
     873              :                                   "offset",
     874              :                                   &po->of.start_row);
     875           14 :   if (INT64_MAX < po->of.start_row)
     876              :   {
     877            0 :     GNUNET_break_op (0);
     878            0 :     return TALER_MHD_reply_with_error (connection,
     879              :                                        MHD_HTTP_BAD_REQUEST,
     880              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     881              :                                        "offset");
     882              :   }
     883           14 :   po->summary_filter = tr (MHD_lookup_connection_value (connection,
     884              :                                                         MHD_GET_ARGUMENT_KIND,
     885              :                                                         "summary_filter"));
     886           14 :   po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */
     887              :   po->of.session_id
     888           14 :     = MHD_lookup_connection_value (connection,
     889              :                                    MHD_GET_ARGUMENT_KIND,
     890              :                                    "session_id");
     891              :   po->of.fulfillment_url
     892           14 :     = MHD_lookup_connection_value (connection,
     893              :                                    MHD_GET_ARGUMENT_KIND,
     894              :                                    "fulfillment_url");
     895           14 :   TALER_MHD_parse_request_timeout (connection,
     896              :                                    &po->long_poll_timeout);
     897           14 :   if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout))
     898              :   {
     899            0 :     GNUNET_break_op (0);
     900            0 :     return TALER_MHD_reply_with_error (connection,
     901              :                                        MHD_HTTP_BAD_REQUEST,
     902              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     903              :                                        "timeout_ms");
     904              :   }
     905           14 :   po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout);
     906           26 :   if ( (0 >= po->of.delta) &&
     907           12 :        (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
     908              :   {
     909            0 :     GNUNET_break_op (0);
     910            0 :     po->of.timeout = GNUNET_TIME_UNIT_ZERO;
     911            0 :     po->long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
     912              :   }
     913              : 
     914           14 :   qs = TMH_db->lookup_orders (TMH_db->cls,
     915              :                               po->instance_id,
     916           14 :                               &po->of,
     917              :                               &add_order,
     918              :                               po);
     919           14 :   if (0 > qs)
     920              :   {
     921            0 :     GNUNET_break (0);
     922            0 :     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     923              :   }
     924           14 :   if (TALER_EC_NONE != po->result)
     925              :   {
     926            0 :     GNUNET_break (0);
     927            0 :     return TALER_MHD_reply_with_error (connection,
     928              :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     929              :                                        po->result,
     930              :                                        NULL);
     931              :   }
     932           20 :   if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
     933            6 :        (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
     934              :   {
     935            2 :     struct TMH_MerchantInstance *mi = hc->instance;
     936              : 
     937              :     /* setup timeout heap (if not yet exists) */
     938            2 :     if (NULL == order_timeout_heap)
     939              :       order_timeout_heap
     940            2 :         = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
     941            2 :     po->hn = GNUNET_CONTAINER_heap_insert (order_timeout_heap,
     942              :                                            po,
     943              :                                            po->long_poll_timeout.abs_value_us);
     944            2 :     GNUNET_CONTAINER_DLL_insert (mi->po_head,
     945              :                                  mi->po_tail,
     946              :                                  po);
     947            2 :     po->in_dll = true;
     948            2 :     if (NULL == mi->po_eh)
     949              :     {
     950            2 :       struct TMH_OrderChangeEventP change_eh = {
     951            2 :         .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
     952            2 :         .header.size = htons (sizeof (change_eh)),
     953              :         .merchant_pub = mi->merchant_pub
     954              :       };
     955              : 
     956            2 :       mi->po_eh = TMH_db->event_listen (TMH_db->cls,
     957              :                                         &change_eh.header,
     958            2 :                                         GNUNET_TIME_UNIT_FOREVER_REL,
     959              :                                         &resume_by_event,
     960              :                                         mi);
     961              :     }
     962            2 :     MHD_suspend_connection (connection);
     963              :     {
     964              :       struct TMH_PendingOrder *pot;
     965              : 
     966              :       /* start timeout task */
     967            2 :       pot = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
     968            2 :       if (NULL != order_timeout_task)
     969            0 :         GNUNET_SCHEDULER_cancel (order_timeout_task);
     970            2 :       order_timeout_task = GNUNET_SCHEDULER_add_at (pot->long_poll_timeout,
     971              :                                                     &order_timeout,
     972              :                                                     NULL);
     973              :     }
     974            2 :     return MHD_YES;
     975              :   }
     976           12 :   return TALER_MHD_REPLY_JSON_PACK (
     977              :     connection,
     978              :     MHD_HTTP_OK,
     979              :     GNUNET_JSON_pack_array_incref ("orders",
     980              :                                    po->pa));
     981              : }
     982              : 
     983              : 
     984              : /* end of taler-merchant-httpd_private-get-orders.c */
        

Generated by: LCOV version 2.0-1