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: 56.3 % 323 182
Test Date: 2025-12-07 14:29:45 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 granted refunds.
     241              :    */
     242              :   struct TALER_Amount total_refund_amount;
     243              : 
     244              :   /**
     245              :    * Place where we accumulate the pending refunds.
     246              :    */
     247              :   struct TALER_Amount pending_refund_amount;
     248              : 
     249              :   /**
     250              :    * Set to an error code if something goes wrong.
     251              :    */
     252              :   enum TALER_ErrorCode ec;
     253              : };
     254              : 
     255              : 
     256              : /**
     257              :  * Function called with information about a refund.
     258              :  * It is responsible for summing up the refund amount.
     259              :  *
     260              :  * @param cls closure
     261              :  * @param refund_serial unique serial number of the refund
     262              :  * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
     263              :  * @param coin_pub public coin from which the refund comes from
     264              :  * @param exchange_url URL of the exchange that issued @a coin_pub
     265              :  * @param rtransaction_id identificator of the refund
     266              :  * @param reason human-readable explanation of the refund
     267              :  * @param refund_amount refund amount which is being taken from @a coin_pub
     268              :  * @param pending true if the this refund was not yet processed by the wallet/exchange
     269              :  */
     270              : static void
     271            0 : process_refunds_cb (void *cls,
     272              :                     uint64_t refund_serial,
     273              :                     struct GNUNET_TIME_Timestamp timestamp,
     274              :                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
     275              :                     const char *exchange_url,
     276              :                     uint64_t rtransaction_id,
     277              :                     const char *reason,
     278              :                     const struct TALER_Amount *refund_amount,
     279              :                     bool pending)
     280              : {
     281            0 :   struct ProcessRefundsClosure *prc = cls;
     282              : 
     283            0 :   if (GNUNET_OK !=
     284            0 :       TALER_amount_cmp_currency (&prc->total_refund_amount,
     285              :                                  refund_amount))
     286              :   {
     287              :     /* Database error, refunds in mixed currency in DB. Not OK! */
     288            0 :     prc->ec = TALER_EC_GENERIC_DB_INVARIANT_FAILURE;
     289            0 :     GNUNET_break (0);
     290            0 :     return;
     291              :   }
     292            0 :   GNUNET_assert (0 <=
     293              :                  TALER_amount_add (&prc->total_refund_amount,
     294              :                                    &prc->total_refund_amount,
     295              :                                    refund_amount));
     296            0 :   if (pending)
     297            0 :     GNUNET_assert (0 <=
     298              :                    TALER_amount_add (&prc->pending_refund_amount,
     299              :                                      &prc->pending_refund_amount,
     300              :                                      refund_amount));
     301              : }
     302              : 
     303              : 
     304              : /**
     305              :  * Add order details to our JSON array.
     306              :  *
     307              :  * @param cls some closure
     308              :  * @param orig_order_id the order this is about
     309              :  * @param order_serial serial ID of the order
     310              :  * @param creation_time when was the order created
     311              :  */
     312              : static void
     313           17 : add_order (void *cls,
     314              :            const char *orig_order_id,
     315              :            uint64_t order_serial,
     316              :            struct GNUNET_TIME_Timestamp creation_time)
     317              : {
     318           17 :   struct TMH_PendingOrder *po = cls;
     319           17 :   json_t *contract_terms = NULL;
     320              :   struct TALER_PrivateContractHashP h_contract_terms;
     321              :   enum GNUNET_DB_QueryStatus qs;
     322           17 :   char *order_id = NULL;
     323           17 :   bool refundable = false;
     324              :   bool paid;
     325              :   bool wired;
     326           17 :   struct TALER_MERCHANT_Contract *contract = NULL;
     327           17 :   int16_t choice_index = -1;
     328           17 :   struct ProcessRefundsClosure prc = {
     329              :     .ec = TALER_EC_NONE
     330              :   };
     331              : 
     332           17 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     333              :               "Adding order `%s' (%llu) to result set at instance `%s'\n",
     334              :               orig_order_id,
     335              :               (unsigned long long) order_serial,
     336              :               po->instance_id);
     337           17 :   qs = TMH_db->lookup_order_status_by_serial (TMH_db->cls,
     338              :                                               po->instance_id,
     339              :                                               order_serial,
     340              :                                               &order_id,
     341              :                                               &h_contract_terms,
     342              :                                               &paid);
     343           17 :   if (qs < 0)
     344              :   {
     345            0 :     GNUNET_break (0);
     346            0 :     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     347            2 :     return;
     348              :   }
     349           17 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     350              :   {
     351              :     /* Contract terms don't exist, so the order cannot be paid. */
     352            3 :     paid = false;
     353            3 :     if (NULL == orig_order_id)
     354              :     {
     355              :       /* Got a DB trigger about a new proposal, but it
     356              :          was already deleted again. Just ignore the event. */
     357            2 :       return;
     358              :     }
     359            1 :     order_id = GNUNET_strdup (orig_order_id);
     360              :   }
     361              : 
     362              :   {
     363              :     /* First try to find the order in the contracts */
     364              :     uint64_t os;
     365              :     bool session_matches;
     366              : 
     367           15 :     qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
     368              :                                          po->instance_id,
     369              :                                          order_id,
     370              :                                          NULL,
     371              :                                          &contract_terms,
     372              :                                          &os,
     373              :                                          &paid,
     374              :                                          &wired,
     375              :                                          &session_matches,
     376              :                                          NULL,
     377              :                                          &choice_index);
     378           15 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     379           14 :       GNUNET_break (os == order_serial);
     380              :   }
     381           15 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     382              :   {
     383              :     /* Might still be unclaimed, so try order table */
     384              :     struct TALER_MerchantPostDataHashP unused;
     385              : 
     386            1 :     paid = false;
     387            1 :     wired = false;
     388            1 :     qs = TMH_db->lookup_order (TMH_db->cls,
     389              :                                po->instance_id,
     390              :                                order_id,
     391              :                                NULL,
     392              :                                &unused,
     393              :                                &contract_terms);
     394              :   }
     395           15 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     396              :   {
     397            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     398              :                 "Order %llu disappeared during iteration. Skipping.\n",
     399              :                 (unsigned long long) order_serial);
     400            0 :     goto cleanup;
     401              :   }
     402           15 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     403              :   {
     404            0 :     GNUNET_break (0);
     405            0 :     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     406            0 :     goto cleanup;
     407              :   }
     408              : 
     409           15 :   contract = TALER_MERCHANT_contract_parse (contract_terms,
     410              :                                             true);
     411           15 :   if (NULL == contract)
     412              :   {
     413            0 :     GNUNET_break (0);
     414            0 :     po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
     415            0 :     goto cleanup;
     416              :   }
     417              : 
     418           15 :   if (paid)
     419              :   {
     420              :     const struct TALER_Amount *brutto;
     421              : 
     422            5 :     switch (contract->version)
     423              :     {
     424            5 :     case TALER_MERCHANT_CONTRACT_VERSION_0:
     425            5 :       brutto = &contract->details.v0.brutto;
     426            5 :       break;
     427            0 :     case TALER_MERCHANT_CONTRACT_VERSION_1:
     428              :       {
     429            0 :         struct TALER_MERCHANT_ContractChoice *choice
     430            0 :           = &contract->details.v1.choices[choice_index];
     431              : 
     432            0 :         GNUNET_assert (choice_index < contract->details.v1.choices_len);
     433            0 :         brutto = &choice->amount;
     434              :       }
     435            0 :       break;
     436            0 :     default:
     437            0 :       GNUNET_break (0);
     438            0 :       goto cleanup;
     439              :     }
     440            5 :     GNUNET_assert (GNUNET_OK ==
     441              :                    TALER_amount_set_zero (brutto->currency,
     442              :                                           &prc.total_refund_amount));
     443            5 :     GNUNET_assert (GNUNET_OK ==
     444              :                    TALER_amount_set_zero (brutto->currency,
     445              :                                           &prc.pending_refund_amount));
     446              : 
     447            5 :     qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
     448              :                                           po->instance_id,
     449              :                                           &h_contract_terms,
     450              :                                           &process_refunds_cb,
     451              :                                           &prc);
     452            5 :     if (0 > qs)
     453              :     {
     454            0 :       GNUNET_break (0);
     455            0 :       po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     456            0 :       goto cleanup;
     457              :     }
     458            5 :     if (TALER_EC_NONE != prc.ec)
     459              :     {
     460            0 :       GNUNET_break (0);
     461            0 :       po->result = prc.ec;
     462            0 :       goto cleanup;
     463              :     }
     464            5 :     if (0 > TALER_amount_cmp (&prc.total_refund_amount,
     465              :                               brutto))
     466            5 :       refundable = true;
     467              :   }
     468              : 
     469           15 :   switch (contract->version)
     470              :   {
     471           15 :   case TALER_MERCHANT_CONTRACT_VERSION_0:
     472           15 :     if (TALER_amount_is_zero (&contract->details.v0.brutto) &&
     473            0 :         (po->of.wired != TALER_EXCHANGE_YNA_ALL) )
     474              :     {
     475              :       /* If we are actually filtering by wire status,
     476              :          and the order was over an amount of zero,
     477              :          do not return it as wire status is not
     478              :          exactly meaningful for orders over zero. */
     479            0 :       goto cleanup;
     480              :     }
     481           15 :     GNUNET_assert (
     482              :       0 ==
     483              :       json_array_append_new (
     484              :         po->pa,
     485              :         GNUNET_JSON_PACK (
     486              :           GNUNET_JSON_pack_string ("order_id",
     487              :                                    contract->order_id),
     488              :           GNUNET_JSON_pack_uint64 ("row_id",
     489              :                                    order_serial),
     490              :           GNUNET_JSON_pack_timestamp ("timestamp",
     491              :                                       creation_time),
     492              :           TALER_JSON_pack_amount (
     493              :             "amount",
     494              :             &contract->details.v0.brutto),
     495              :           GNUNET_JSON_pack_allow_null (
     496              :             TALER_JSON_pack_amount (
     497              :               "refund_amount",
     498              :               paid
     499              :             ? &prc.total_refund_amount
     500              :             : NULL)),
     501              :           GNUNET_JSON_pack_allow_null (
     502              :             TALER_JSON_pack_amount (
     503              :               "pending_refund_amount",
     504              :               paid
     505              :             ? &prc.pending_refund_amount
     506              :             : NULL)),
     507              :           TALER_JSON_pack_amount (
     508              :             "amount",
     509              :             &contract->details.v0.brutto),
     510              :           GNUNET_JSON_pack_string ("summary",
     511              :                                    contract->summary),
     512              :           GNUNET_JSON_pack_bool ("refundable",
     513              :                                  refundable),
     514              :           GNUNET_JSON_pack_bool ("paid",
     515              :                                  paid))));
     516           15 :     break;
     517            0 :   case TALER_MERCHANT_CONTRACT_VERSION_1:
     518            0 :     if (-1 == choice_index)
     519            0 :       choice_index = 0; /* default choice */
     520            0 :     GNUNET_assert (choice_index < contract->details.v1.choices_len);
     521              :     {
     522            0 :       struct TALER_MERCHANT_ContractChoice *choice
     523            0 :         = &contract->details.v1.choices[choice_index];
     524              : 
     525            0 :       GNUNET_assert (
     526              :         0 ==
     527              :         json_array_append_new (
     528              :           po->pa,
     529              :           GNUNET_JSON_PACK (
     530              :             GNUNET_JSON_pack_string ("order_id",
     531              :                                      contract->order_id),
     532              :             GNUNET_JSON_pack_uint64 ("row_id",
     533              :                                      order_serial),
     534              :             GNUNET_JSON_pack_timestamp ("timestamp",
     535              :                                         creation_time),
     536              :             TALER_JSON_pack_amount ("amount",
     537              :                                     &choice->amount),
     538              :             GNUNET_JSON_pack_allow_null (
     539              :               TALER_JSON_pack_amount (
     540              :                 "refund_amount",
     541              :                 paid
     542              :               ? &prc.total_refund_amount
     543              :               : NULL)),
     544              :             GNUNET_JSON_pack_allow_null (
     545              :               TALER_JSON_pack_amount (
     546              :                 "pending_refund_amount",
     547              :                 paid
     548              :               ? &prc.pending_refund_amount
     549              :               : NULL)),
     550              :             GNUNET_JSON_pack_string ("summary",
     551              :                                      contract->summary),
     552              :             GNUNET_JSON_pack_bool ("refundable",
     553              :                                    refundable),
     554              :             GNUNET_JSON_pack_bool ("paid",
     555              :                                    paid))));
     556              :     }
     557            0 :     break;
     558            0 :   default:
     559            0 :     GNUNET_break (0);
     560            0 :     goto cleanup;
     561              :   }
     562              : 
     563           15 : cleanup:
     564           15 :   json_decref (contract_terms);
     565           15 :   GNUNET_free (order_id);
     566           15 :   if (NULL != contract)
     567              :   {
     568           15 :     TALER_MERCHANT_contract_free (contract);
     569           15 :     contract = NULL;
     570              :   }
     571              : }
     572              : 
     573              : 
     574              : /**
     575              :  * We have received a trigger from the database
     576              :  * that we should (possibly) resume some requests.
     577              :  *
     578              :  * @param cls a `struct TMH_MerchantInstance`
     579              :  * @param extra a `struct TMH_OrderChangeEventP`
     580              :  * @param extra_size number of bytes in @a extra
     581              :  */
     582              : static void
     583            2 : resume_by_event (void *cls,
     584              :                  const void *extra,
     585              :                  size_t extra_size)
     586              : {
     587            2 :   struct TMH_MerchantInstance *mi = cls;
     588            2 :   const struct TMH_OrderChangeEventDetailsP *oce = extra;
     589              :   struct TMH_PendingOrder *pn;
     590              :   enum TMH_OrderStateFlags osf;
     591              :   uint64_t order_serial_id;
     592              :   struct GNUNET_TIME_Timestamp date;
     593              : 
     594            2 :   if (sizeof (*oce) != extra_size)
     595              :   {
     596            0 :     GNUNET_break (0);
     597            0 :     return;
     598              :   }
     599            2 :   osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state);
     600            2 :   order_serial_id = GNUNET_ntohll (oce->order_serial_id);
     601            2 :   date = GNUNET_TIME_timestamp_ntoh (oce->execution_date);
     602            2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     603              :               "Received notification about new order %llu\n",
     604              :               (unsigned long long) order_serial_id);
     605            2 :   for (struct TMH_PendingOrder *po = mi->po_head;
     606            4 :        NULL != po;
     607            2 :        po = pn)
     608              :   {
     609            2 :     pn = po->next;
     610            4 :     if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) ==
     611            2 :                (0 != (osf & TMH_OSF_PAID))) ||
     612            0 :               (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) &&
     613            2 :             ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) ==
     614            2 :                (0 != (osf & TMH_OSF_REFUNDED))) ||
     615            0 :               (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) &&
     616            2 :             ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) ==
     617            2 :                (0 != (osf & TMH_OSF_WIRED))) ||
     618            0 :               (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) )
     619              :     {
     620            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     621              :                   "Client %p waits on different order type\n",
     622              :                   po);
     623            0 :       continue;
     624              :     }
     625            2 :     if (po->of.delta > 0)
     626              :     {
     627            2 :       if (order_serial_id < po->of.start_row)
     628              :       {
     629            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     630              :                     "Client %p waits on different order row\n",
     631              :                     po);
     632            0 :         continue;
     633              :       }
     634            2 :       if (GNUNET_TIME_timestamp_cmp (date,
     635              :                                      <,
     636              :                                      po->of.date))
     637              :       {
     638            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     639              :                     "Client %p waits on different order date\n",
     640              :                     po);
     641            0 :         continue;
     642              :       }
     643            2 :       po->of.delta--;
     644              :     }
     645              :     else
     646              :     {
     647            0 :       if (order_serial_id > po->of.start_row)
     648              :       {
     649            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     650              :                     "Client %p waits on different order row\n",
     651              :                     po);
     652            0 :         continue;
     653              :       }
     654            0 :       if (GNUNET_TIME_timestamp_cmp (date,
     655              :                                      >,
     656              :                                      po->of.date))
     657              :       {
     658            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     659              :                     "Client %p waits on different order date\n",
     660              :                     po);
     661            0 :         continue;
     662              :       }
     663            0 :       po->of.delta++;
     664              :     }
     665            2 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     666              :                 "Waking up client %p!\n",
     667              :                 po);
     668            2 :     add_order (po,
     669              :                NULL,
     670              :                order_serial_id,
     671              :                date);
     672            2 :     GNUNET_assert (po->in_dll);
     673            2 :     GNUNET_CONTAINER_DLL_remove (mi->po_head,
     674              :                                  mi->po_tail,
     675              :                                  po);
     676            2 :     po->in_dll = false;
     677            2 :     GNUNET_assert (po ==
     678              :                    GNUNET_CONTAINER_heap_remove_node (po->hn));
     679            2 :     po->hn = NULL;
     680            2 :     MHD_resume_connection (po->con);
     681            2 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     682              :   }
     683            2 :   if (NULL == mi->po_head)
     684              :   {
     685            2 :     TMH_db->event_listen_cancel (mi->po_eh);
     686            2 :     mi->po_eh = NULL;
     687              :   }
     688              : }
     689              : 
     690              : 
     691              : /**
     692              :  * There has been a change or addition of a new @a order_id.  Wake up
     693              :  * long-polling clients that may have been waiting for this event.
     694              :  *
     695              :  * @param mi the instance where the order changed
     696              :  * @param osf order state flags
     697              :  * @param date execution date of the order
     698              :  * @param order_serial_id serial ID of the order in the database
     699              :  */
     700              : void
     701          154 : TMH_notify_order_change (struct TMH_MerchantInstance *mi,
     702              :                          enum TMH_OrderStateFlags osf,
     703              :                          struct GNUNET_TIME_Timestamp date,
     704              :                          uint64_t order_serial_id)
     705              : {
     706          308 :   struct TMH_OrderChangeEventDetailsP oce = {
     707          154 :     .order_serial_id = GNUNET_htonll (order_serial_id),
     708          154 :     .execution_date = GNUNET_TIME_timestamp_hton (date),
     709          154 :     .order_state = htonl (osf)
     710              :   };
     711          154 :   struct TMH_OrderChangeEventP eh = {
     712          154 :     .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
     713          154 :     .header.size = htons (sizeof (eh)),
     714              :     .merchant_pub = mi->merchant_pub
     715              :   };
     716              : 
     717          154 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     718              :               "Notifying clients of new order %llu at %s\n",
     719              :               (unsigned long long) order_serial_id,
     720              :               TALER_B2S (&mi->merchant_pub));
     721          154 :   TMH_db->event_notify (TMH_db->cls,
     722              :                         &eh.header,
     723              :                         &oce,
     724              :                         sizeof (oce));
     725          154 : }
     726              : 
     727              : 
     728              : /**
     729              :  * Transforms an (untrusted) input filter into a Postgresql LIKE filter.
     730              :  * Escapes "%" and "_" in the @a input and adds "%" at the beginning
     731              :  * and the end to turn the @a input into a suitable Postgresql argument.
     732              :  *
     733              :  * @param input text to turn into a substring match expression, or NULL
     734              :  * @return NULL if @a input was NULL, otherwise transformed @a input
     735              :  */
     736              : static char *
     737           14 : tr (const char *input)
     738              : {
     739              :   char *out;
     740              :   size_t slen;
     741              :   size_t wpos;
     742              : 
     743           14 :   if (NULL == input)
     744           14 :     return NULL;
     745            0 :   slen = strlen (input);
     746            0 :   out = GNUNET_malloc (slen * 2 + 3);
     747            0 :   wpos = 0;
     748            0 :   out[wpos++] = '%';
     749            0 :   for (size_t i = 0; i<slen; i++)
     750              :   {
     751            0 :     char c = input[i];
     752              : 
     753            0 :     if ( (c == '%') ||
     754              :          (c == '_') )
     755            0 :       out[wpos++] = '\\';
     756            0 :     out[wpos++] = c;
     757              :   }
     758            0 :   out[wpos++] = '%';
     759            0 :   GNUNET_assert (wpos < slen * 2 + 3);
     760            0 :   return out;
     761              : }
     762              : 
     763              : 
     764              : /**
     765              :  * Handle a GET "/orders" request.
     766              :  *
     767              :  * @param rh context of the handler
     768              :  * @param connection the MHD connection to handle
     769              :  * @param[in,out] hc context with further information about the request
     770              :  * @return MHD result code
     771              :  */
     772              : MHD_RESULT
     773           16 : TMH_private_get_orders (const struct TMH_RequestHandler *rh,
     774              :                         struct MHD_Connection *connection,
     775              :                         struct TMH_HandlerContext *hc)
     776              : {
     777           16 :   struct TMH_PendingOrder *po = hc->ctx;
     778              :   enum GNUNET_DB_QueryStatus qs;
     779              : 
     780           16 :   if (NULL != po)
     781              :   {
     782              :     /* resumed from long-polling, return answer we already have
     783              :        in 'hc->ctx' */
     784            2 :     if (TALER_EC_NONE != po->result)
     785              :     {
     786            0 :       GNUNET_break (0);
     787            0 :       return TALER_MHD_reply_with_error (connection,
     788              :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     789              :                                          po->result,
     790              :                                          NULL);
     791              :     }
     792            2 :     return TALER_MHD_REPLY_JSON_PACK (
     793              :       connection,
     794              :       MHD_HTTP_OK,
     795              :       GNUNET_JSON_pack_array_incref ("orders",
     796              :                                      po->pa));
     797              :   }
     798           14 :   po = GNUNET_new (struct TMH_PendingOrder);
     799           14 :   hc->ctx = po;
     800           14 :   hc->cc = &cleanup;
     801           14 :   po->con = connection;
     802           14 :   po->pa = json_array ();
     803           14 :   GNUNET_assert (NULL != po->pa);
     804           14 :   po->instance_id = hc->instance->settings.id;
     805           14 :   po->mi = hc->instance;
     806              : 
     807           14 :   if (! (TALER_MHD_arg_to_yna (connection,
     808              :                                "paid",
     809              :                                TALER_EXCHANGE_YNA_ALL,
     810              :                                &po->of.paid)) )
     811              :   {
     812            0 :     GNUNET_break_op (0);
     813            0 :     return TALER_MHD_reply_with_error (connection,
     814              :                                        MHD_HTTP_BAD_REQUEST,
     815              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     816              :                                        "paid");
     817              :   }
     818           14 :   if (! (TALER_MHD_arg_to_yna (connection,
     819              :                                "refunded",
     820              :                                TALER_EXCHANGE_YNA_ALL,
     821              :                                &po->of.refunded)) )
     822              :   {
     823            0 :     GNUNET_break_op (0);
     824            0 :     return TALER_MHD_reply_with_error (connection,
     825              :                                        MHD_HTTP_BAD_REQUEST,
     826              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     827              :                                        "refunded");
     828              :   }
     829           14 :   if (! (TALER_MHD_arg_to_yna (connection,
     830              :                                "wired",
     831              :                                TALER_EXCHANGE_YNA_ALL,
     832              :                                &po->of.wired)) )
     833              :   {
     834            0 :     GNUNET_break_op (0);
     835            0 :     return TALER_MHD_reply_with_error (connection,
     836              :                                        MHD_HTTP_BAD_REQUEST,
     837              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     838              :                                        "wired");
     839              :   }
     840           14 :   po->of.delta = -20;
     841              :   /* deprecated in protocol v12 */
     842           14 :   TALER_MHD_parse_request_snumber (connection,
     843              :                                    "delta",
     844              :                                    &po->of.delta);
     845              :   /* since protocol v12 */
     846           14 :   TALER_MHD_parse_request_snumber (connection,
     847              :                                    "limit",
     848              :                                    &po->of.delta);
     849           14 :   if ( (-MAX_DELTA > po->of.delta) ||
     850           14 :        (po->of.delta > MAX_DELTA) )
     851              :   {
     852            0 :     GNUNET_break_op (0);
     853            0 :     return TALER_MHD_reply_with_error (connection,
     854              :                                        MHD_HTTP_BAD_REQUEST,
     855              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     856              :                                        "limit");
     857              :   }
     858              :   {
     859              :     const char *date_s_str;
     860              : 
     861           14 :     date_s_str = MHD_lookup_connection_value (connection,
     862              :                                               MHD_GET_ARGUMENT_KIND,
     863              :                                               "date_s");
     864           14 :     if (NULL == date_s_str)
     865              :     {
     866           14 :       if (po->of.delta > 0)
     867            2 :         po->of.date = GNUNET_TIME_UNIT_ZERO_TS;
     868              :       else
     869           12 :         po->of.date = GNUNET_TIME_UNIT_FOREVER_TS;
     870              :     }
     871              :     else
     872              :     {
     873              :       char dummy;
     874              :       unsigned long long ll;
     875              : 
     876            0 :       if (1 !=
     877            0 :           sscanf (date_s_str,
     878              :                   "%llu%c",
     879              :                   &ll,
     880              :                   &dummy))
     881              :       {
     882            0 :         GNUNET_break_op (0);
     883            0 :         return TALER_MHD_reply_with_error (connection,
     884              :                                            MHD_HTTP_BAD_REQUEST,
     885              :                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
     886              :                                            "date_s");
     887              :       }
     888              : 
     889            0 :       po->of.date = GNUNET_TIME_absolute_to_timestamp (
     890              :         GNUNET_TIME_absolute_from_s (ll));
     891            0 :       if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time))
     892              :       {
     893            0 :         GNUNET_break_op (0);
     894            0 :         return TALER_MHD_reply_with_error (connection,
     895              :                                            MHD_HTTP_BAD_REQUEST,
     896              :                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
     897              :                                            "date_s");
     898              :       }
     899              :     }
     900              :   }
     901           14 :   if (po->of.delta > 0)
     902            2 :     po->of.start_row = 0;
     903              :   else
     904           12 :     po->of.start_row = INT64_MAX;
     905              :   /* deprecated in protocol v12 */
     906           14 :   TALER_MHD_parse_request_number (connection,
     907              :                                   "start",
     908              :                                   &po->of.start_row);
     909              :   /* since protocol v12 */
     910           14 :   TALER_MHD_parse_request_number (connection,
     911              :                                   "offset",
     912              :                                   &po->of.start_row);
     913           14 :   if (INT64_MAX < po->of.start_row)
     914              :   {
     915            0 :     GNUNET_break_op (0);
     916            0 :     return TALER_MHD_reply_with_error (connection,
     917              :                                        MHD_HTTP_BAD_REQUEST,
     918              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     919              :                                        "offset");
     920              :   }
     921           14 :   po->summary_filter = tr (MHD_lookup_connection_value (connection,
     922              :                                                         MHD_GET_ARGUMENT_KIND,
     923              :                                                         "summary_filter"));
     924           14 :   po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */
     925              :   po->of.session_id
     926           14 :     = MHD_lookup_connection_value (connection,
     927              :                                    MHD_GET_ARGUMENT_KIND,
     928              :                                    "session_id");
     929              :   po->of.fulfillment_url
     930           14 :     = MHD_lookup_connection_value (connection,
     931              :                                    MHD_GET_ARGUMENT_KIND,
     932              :                                    "fulfillment_url");
     933           14 :   TALER_MHD_parse_request_timeout (connection,
     934              :                                    &po->long_poll_timeout);
     935           14 :   if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout))
     936              :   {
     937            0 :     GNUNET_break_op (0);
     938            0 :     return TALER_MHD_reply_with_error (connection,
     939              :                                        MHD_HTTP_BAD_REQUEST,
     940              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     941              :                                        "timeout_ms");
     942              :   }
     943           14 :   po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout);
     944           26 :   if ( (0 >= po->of.delta) &&
     945           12 :        (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
     946              :   {
     947            0 :     GNUNET_break_op (0);
     948            0 :     po->of.timeout = GNUNET_TIME_UNIT_ZERO;
     949            0 :     po->long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
     950              :   }
     951              : 
     952           14 :   qs = TMH_db->lookup_orders (TMH_db->cls,
     953              :                               po->instance_id,
     954           14 :                               &po->of,
     955              :                               &add_order,
     956              :                               po);
     957           14 :   if (0 > qs)
     958              :   {
     959            0 :     GNUNET_break (0);
     960            0 :     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     961              :   }
     962           14 :   if (TALER_EC_NONE != po->result)
     963              :   {
     964            0 :     GNUNET_break (0);
     965            0 :     return TALER_MHD_reply_with_error (connection,
     966              :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     967              :                                        po->result,
     968              :                                        NULL);
     969              :   }
     970           20 :   if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
     971            6 :        (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
     972              :   {
     973            2 :     struct TMH_MerchantInstance *mi = hc->instance;
     974              : 
     975              :     /* setup timeout heap (if not yet exists) */
     976            2 :     if (NULL == order_timeout_heap)
     977              :       order_timeout_heap
     978            2 :         = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
     979            2 :     po->hn = GNUNET_CONTAINER_heap_insert (order_timeout_heap,
     980              :                                            po,
     981              :                                            po->long_poll_timeout.abs_value_us);
     982            2 :     GNUNET_CONTAINER_DLL_insert (mi->po_head,
     983              :                                  mi->po_tail,
     984              :                                  po);
     985            2 :     po->in_dll = true;
     986            2 :     if (NULL == mi->po_eh)
     987              :     {
     988            2 :       struct TMH_OrderChangeEventP change_eh = {
     989            2 :         .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
     990            2 :         .header.size = htons (sizeof (change_eh)),
     991              :         .merchant_pub = mi->merchant_pub
     992              :       };
     993              : 
     994            2 :       mi->po_eh = TMH_db->event_listen (TMH_db->cls,
     995              :                                         &change_eh.header,
     996            2 :                                         GNUNET_TIME_UNIT_FOREVER_REL,
     997              :                                         &resume_by_event,
     998              :                                         mi);
     999              :     }
    1000            2 :     MHD_suspend_connection (connection);
    1001              :     {
    1002              :       struct TMH_PendingOrder *pot;
    1003              : 
    1004              :       /* start timeout task */
    1005            2 :       pot = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
    1006            2 :       if (NULL != order_timeout_task)
    1007            0 :         GNUNET_SCHEDULER_cancel (order_timeout_task);
    1008            2 :       order_timeout_task = GNUNET_SCHEDULER_add_at (pot->long_poll_timeout,
    1009              :                                                     &order_timeout,
    1010              :                                                     NULL);
    1011              :     }
    1012            2 :     return MHD_YES;
    1013              :   }
    1014           12 :   return TALER_MHD_REPLY_JSON_PACK (
    1015              :     connection,
    1016              :     MHD_HTTP_OK,
    1017              :     GNUNET_JSON_pack_array_incref ("orders",
    1018              :                                    po->pa));
    1019              : }
    1020              : 
    1021              : 
    1022              : /* end of taler-merchant-httpd_private-get-orders.c */
        

Generated by: LCOV version 2.0-1