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 0 : TMH_force_get_orders_resume (struct TMH_MerchantInstance *mi)
117 : {
118 : struct TMH_PendingOrder *po;
119 :
120 0 : 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 0 : if (NULL != mi->po_eh)
132 : {
133 0 : TMH_db->event_listen_cancel (mi->po_eh);
134 0 : mi->po_eh = NULL;
135 : }
136 0 : if (NULL != order_timeout_task)
137 : {
138 0 : GNUNET_SCHEDULER_cancel (order_timeout_task);
139 0 : order_timeout_task = NULL;
140 : }
141 0 : if (NULL != order_timeout_heap)
142 : {
143 0 : GNUNET_CONTAINER_heap_destroy (order_timeout_heap);
144 0 : order_timeout_heap = NULL;
145 : }
146 0 : }
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 0 : cleanup (void *ctx)
208 : {
209 0 : struct TMH_PendingOrder *po = ctx;
210 :
211 0 : 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 0 : if (NULL != po->hn)
220 0 : GNUNET_assert (po ==
221 : GNUNET_CONTAINER_heap_remove_node (po->hn));
222 0 : json_decref (po->pa);
223 0 : GNUNET_free (po);
224 0 : }
225 :
226 :
227 : /**
228 : * Function called with information about a refund.
229 : * It is responsible for summing up the refund amount.
230 : *
231 : * @param cls closure
232 : * @param refund_serial unique serial number of the refund
233 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
234 : * @param coin_pub public coin from which the refund comes from
235 : * @param exchange_url URL of the exchange that issued @a coin_pub
236 : * @param rtransaction_id identificator of the refund
237 : * @param reason human-readable explanation of the refund
238 : * @param refund_amount refund amount which is being taken from @a coin_pub
239 : * @param pending true if the this refund was not yet processed by the wallet/exchange
240 : */
241 : static void
242 0 : process_refunds_cb (void *cls,
243 : uint64_t refund_serial,
244 : struct GNUNET_TIME_Timestamp timestamp,
245 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
246 : const char *exchange_url,
247 : uint64_t rtransaction_id,
248 : const char *reason,
249 : const struct TALER_Amount *refund_amount,
250 : bool pending)
251 : {
252 0 : struct TALER_Amount *total_refund_amount = cls;
253 :
254 0 : GNUNET_assert (0 <=
255 : TALER_amount_add (total_refund_amount,
256 : total_refund_amount,
257 : refund_amount));
258 0 : }
259 :
260 :
261 : /**
262 : * Add order details to our JSON array.
263 : *
264 : * @param cls some closure
265 : * @param orig_order_id the order this is about
266 : * @param order_serial serial ID of the order
267 : * @param creation_time when was the order created
268 : */
269 : static void
270 0 : add_order (void *cls,
271 : const char *orig_order_id,
272 : uint64_t order_serial,
273 : struct GNUNET_TIME_Timestamp creation_time)
274 : {
275 0 : struct TMH_PendingOrder *po = cls;
276 0 : json_t *contract_terms = NULL;
277 : struct TALER_PrivateContractHashP h_contract_terms;
278 : enum GNUNET_DB_QueryStatus qs;
279 : const char *summary;
280 0 : char *order_id = NULL;
281 0 : bool refundable = false;
282 : bool paid;
283 : struct TALER_Amount order_amount;
284 :
285 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
286 : "Adding order `%s' (%llu) to result set\n",
287 : orig_order_id,
288 : (unsigned long long) order_serial);
289 0 : qs = TMH_db->lookup_order_status_by_serial (TMH_db->cls,
290 : po->instance_id,
291 : order_serial,
292 : &order_id,
293 : &h_contract_terms,
294 : &paid);
295 0 : if (qs < 0)
296 : {
297 0 : GNUNET_break (0);
298 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
299 0 : return;
300 : }
301 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
302 : {
303 : /* Contract terms don't exist, so the order cannot be paid. */
304 0 : paid = false;
305 0 : if (NULL == orig_order_id)
306 : {
307 : /* cannot be via DB trigger, and the other code
308 : path should have passed an orig_order_id */
309 0 : GNUNET_break (0);
310 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
311 0 : return;
312 : }
313 0 : order_id = GNUNET_strdup (orig_order_id);
314 : }
315 :
316 : {
317 : /* First try to find the order in the contracts */
318 : uint64_t os;
319 0 : bool paid = false;
320 :
321 0 : qs = TMH_db->lookup_contract_terms (TMH_db->cls,
322 : po->instance_id,
323 : order_id,
324 : &contract_terms,
325 : &os,
326 : &paid,
327 : NULL);
328 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
329 0 : GNUNET_break (os == order_serial);
330 : }
331 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
332 : {
333 : /* Might still be unclaimed, so try order table */
334 : struct TALER_MerchantPostDataHashP unused;
335 :
336 0 : qs = TMH_db->lookup_order (TMH_db->cls,
337 : po->instance_id,
338 : order_id,
339 : NULL,
340 : &unused,
341 : &contract_terms);
342 : }
343 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
344 : {
345 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
346 : "Order %llu disappeared during iteration. Skipping.\n",
347 : (unsigned long long) order_serial);
348 0 : json_decref (contract_terms); /* should still be NULL */
349 0 : GNUNET_free (order_id);
350 0 : return;
351 : }
352 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
353 : {
354 0 : GNUNET_break (0);
355 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
356 0 : json_decref (contract_terms);
357 0 : GNUNET_free (order_id);
358 0 : return;
359 : }
360 :
361 : {
362 : struct GNUNET_TIME_Timestamp rd;
363 : struct GNUNET_JSON_Specification spec[] = {
364 0 : TALER_JSON_spec_amount ("amount",
365 : TMH_currency,
366 : &order_amount),
367 0 : GNUNET_JSON_spec_timestamp ("refund_deadline",
368 : &rd),
369 0 : GNUNET_JSON_spec_string ("summary",
370 : &summary),
371 0 : GNUNET_JSON_spec_end ()
372 : };
373 :
374 0 : if (GNUNET_OK !=
375 0 : GNUNET_JSON_parse (contract_terms,
376 : spec,
377 : NULL, NULL))
378 : {
379 0 : GNUNET_break (0);
380 0 : po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
381 0 : json_decref (contract_terms);
382 0 : GNUNET_free (order_id);
383 0 : return;
384 : }
385 :
386 0 : if (GNUNET_TIME_absolute_is_future (rd.abs_time) &&
387 : paid)
388 : {
389 : struct TALER_Amount refund_amount;
390 :
391 0 : GNUNET_assert (GNUNET_OK ==
392 : TALER_amount_set_zero (TMH_currency,
393 : &refund_amount));
394 0 : qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
395 : po->instance_id,
396 : &h_contract_terms,
397 : &process_refunds_cb,
398 : &refund_amount);
399 0 : if (0 > qs)
400 : {
401 0 : GNUNET_break (0);
402 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
403 0 : json_decref (contract_terms);
404 0 : GNUNET_free (order_id);
405 0 : return;
406 : }
407 0 : if (0 > TALER_amount_cmp (&refund_amount,
408 : &order_amount))
409 0 : refundable = true;
410 : }
411 : }
412 :
413 0 : GNUNET_assert (0 ==
414 : json_array_append_new (
415 : po->pa,
416 : GNUNET_JSON_PACK (
417 : GNUNET_JSON_pack_string ("order_id",
418 : order_id),
419 : GNUNET_JSON_pack_uint64 ("row_id",
420 : order_serial),
421 : GNUNET_JSON_pack_timestamp ("timestamp",
422 : creation_time),
423 : TALER_JSON_pack_amount ("amount",
424 : &order_amount),
425 : GNUNET_JSON_pack_string ("summary",
426 : summary),
427 : GNUNET_JSON_pack_bool ("refundable",
428 : refundable),
429 : GNUNET_JSON_pack_bool ("paid",
430 : paid))));
431 0 : json_decref (contract_terms);
432 0 : GNUNET_free (order_id);
433 : }
434 :
435 :
436 : /**
437 : * We have received a trigger from the database
438 : * that we should (possibly) resume some requests.
439 : *
440 : * @param cls a `struct TMH_MerchantInstance`
441 : * @param extra a `struct TMH_OrderChangeEventP`
442 : * @param extra_size number of bytes in @a extra
443 : */
444 : static void
445 0 : resume_by_event (void *cls,
446 : const void *extra,
447 : size_t extra_size)
448 : {
449 0 : struct TMH_MerchantInstance *mi = cls;
450 0 : const struct TMH_OrderChangeEventDetailsP *oce = extra;
451 : struct TMH_PendingOrder *pn;
452 : enum TMH_OrderStateFlags osf;
453 : uint64_t order_serial_id;
454 : struct GNUNET_TIME_Timestamp date;
455 :
456 0 : if (sizeof (*oce) != extra_size)
457 : {
458 0 : GNUNET_break (0);
459 0 : return;
460 : }
461 0 : osf = (enum TMH_OrderStateFlags) ntohl (oce->order_state);
462 0 : order_serial_id = GNUNET_ntohll (oce->order_serial_id);
463 0 : date = GNUNET_TIME_timestamp_ntoh (oce->execution_date);
464 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
465 : "Received notification about new order %llu\n",
466 : (unsigned long long) order_serial_id);
467 0 : for (struct TMH_PendingOrder *po = mi->po_head;
468 : NULL != po;
469 0 : po = pn)
470 : {
471 0 : pn = po->next;
472 0 : if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) ==
473 0 : (0 != (osf & TMH_OSF_PAID))) ||
474 0 : (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) &&
475 0 : ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) ==
476 0 : (0 != (osf & TMH_OSF_REFUNDED))) ||
477 0 : (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) &&
478 0 : ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) ==
479 0 : (0 != (osf & TMH_OSF_WIRED))) ||
480 0 : (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) )
481 : {
482 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
483 : "Client %p waits on different order type\n",
484 : po);
485 0 : continue;
486 : }
487 0 : if (po->of.delta > 0)
488 : {
489 0 : if (order_serial_id < po->of.start_row)
490 : {
491 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
492 : "Client %p waits on different order row\n",
493 : po);
494 0 : continue;
495 : }
496 0 : if (GNUNET_TIME_timestamp_cmp (date,
497 : <,
498 : po->of.date))
499 : {
500 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
501 : "Client %p waits on different order date\n",
502 : po);
503 0 : continue;
504 : }
505 0 : po->of.delta--;
506 : }
507 : else
508 : {
509 0 : if (order_serial_id > po->of.start_row)
510 : {
511 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
512 : "Client %p waits on different order row\n",
513 : po);
514 0 : continue;
515 : }
516 0 : if (GNUNET_TIME_timestamp_cmp (date,
517 : >,
518 : po->of.date))
519 : {
520 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
521 : "Client %p waits on different order date\n",
522 : po);
523 0 : continue;
524 : }
525 0 : po->of.delta++;
526 : }
527 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
528 : "Waking up client %p!\n",
529 : po);
530 0 : add_order (po,
531 : NULL,
532 : order_serial_id,
533 : date);
534 0 : GNUNET_assert (po->in_dll);
535 0 : GNUNET_CONTAINER_DLL_remove (mi->po_head,
536 : mi->po_tail,
537 : po);
538 0 : po->in_dll = false;
539 0 : GNUNET_assert (po ==
540 : GNUNET_CONTAINER_heap_remove_node (po->hn));
541 0 : po->hn = NULL;
542 0 : MHD_resume_connection (po->con);
543 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
544 : }
545 0 : if (NULL == mi->po_head)
546 : {
547 0 : TMH_db->event_listen_cancel (mi->po_eh);
548 0 : mi->po_eh = NULL;
549 : }
550 : }
551 :
552 :
553 : /**
554 : * There has been a change or addition of a new @a order_id. Wake up
555 : * long-polling clients that may have been waiting for this event.
556 : *
557 : * @param mi the instance where the order changed
558 : * @param osf order state flags
559 : * @param date execution date of the order
560 : * @param order_serial_id serial ID of the order in the database
561 : */
562 : void
563 0 : TMH_notify_order_change (struct TMH_MerchantInstance *mi,
564 : enum TMH_OrderStateFlags osf,
565 : struct GNUNET_TIME_Timestamp date,
566 : uint64_t order_serial_id)
567 : {
568 0 : struct TMH_OrderChangeEventDetailsP oce = {
569 0 : .order_serial_id = GNUNET_htonll (order_serial_id),
570 0 : .execution_date = GNUNET_TIME_timestamp_hton (date),
571 0 : .order_state = htonl (osf)
572 : };
573 0 : struct TMH_OrderChangeEventP eh = {
574 0 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
575 0 : .header.size = htons (sizeof (eh)),
576 : .merchant_pub = mi->merchant_pub
577 : };
578 :
579 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
580 : "Notifying clients of new order %llu at %s\n",
581 : (unsigned long long) order_serial_id,
582 : TALER_B2S (&mi->merchant_pub));
583 0 : TMH_db->event_notify (TMH_db->cls,
584 : &eh.header,
585 : &oce,
586 : sizeof (oce));
587 0 : }
588 :
589 :
590 : /**
591 : * Handle a GET "/orders" request.
592 : *
593 : * @param rh context of the handler
594 : * @param connection the MHD connection to handle
595 : * @param[in,out] hc context with further information about the request
596 : * @return MHD result code
597 : */
598 : MHD_RESULT
599 0 : TMH_private_get_orders (const struct TMH_RequestHandler *rh,
600 : struct MHD_Connection *connection,
601 : struct TMH_HandlerContext *hc)
602 : {
603 0 : struct TMH_PendingOrder *po = hc->ctx;
604 : enum GNUNET_DB_QueryStatus qs;
605 : struct TALER_MERCHANTDB_OrderFilter of;
606 :
607 0 : if (NULL != po)
608 : {
609 : /* resumed from long-polling, return answer we already have
610 : in 'hc->ctx' */
611 0 : if (TALER_EC_NONE != po->result)
612 : {
613 0 : GNUNET_break (0);
614 0 : return TALER_MHD_reply_with_error (connection,
615 : MHD_HTTP_INTERNAL_SERVER_ERROR,
616 : po->result,
617 : NULL);
618 : }
619 0 : return TALER_MHD_REPLY_JSON_PACK (
620 : connection,
621 : MHD_HTTP_OK,
622 : GNUNET_JSON_pack_array_incref ("orders",
623 : po->pa));
624 : }
625 :
626 0 : if (! (TALER_arg_to_yna (connection,
627 : "paid",
628 : TALER_EXCHANGE_YNA_ALL,
629 : &of.paid)) )
630 0 : return TALER_MHD_reply_with_error (connection,
631 : MHD_HTTP_BAD_REQUEST,
632 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
633 : "paid");
634 0 : if (! (TALER_arg_to_yna (connection,
635 : "refunded",
636 : TALER_EXCHANGE_YNA_ALL,
637 : &of.refunded)) )
638 0 : return TALER_MHD_reply_with_error (connection,
639 : MHD_HTTP_BAD_REQUEST,
640 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
641 : "refunded");
642 0 : if (! (TALER_arg_to_yna (connection,
643 : "wired",
644 : TALER_EXCHANGE_YNA_ALL,
645 : &of.wired)) )
646 0 : return TALER_MHD_reply_with_error (connection,
647 : MHD_HTTP_BAD_REQUEST,
648 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
649 : "wired");
650 : {
651 : const char *delta_str;
652 :
653 0 : delta_str = MHD_lookup_connection_value (connection,
654 : MHD_GET_ARGUMENT_KIND,
655 : "delta");
656 0 : if (NULL == delta_str)
657 : {
658 0 : of.delta = -20;
659 : }
660 : else
661 : {
662 : char dummy;
663 : long long ll;
664 :
665 0 : if (1 !=
666 0 : sscanf (delta_str,
667 : "%lld%c",
668 : &ll,
669 : &dummy))
670 : {
671 0 : GNUNET_break_op (0);
672 0 : return TALER_MHD_reply_with_error (connection,
673 : MHD_HTTP_BAD_REQUEST,
674 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
675 : "delta");
676 : }
677 0 : of.delta = (int64_t) ll;
678 0 : if ( (-MAX_DELTA > of.delta) ||
679 0 : (of.delta > MAX_DELTA) )
680 : {
681 0 : GNUNET_break_op (0);
682 0 : return TALER_MHD_reply_with_error (connection,
683 : MHD_HTTP_BAD_REQUEST,
684 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
685 : "delta");
686 : }
687 : }
688 : }
689 : {
690 : const char *date_s_str;
691 :
692 0 : date_s_str = MHD_lookup_connection_value (connection,
693 : MHD_GET_ARGUMENT_KIND,
694 : "date_s");
695 0 : if (NULL == date_s_str)
696 : {
697 0 : if (of.delta > 0)
698 0 : of.date = GNUNET_TIME_UNIT_ZERO_TS;
699 : else
700 0 : of.date = GNUNET_TIME_UNIT_FOREVER_TS;
701 : }
702 : else
703 : {
704 : char dummy;
705 : unsigned long long ll;
706 :
707 0 : if (1 !=
708 0 : sscanf (date_s_str,
709 : "%llu%c",
710 : &ll,
711 : &dummy))
712 : {
713 0 : GNUNET_break_op (0);
714 0 : return TALER_MHD_reply_with_error (connection,
715 : MHD_HTTP_BAD_REQUEST,
716 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
717 : "date_ms");
718 : }
719 :
720 0 : of.date = GNUNET_TIME_absolute_to_timestamp (
721 : GNUNET_TIME_absolute_from_s (ll));
722 0 : if (GNUNET_TIME_absolute_is_never (of.date.abs_time))
723 : {
724 0 : GNUNET_break_op (0);
725 0 : return TALER_MHD_reply_with_error (connection,
726 : MHD_HTTP_BAD_REQUEST,
727 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
728 : "date_s");
729 : }
730 : }
731 : }
732 : {
733 : const char *start_row_str;
734 :
735 0 : start_row_str = MHD_lookup_connection_value (connection,
736 : MHD_GET_ARGUMENT_KIND,
737 : "start");
738 0 : if (NULL == start_row_str)
739 : {
740 0 : if (of.delta > 0)
741 0 : of.start_row = 0;
742 : else
743 0 : of.start_row = INT64_MAX;
744 : }
745 : else
746 : {
747 : char dummy;
748 : unsigned long long ull;
749 :
750 0 : if (1 !=
751 0 : sscanf (start_row_str,
752 : "%llu%c",
753 : &ull,
754 : &dummy))
755 0 : return TALER_MHD_reply_with_error (connection,
756 : MHD_HTTP_BAD_REQUEST,
757 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
758 : "start");
759 0 : of.start_row = (uint64_t) ull;
760 0 : if (INT64_MAX < of.start_row)
761 : {
762 0 : GNUNET_break_op (0);
763 0 : return TALER_MHD_reply_with_error (connection,
764 : MHD_HTTP_BAD_REQUEST,
765 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
766 : "start");
767 : }
768 : }
769 : }
770 : {
771 : const char *timeout_ms_str;
772 :
773 0 : timeout_ms_str = MHD_lookup_connection_value (connection,
774 : MHD_GET_ARGUMENT_KIND,
775 : "timeout_ms");
776 0 : if (NULL == timeout_ms_str)
777 : {
778 0 : of.timeout = GNUNET_TIME_UNIT_ZERO;
779 : }
780 : else
781 : {
782 : char dummy;
783 : unsigned long long ull;
784 :
785 0 : if (1 !=
786 0 : sscanf (timeout_ms_str,
787 : "%lld%c",
788 : &ull,
789 : &dummy))
790 0 : return TALER_MHD_reply_with_error (connection,
791 : MHD_HTTP_BAD_REQUEST,
792 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
793 : "timeout_ms");
794 0 : of.timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
795 : ull);
796 0 : if (GNUNET_TIME_relative_is_forever (of.timeout))
797 : {
798 0 : GNUNET_break_op (0);
799 0 : return TALER_MHD_reply_with_error (connection,
800 : MHD_HTTP_BAD_REQUEST,
801 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
802 : "timeout_ms");
803 : }
804 : }
805 :
806 0 : if ( (0 >= of.delta) &&
807 0 : (! GNUNET_TIME_relative_is_zero (of.timeout)) )
808 : {
809 0 : GNUNET_break_op (0);
810 0 : of.timeout = GNUNET_TIME_UNIT_ZERO;
811 : }
812 : }
813 :
814 0 : po = GNUNET_new (struct TMH_PendingOrder);
815 0 : hc->ctx = po;
816 0 : hc->cc = &cleanup;
817 0 : po->con = connection;
818 0 : po->pa = json_array ();
819 0 : GNUNET_assert (NULL != po->pa);
820 0 : po->instance_id = hc->instance->settings.id;
821 0 : po->mi = hc->instance;
822 0 : qs = TMH_db->lookup_orders (TMH_db->cls,
823 : po->instance_id,
824 : &of,
825 : &add_order,
826 : po);
827 0 : if (0 > qs)
828 : {
829 0 : GNUNET_break (0);
830 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
831 : }
832 0 : if (TALER_EC_NONE != po->result)
833 : {
834 0 : GNUNET_break (0);
835 0 : return TALER_MHD_reply_with_error (connection,
836 : MHD_HTTP_INTERNAL_SERVER_ERROR,
837 : po->result,
838 : NULL);
839 : }
840 0 : if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
841 0 : (! GNUNET_TIME_relative_is_zero (of.timeout)) )
842 : {
843 0 : struct TMH_MerchantInstance *mi = hc->instance;
844 :
845 : /* setup timeout heap (if not yet exists) */
846 0 : if (NULL == order_timeout_heap)
847 : order_timeout_heap
848 0 : = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
849 0 : po->hn = GNUNET_CONTAINER_heap_insert (order_timeout_heap,
850 : po,
851 : po->long_poll_timeout.abs_value_us);
852 0 : po->long_poll_timeout = GNUNET_TIME_relative_to_absolute (of.timeout);
853 0 : po->of = of;
854 0 : GNUNET_CONTAINER_DLL_insert (mi->po_head,
855 : mi->po_tail,
856 : po);
857 0 : po->in_dll = true;
858 0 : if (NULL == mi->po_eh)
859 : {
860 0 : struct TMH_OrderChangeEventP change_eh = {
861 0 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
862 0 : .header.size = htons (sizeof (change_eh)),
863 : .merchant_pub = mi->merchant_pub
864 : };
865 :
866 0 : mi->po_eh = TMH_db->event_listen (TMH_db->cls,
867 : &change_eh.header,
868 0 : GNUNET_TIME_UNIT_FOREVER_REL,
869 : &resume_by_event,
870 : mi);
871 : }
872 0 : MHD_suspend_connection (connection);
873 : {
874 : struct TMH_PendingOrder *pot;
875 :
876 : /* start timeout task */
877 0 : pot = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
878 0 : if (NULL != order_timeout_task)
879 0 : GNUNET_SCHEDULER_cancel (order_timeout_task);
880 0 : order_timeout_task = GNUNET_SCHEDULER_add_at (pot->long_poll_timeout,
881 : &order_timeout,
882 : NULL);
883 : }
884 0 : return MHD_YES;
885 : }
886 0 : return TALER_MHD_REPLY_JSON_PACK (
887 : connection,
888 : MHD_HTTP_OK,
889 : GNUNET_JSON_pack_array_incref ("orders",
890 : po->pa));
891 : }
892 :
893 :
894 : /* end of taler-merchant-httpd_private-get-orders.c */
|