Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2016-2023 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or
6 : modify it under the terms of the GNU General Public License
7 : as published by the Free Software Foundation; either version 3,
8 : or (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful,
11 : but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not,
17 : see <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file bank-lib/fakebank_twg_history.c
21 : * @brief routines to return account histories for the Taler Wire Gateway API
22 : * @author Christian Grothoff <christian@grothoff.org>
23 : */
24 : #include "taler/platform.h"
25 : #include <pthread.h>
26 : #include "taler/taler_fakebank_lib.h"
27 : #include "taler/taler_bank_service.h"
28 : #include "taler/taler_mhd_lib.h"
29 : #include <gnunet/gnunet_mhd_compat.h>
30 : #include "fakebank.h"
31 : #include "fakebank_twg_history.h"
32 : #include "fakebank_common_lookup.h"
33 : #include "fakebank_common_lp.h"
34 : #include "fakebank_common_parser.h"
35 :
36 : /**
37 : * Function called to clean up a history context.
38 : *
39 : * @param cls a `struct HistoryContext *`
40 : */
41 : static void
42 173 : history_cleanup (void *cls)
43 : {
44 173 : struct HistoryContext *hc = cls;
45 :
46 173 : json_decref (hc->history);
47 173 : GNUNET_free (hc);
48 173 : }
49 :
50 :
51 : MHD_RESULT
52 3 : TALER_FAKEBANK_twg_get_debit_history_ (
53 : struct TALER_FAKEBANK_Handle *h,
54 : struct MHD_Connection *connection,
55 : const char *account,
56 : void **con_cls)
57 : {
58 3 : struct ConnectionContext *cc = *con_cls;
59 : struct HistoryContext *hc;
60 : struct Transaction *pos;
61 : enum GNUNET_GenericReturnValue ret;
62 : bool in_shutdown;
63 : const char *acc_payto_uri;
64 :
65 3 : if (NULL == cc)
66 : {
67 3 : cc = GNUNET_new (struct ConnectionContext);
68 3 : cc->ctx_cleaner = &history_cleanup;
69 3 : *con_cls = cc;
70 3 : hc = GNUNET_new (struct HistoryContext);
71 3 : cc->ctx = hc;
72 :
73 3 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
74 : "Handling /history/outgoing connection %p\n",
75 : connection);
76 3 : if (GNUNET_OK !=
77 3 : (ret = TALER_FAKEBANK_common_parse_history_args (h,
78 : connection,
79 : &hc->ha)))
80 : {
81 0 : GNUNET_break_op (0);
82 0 : return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
83 : }
84 3 : GNUNET_assert (0 ==
85 : pthread_mutex_lock (&h->big_lock));
86 3 : if (UINT64_MAX == hc->ha.start_idx)
87 1 : hc->ha.start_idx = h->serial_counter;
88 3 : hc->acc = TALER_FAKEBANK_lookup_account_ (h,
89 : account,
90 : NULL);
91 3 : if (NULL == hc->acc)
92 : {
93 0 : GNUNET_assert (0 ==
94 : pthread_mutex_unlock (&h->big_lock));
95 0 : return TALER_MHD_reply_with_error (connection,
96 : MHD_HTTP_NOT_FOUND,
97 : TALER_EC_BANK_UNKNOWN_ACCOUNT,
98 : account);
99 : }
100 3 : hc->history = json_array ();
101 3 : if (NULL == hc->history)
102 : {
103 0 : GNUNET_break (0);
104 0 : GNUNET_assert (0 ==
105 : pthread_mutex_unlock (&h->big_lock));
106 0 : return MHD_NO;
107 : }
108 3 : hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout);
109 : }
110 : else
111 : {
112 0 : hc = cc->ctx;
113 0 : GNUNET_assert (0 ==
114 : pthread_mutex_lock (&h->big_lock));
115 : }
116 :
117 3 : if (! hc->ha.have_start)
118 : {
119 3 : pos = (0 > hc->ha.delta)
120 1 : ? hc->acc->out_tail
121 3 : : hc->acc->out_head;
122 : }
123 : else
124 : {
125 0 : struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit];
126 : bool overflow;
127 : uint64_t dir;
128 0 : bool skip = true;
129 :
130 0 : dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1;
131 0 : overflow = (t->row_id != hc->ha.start_idx);
132 : /* If account does not match, linear scan for
133 : first matching account. */
134 0 : while ( (! overflow) &&
135 0 : (NULL != t) &&
136 0 : (t->debit_account != hc->acc) )
137 : {
138 0 : skip = false;
139 0 : t = h->transactions[(t->row_id + dir) % h->ram_limit];
140 0 : if ( (NULL != t) &&
141 0 : (t->row_id == hc->ha.start_idx) )
142 0 : overflow = true; /* full circle, give up! */
143 : }
144 0 : if ( (NULL == t) ||
145 : overflow)
146 : {
147 : /* FIXME: these conditions are unclear to me. */
148 0 : if ( (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout)) &&
149 0 : (0 < hc->ha.delta))
150 : {
151 0 : acc_payto_uri = hc->acc->payto_uri;
152 0 : in_shutdown = h->in_shutdown;
153 0 : GNUNET_assert (0 ==
154 : pthread_mutex_unlock (&h->big_lock));
155 0 : if (overflow)
156 : {
157 0 : return TALER_MHD_reply_with_ec (
158 : connection,
159 : TALER_EC_BANK_ANCIENT_TRANSACTION_GONE,
160 : NULL);
161 : }
162 0 : goto finish;
163 : }
164 0 : if (h->in_shutdown)
165 : {
166 0 : acc_payto_uri = hc->acc->payto_uri;
167 0 : in_shutdown = h->in_shutdown;
168 0 : GNUNET_assert (0 ==
169 : pthread_mutex_unlock (&h->big_lock));
170 0 : goto finish;
171 : }
172 0 : TALER_FAKEBANK_start_lp_ (h,
173 : connection,
174 : hc->acc,
175 : GNUNET_TIME_absolute_get_remaining (
176 : hc->timeout),
177 : LP_DEBIT,
178 : NULL);
179 0 : GNUNET_assert (0 ==
180 : pthread_mutex_unlock (&h->big_lock));
181 0 : return MHD_YES;
182 : }
183 0 : if (t->debit_account != hc->acc)
184 : {
185 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
186 : "Invalid start specified, transaction %llu not with account %s!\n",
187 : (unsigned long long) hc->ha.start_idx,
188 : account);
189 0 : GNUNET_assert (0 ==
190 : pthread_mutex_unlock (&h->big_lock));
191 0 : return MHD_NO;
192 : }
193 0 : if (skip)
194 : {
195 : /* range is exclusive, skip the matching entry */
196 0 : if (0 > hc->ha.delta)
197 0 : pos = t->prev_out;
198 : else
199 0 : pos = t->next_out;
200 : }
201 : else
202 : {
203 0 : pos = t;
204 : }
205 : }
206 3 : if (NULL != pos)
207 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
208 : "Returning %lld debit transactions starting (inclusive) from %llu\n",
209 : (long long) hc->ha.delta,
210 : (unsigned long long) pos->row_id);
211 5 : while ( (0 != hc->ha.delta) &&
212 : (NULL != pos) )
213 : {
214 : json_t *trans;
215 : char *credit_payto;
216 :
217 2 : if (T_DEBIT != pos->type)
218 : {
219 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
220 : "Unexpected CREDIT transaction #%llu for account `%s'\n",
221 : (unsigned long long) pos->row_id,
222 : account);
223 0 : if (0 > hc->ha.delta)
224 0 : pos = pos->prev_in;
225 0 : if (0 < hc->ha.delta)
226 0 : pos = pos->next_in;
227 0 : continue;
228 : }
229 2 : GNUNET_asprintf (&credit_payto,
230 : "payto://x-taler-bank/localhost/%s?receiver-name=%s",
231 2 : pos->credit_account->account_name,
232 2 : pos->credit_account->receiver_name);
233 :
234 2 : trans = GNUNET_JSON_PACK (
235 : GNUNET_JSON_pack_uint64 ("row_id",
236 : pos->row_id),
237 : GNUNET_JSON_pack_timestamp ("date",
238 : pos->date),
239 : TALER_JSON_pack_amount ("amount",
240 : &pos->amount),
241 : GNUNET_JSON_pack_string ("credit_account",
242 : credit_payto),
243 : GNUNET_JSON_pack_string ("exchange_base_url",
244 : pos->subject.debit.exchange_base_url),
245 : GNUNET_JSON_pack_data_auto ("wtid",
246 : &pos->subject.debit.wtid));
247 2 : GNUNET_assert (NULL != trans);
248 2 : GNUNET_free (credit_payto);
249 2 : GNUNET_assert (0 ==
250 : json_array_append_new (hc->history,
251 : trans));
252 2 : if (hc->ha.delta > 0)
253 1 : hc->ha.delta--;
254 : else
255 1 : hc->ha.delta++;
256 2 : if (0 > hc->ha.delta)
257 1 : pos = pos->prev_out;
258 2 : if (0 < hc->ha.delta)
259 1 : pos = pos->next_out;
260 : }
261 3 : if ( (0 == json_array_size (hc->history)) &&
262 2 : (! h->in_shutdown) &&
263 1 : (GNUNET_TIME_absolute_is_future (hc->timeout)) &&
264 0 : (0 < hc->ha.delta))
265 : {
266 0 : TALER_FAKEBANK_start_lp_ (h,
267 : connection,
268 : hc->acc,
269 : GNUNET_TIME_absolute_get_remaining (hc->timeout),
270 : LP_DEBIT,
271 : NULL);
272 0 : GNUNET_assert (0 ==
273 : pthread_mutex_unlock (&h->big_lock));
274 0 : return MHD_YES;
275 : }
276 3 : in_shutdown = h->in_shutdown;
277 3 : acc_payto_uri = hc->acc->payto_uri;
278 3 : GNUNET_assert (0 ==
279 : pthread_mutex_unlock (&h->big_lock));
280 3 : finish:
281 3 : if (0 == json_array_size (hc->history))
282 : {
283 1 : GNUNET_break (in_shutdown ||
284 : (! GNUNET_TIME_absolute_is_future (hc->timeout)));
285 1 : return TALER_MHD_reply_static (connection,
286 : MHD_HTTP_NO_CONTENT,
287 : NULL,
288 : NULL,
289 : 0);
290 : }
291 : {
292 2 : json_t *jh = hc->history;
293 :
294 2 : hc->history = NULL;
295 2 : return TALER_MHD_REPLY_JSON_PACK (
296 : connection,
297 : MHD_HTTP_OK,
298 : GNUNET_JSON_pack_string (
299 : "debit_account",
300 : acc_payto_uri),
301 : GNUNET_JSON_pack_array_steal (
302 : "outgoing_transactions",
303 : jh));
304 : }
305 : }
306 :
307 :
308 : MHD_RESULT
309 170 : TALER_FAKEBANK_twg_get_credit_history_ (
310 : struct TALER_FAKEBANK_Handle *h,
311 : struct MHD_Connection *connection,
312 : const char *account,
313 : void **con_cls)
314 : {
315 170 : struct ConnectionContext *cc = *con_cls;
316 : struct HistoryContext *hc;
317 : const struct Transaction *pos;
318 : enum GNUNET_GenericReturnValue ret;
319 : bool in_shutdown;
320 : const char *acc_payto_uri;
321 :
322 170 : if (NULL == cc)
323 : {
324 170 : cc = GNUNET_new (struct ConnectionContext);
325 170 : cc->ctx_cleaner = &history_cleanup;
326 170 : *con_cls = cc;
327 170 : hc = GNUNET_new (struct HistoryContext);
328 170 : cc->ctx = hc;
329 170 : hc->history = json_array ();
330 170 : if (NULL == hc->history)
331 : {
332 0 : GNUNET_break (0);
333 0 : return MHD_NO;
334 : }
335 170 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
336 : "Handling /history/incoming connection %p\n",
337 : connection);
338 170 : if (GNUNET_OK !=
339 170 : (ret = TALER_FAKEBANK_common_parse_history_args (h,
340 : connection,
341 : &hc->ha)))
342 : {
343 0 : GNUNET_break_op (0);
344 0 : return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
345 : }
346 170 : hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout);
347 170 : GNUNET_assert (0 ==
348 : pthread_mutex_lock (&h->big_lock));
349 170 : if (UINT64_MAX == hc->ha.start_idx)
350 1 : hc->ha.start_idx = h->serial_counter;
351 170 : hc->acc = TALER_FAKEBANK_lookup_account_ (h,
352 : account,
353 : NULL);
354 170 : if (NULL == hc->acc)
355 : {
356 2 : GNUNET_assert (0 ==
357 : pthread_mutex_unlock (&h->big_lock));
358 2 : return TALER_MHD_reply_with_error (connection,
359 : MHD_HTTP_NOT_FOUND,
360 : TALER_EC_BANK_UNKNOWN_ACCOUNT,
361 : account);
362 : }
363 : }
364 : else
365 : {
366 0 : hc = cc->ctx;
367 0 : GNUNET_assert (0 ==
368 : pthread_mutex_lock (&h->big_lock));
369 : }
370 :
371 168 : if (! hc->ha.have_start)
372 : {
373 24 : pos = (0 > hc->ha.delta)
374 1 : ? hc->acc->in_tail
375 11 : : hc->acc->in_head;
376 : }
377 : else
378 : {
379 156 : struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit];
380 : bool overflow;
381 : uint64_t dir;
382 156 : bool skip = true;
383 :
384 156 : overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) );
385 156 : dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1;
386 : /* If account does not match, linear scan for
387 : first matching account. */
388 505 : while ( (! overflow) &&
389 349 : (NULL != t) &&
390 349 : (t->credit_account != hc->acc) )
391 : {
392 193 : skip = false;
393 193 : t = h->transactions[(t->row_id + dir) % h->ram_limit];
394 193 : if ( (NULL != t) &&
395 193 : (t->row_id == hc->ha.start_idx) )
396 0 : overflow = true; /* full circle, give up! */
397 : }
398 156 : if ( (NULL == t) ||
399 : overflow)
400 : {
401 0 : in_shutdown = h->in_shutdown;
402 : /* FIXME: these conditions are unclear to me. */
403 0 : if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) &&
404 0 : (0 < hc->ha.delta))
405 : {
406 0 : acc_payto_uri = hc->acc->payto_uri;
407 0 : GNUNET_assert (0 ==
408 : pthread_mutex_unlock (&h->big_lock));
409 0 : if (overflow)
410 0 : return TALER_MHD_reply_with_ec (
411 : connection,
412 : TALER_EC_BANK_ANCIENT_TRANSACTION_GONE,
413 : NULL);
414 0 : goto finish;
415 : }
416 0 : if (in_shutdown)
417 : {
418 0 : acc_payto_uri = hc->acc->payto_uri;
419 0 : GNUNET_assert (0 ==
420 : pthread_mutex_unlock (&h->big_lock));
421 0 : goto finish;
422 : }
423 0 : TALER_FAKEBANK_start_lp_ (h,
424 : connection,
425 : hc->acc,
426 : GNUNET_TIME_absolute_get_remaining (
427 : hc->timeout),
428 : LP_CREDIT,
429 : NULL);
430 0 : GNUNET_assert (0 ==
431 : pthread_mutex_unlock (&h->big_lock));
432 0 : return MHD_YES;
433 : }
434 156 : if (skip)
435 : {
436 : /* range from application is exclusive, skip the
437 : matching entry */
438 121 : if (0 > hc->ha.delta)
439 0 : pos = t->prev_in;
440 : else
441 121 : pos = t->next_in;
442 : }
443 : else
444 : {
445 35 : pos = t;
446 : }
447 : }
448 168 : if (NULL != pos)
449 107 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
450 : "Returning %lld credit transactions starting (inclusive) from %llu\n",
451 : (long long) hc->ha.delta,
452 : (unsigned long long) pos->row_id);
453 275 : while ( (0 != hc->ha.delta) &&
454 : (NULL != pos) )
455 : {
456 : json_t *trans;
457 :
458 107 : if ( (T_CREDIT != pos->type) &&
459 16 : (T_AUTH != pos->type) )
460 : {
461 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
462 : "Unexpected DEBIT transaction #%llu for account `%s'\n",
463 : (unsigned long long) pos->row_id,
464 : account);
465 0 : if (0 > hc->ha.delta)
466 0 : pos = pos->prev_in;
467 0 : if (0 < hc->ha.delta)
468 0 : pos = pos->next_in;
469 0 : continue;
470 : }
471 107 : switch (pos->type)
472 : {
473 0 : case T_DEBIT:
474 0 : GNUNET_assert (0);
475 : break;
476 0 : case T_WAD:
477 0 : trans = GNUNET_JSON_PACK (
478 : GNUNET_JSON_pack_string ("type",
479 : "WAD"),
480 : GNUNET_JSON_pack_uint64 ("row_id",
481 : pos->row_id),
482 : GNUNET_JSON_pack_timestamp ("date",
483 : pos->date),
484 : TALER_JSON_pack_amount ("amount",
485 : &pos->amount),
486 : GNUNET_JSON_pack_string ("debit_account",
487 : pos->debit_account->payto_uri),
488 : GNUNET_JSON_pack_string ("origin_exchange_url",
489 : pos->subject.wad.origin_base_url),
490 : GNUNET_JSON_pack_data_auto ("wad_id",
491 : &pos->subject.wad.wad_id));
492 0 : break;
493 91 : case T_CREDIT:
494 91 : trans = GNUNET_JSON_PACK (
495 : GNUNET_JSON_pack_string ("type",
496 : "RESERVE"),
497 : GNUNET_JSON_pack_uint64 ("row_id",
498 : pos->row_id),
499 : GNUNET_JSON_pack_timestamp ("date",
500 : pos->date),
501 : TALER_JSON_pack_amount ("amount",
502 : &pos->amount),
503 : GNUNET_JSON_pack_string ("debit_account",
504 : pos->debit_account->payto_uri),
505 : GNUNET_JSON_pack_data_auto ("reserve_pub",
506 : &pos->subject.credit.reserve_pub));
507 91 : break;
508 16 : case T_AUTH:
509 16 : trans = GNUNET_JSON_PACK (
510 : GNUNET_JSON_pack_string ("type",
511 : "KYCAUTH"),
512 : GNUNET_JSON_pack_uint64 ("row_id",
513 : pos->row_id),
514 : GNUNET_JSON_pack_timestamp ("date",
515 : pos->date),
516 : TALER_JSON_pack_amount ("amount",
517 : &pos->amount),
518 : GNUNET_JSON_pack_string ("debit_account",
519 : pos->debit_account->payto_uri),
520 : GNUNET_JSON_pack_data_auto ("account_pub",
521 : &pos->subject.auth.account_pub));
522 16 : break;
523 : }
524 107 : GNUNET_assert (NULL != trans);
525 107 : GNUNET_assert (0 ==
526 : json_array_append_new (hc->history,
527 : trans));
528 107 : if (hc->ha.delta > 0)
529 106 : hc->ha.delta--;
530 : else
531 1 : hc->ha.delta++;
532 107 : if (0 > hc->ha.delta)
533 1 : pos = pos->prev_in;
534 107 : if (0 < hc->ha.delta)
535 1 : pos = pos->next_in;
536 : }
537 168 : if ( (0 == json_array_size (hc->history)) &&
538 122 : (! h->in_shutdown) &&
539 61 : (GNUNET_TIME_absolute_is_future (hc->timeout)) &&
540 0 : (0 < hc->ha.delta))
541 : {
542 0 : TALER_FAKEBANK_start_lp_ (h,
543 : connection,
544 : hc->acc,
545 : GNUNET_TIME_absolute_get_remaining (hc->timeout),
546 : LP_CREDIT,
547 : NULL);
548 0 : GNUNET_assert (0 ==
549 : pthread_mutex_unlock (&h->big_lock));
550 0 : return MHD_YES;
551 : }
552 168 : in_shutdown = h->in_shutdown;
553 168 : acc_payto_uri = hc->acc->payto_uri;
554 168 : GNUNET_assert (0 ==
555 : pthread_mutex_unlock (&h->big_lock));
556 168 : finish:
557 168 : if (0 == json_array_size (hc->history))
558 : {
559 61 : GNUNET_break (in_shutdown ||
560 : (! GNUNET_TIME_absolute_is_future (hc->timeout)));
561 61 : return TALER_MHD_reply_static (connection,
562 : MHD_HTTP_NO_CONTENT,
563 : NULL,
564 : NULL,
565 : 0);
566 : }
567 : {
568 107 : json_t *jh = hc->history;
569 :
570 107 : hc->history = NULL;
571 107 : return TALER_MHD_REPLY_JSON_PACK (
572 : connection,
573 : MHD_HTTP_OK,
574 : GNUNET_JSON_pack_string (
575 : "credit_account",
576 : acc_payto_uri),
577 : GNUNET_JSON_pack_array_steal (
578 : "incoming_transactions",
579 : jh));
580 : }
581 : }
|