LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_private-get-orders.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 164 303 54.1 %
Date: 2025-08-28 06:06:54 Functions: 6 8 75.0 %

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

Generated by: LCOV version 1.16