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

Generated by: LCOV version 1.16