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