Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018-2021 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU General Public License as
7 : published by the Free Software Foundation; either version 3, or
8 : (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : 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, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing/testing_api_cmd_bank_history_debit.c
21 : * @brief command to check the /history/outgoing API from the bank.
22 : * @author Marcello Stanisci
23 : */
24 : #include "platform.h"
25 : #include "taler_json_lib.h"
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler_exchange_service.h"
28 : #include "taler_testing_lib.h"
29 : #include "taler_fakebank_lib.h"
30 : #include "taler_bank_service.h"
31 : #include "taler_fakebank_lib.h"
32 :
33 : /**
34 : * Item in the transaction history, as reconstructed from the
35 : * command history.
36 : */
37 : struct History
38 : {
39 :
40 : /**
41 : * Wire details.
42 : */
43 : struct TALER_BANK_DebitDetails details;
44 :
45 : /**
46 : * Serial ID of the wire transfer.
47 : */
48 : uint64_t row_id;
49 :
50 : /**
51 : * URL to free.
52 : */
53 : char *c_url;
54 :
55 : /**
56 : * URL to free.
57 : */
58 : char *d_url;
59 : };
60 :
61 :
62 : /**
63 : * State for a "history" CMD.
64 : */
65 : struct HistoryState
66 : {
67 : /**
68 : * Base URL of the account offering the "history" operation.
69 : */
70 : const char *account_url;
71 :
72 : /**
73 : * Reference to command defining the
74 : * first row number we want in the result.
75 : */
76 : const char *start_row_reference;
77 :
78 : /**
79 : * How many rows we want in the result, _at most_,
80 : * and ascending/descending.
81 : */
82 : long long num_results;
83 :
84 : /**
85 : * Login data to use to authenticate.
86 : */
87 : struct TALER_BANK_AuthenticationData auth;
88 :
89 : /**
90 : * Handle to a pending "history" operation.
91 : */
92 : struct TALER_BANK_DebitHistoryHandle *hh;
93 :
94 : /**
95 : * Expected number of results (= rows).
96 : */
97 : uint64_t results_obtained;
98 :
99 : /**
100 : * Set to #GNUNET_YES if the callback detects something
101 : * unexpected.
102 : */
103 : int failed;
104 :
105 : /**
106 : * Expected history.
107 : */
108 : struct History *h;
109 :
110 : /**
111 : * Length of @e h
112 : */
113 : unsigned int total;
114 :
115 : };
116 :
117 :
118 : /**
119 : * Log which history we expected. Called when an error occurs.
120 : *
121 : * @param h what we expected.
122 : * @param h_len number of entries in @a h.
123 : * @param off position of the mismatch.
124 : */
125 : static void
126 0 : print_expected (struct History *h,
127 : unsigned int h_len,
128 : unsigned int off)
129 : {
130 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
131 : "Transaction history (debit) mismatch at position %u/%u\n",
132 : off,
133 : h_len);
134 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
135 : "Expected history:\n");
136 0 : for (unsigned int i = 0; i<h_len; i++)
137 : {
138 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
139 : "H(%u): %s (serial: %llu, subject: %s, counterpart: %s)\n",
140 : i,
141 : TALER_amount2s (&h[i].details.amount),
142 : (unsigned long long) h[i].row_id,
143 : TALER_B2S (&h[i].details.wtid),
144 : h[i].details.credit_account_uri);
145 : }
146 0 : }
147 :
148 :
149 : /**
150 : * This function constructs the list of history elements that
151 : * interest the account number of the caller. It has two main
152 : * loops: the first to figure out how many history elements have
153 : * to be allocated, and the second to actually populate every
154 : * element.
155 : *
156 : * @param is interpreter state (supposedly having the
157 : * current CMD pointing at a "history" CMD).
158 : * @param[out] rh history array to initialize.
159 : * @return number of entries in @a rh.
160 : */
161 : static unsigned int
162 4 : build_history (struct TALER_TESTING_Interpreter *is,
163 : struct History **rh)
164 : {
165 4 : struct HistoryState *hs = is->commands[is->ip].cls;
166 : unsigned int total;
167 : unsigned int pos;
168 : struct History *h;
169 : const struct TALER_TESTING_Command *add_incoming_cmd;
170 : int inc;
171 : int start;
172 : int end;
173 : /* #GNUNET_YES whenever either no 'start' value was given for the history
174 : * query, or the given value is found in the list of all the CMDs. */
175 : int ok;
176 4 : const uint64_t *row_id_start = NULL;
177 :
178 4 : if (NULL != hs->start_row_reference)
179 : {
180 0 : TALER_LOG_INFO
181 : ("`%s': start row given via reference `%s'\n",
182 : TALER_TESTING_interpreter_get_current_label (is),
183 : hs->start_row_reference);
184 0 : add_incoming_cmd = TALER_TESTING_interpreter_lookup_command (
185 : is,
186 : hs->start_row_reference);
187 0 : GNUNET_assert (NULL != add_incoming_cmd);
188 0 : GNUNET_assert (GNUNET_OK ==
189 : TALER_TESTING_get_trait_row (add_incoming_cmd,
190 : &row_id_start));
191 : }
192 :
193 4 : GNUNET_assert (0 != hs->num_results);
194 4 : if (0 == is->ip)
195 : {
196 0 : TALER_LOG_DEBUG ("Checking history at first CMD..\n");
197 0 : *rh = NULL;
198 0 : return 0;
199 : }
200 :
201 : /* AKA 'delta' */
202 4 : if (hs->num_results > 0)
203 : {
204 4 : inc = 1; /* _inc_rement: go forwards */
205 4 : start = 0;
206 4 : end = is->ip;
207 : }
208 : else
209 : {
210 0 : inc = -1; /* decrement: we go backwards */
211 0 : start = is->ip - 1;
212 0 : end = -1; /* range is exclusive, do look at 0! */
213 : }
214 :
215 4 : ok = GNUNET_NO;
216 4 : if (NULL == row_id_start)
217 4 : ok = GNUNET_YES;
218 4 : h = NULL;
219 4 : total = 0;
220 4 : GNUNET_array_grow (h,
221 : total,
222 : 4);
223 4 : pos = 0;
224 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
225 : "Checking commands %u to %u for debit history\n",
226 : start,
227 : end);
228 32 : for (int off = start; off != end; off += inc)
229 : {
230 28 : const struct TALER_TESTING_Command *cmd = &is->commands[off];
231 : const uint64_t *row_id;
232 : const char **debit_account;
233 : const char **credit_account;
234 : const struct TALER_Amount *amount;
235 : const struct TALER_WireTransferIdentifierRawP *wtid;
236 : const char **exchange_base_url;
237 :
238 28 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
239 : "Checking if command %s is relevant for debit history\n",
240 : cmd->label);
241 28 : if ( (GNUNET_OK !=
242 28 : TALER_TESTING_get_trait_bank_row (cmd,
243 8 : &row_id)) ||
244 : (GNUNET_OK !=
245 8 : TALER_TESTING_get_trait_debit_payto_uri (cmd,
246 8 : &debit_account)) ||
247 : (GNUNET_OK !=
248 8 : TALER_TESTING_get_trait_credit_payto_uri (cmd,
249 8 : &credit_account)) ||
250 : (GNUNET_OK !=
251 8 : TALER_TESTING_get_trait_amount (cmd,
252 8 : &amount)) ||
253 : (GNUNET_OK !=
254 8 : TALER_TESTING_get_trait_wtid (cmd,
255 2 : &wtid)) ||
256 : (GNUNET_OK !=
257 2 : TALER_TESTING_get_trait_exchange_url (cmd,
258 : &exchange_base_url)) )
259 26 : continue; /* not an event we care about */
260 : /* Seek "/history/outgoing" starting row. */
261 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
262 : "Command %s is relevant for debit history!\n",
263 : cmd->label);
264 2 : if ( (NULL != row_id_start) &&
265 0 : (*row_id_start == *row_id) &&
266 : (GNUNET_NO == ok) )
267 : {
268 : /* Until here, nothing counted. */
269 0 : ok = GNUNET_YES;
270 0 : continue;
271 : }
272 : /* when 'start' was _not_ given, then ok == GNUNET_YES */
273 2 : if (GNUNET_NO == ok)
274 0 : continue; /* skip until we find the marker */
275 2 : if (total >= GNUNET_MAX (hs->num_results,
276 : -hs->num_results) )
277 : {
278 0 : TALER_LOG_DEBUG ("Hit history limit\n");
279 0 : break;
280 : }
281 2 : TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
282 : *debit_account,
283 : *credit_account,
284 : hs->account_url);
285 : /* found matching record, make sure we have room */
286 2 : if (pos == total)
287 0 : GNUNET_array_grow (h,
288 : total,
289 : pos * 2);
290 2 : h[pos].c_url = GNUNET_strdup (*credit_account);
291 2 : h[pos].d_url = GNUNET_strdup (*debit_account);
292 2 : h[pos].details.credit_account_uri = h[pos].c_url;
293 2 : h[pos].details.debit_account_uri = h[pos].d_url;
294 2 : h[pos].details.amount = *amount;
295 2 : h[pos].row_id = *row_id;
296 2 : h[pos].details.wtid = *wtid;
297 2 : h[pos].details.exchange_base_url = *exchange_base_url;
298 2 : pos++;
299 : }
300 4 : GNUNET_assert (GNUNET_YES == ok);
301 4 : GNUNET_array_grow (h,
302 : total,
303 : pos);
304 4 : if (0 == pos)
305 2 : TALER_LOG_DEBUG ("Empty debit history computed\n");
306 4 : *rh = h;
307 4 : return total;
308 : }
309 :
310 :
311 : /**
312 : * Check that the "/history/outgoing" response matches the
313 : * CMD whose offset in the list of CMDs is @a off.
314 : *
315 : * @param h expected history
316 : * @param total number of entries in @a h
317 : * @param off the offset (of the CMD list) where the command
318 : * to check is.
319 : * @param details the expected transaction details.
320 : * @return #GNUNET_OK if the transaction is what we expect.
321 : */
322 : static enum GNUNET_GenericReturnValue
323 2 : check_result (struct History *h,
324 : uint64_t total,
325 : unsigned int off,
326 : const struct TALER_BANK_DebitDetails *details)
327 : {
328 2 : if (off >= total)
329 : {
330 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
331 : "Test says history has at most %u"
332 : " results, but got result #%u to check\n",
333 : (unsigned int) total,
334 : off);
335 0 : print_expected (h,
336 : total,
337 : off);
338 0 : return GNUNET_SYSERR;
339 : }
340 2 : if ( (0 != GNUNET_memcmp (&h[off].details.wtid,
341 2 : &details->wtid)) ||
342 2 : (0 != TALER_amount_cmp (&h[off].details.amount,
343 2 : &details->amount)) ||
344 2 : (0 != strcasecmp (h[off].details.credit_account_uri,
345 : details->credit_account_uri)) )
346 : {
347 0 : GNUNET_break (0);
348 0 : print_expected (h,
349 : total,
350 : off);
351 0 : return GNUNET_SYSERR;
352 : }
353 2 : return GNUNET_OK;
354 : }
355 :
356 :
357 : /**
358 : * This callback will (1) check that the HTTP response code
359 : * is acceptable and (2) that the history is consistent. The
360 : * consistency is checked by going through all the past CMDs,
361 : * reconstructing then the expected history as of those, and
362 : * finally check it against what the bank returned.
363 : *
364 : * @param cls closure.
365 : * @param http_status HTTP response code, #MHD_HTTP_OK (200)
366 : * for successful status request 0 if the bank's reply is
367 : * bogus (fails to follow the protocol),
368 : * #MHD_HTTP_NO_CONTENT if there are no more results; on
369 : * success the last callback is always of this status
370 : * (even if `abs(num_results)` were already returned).
371 : * @param ec taler status code.
372 : * @param row_id monotonically increasing counter corresponding to
373 : * the transaction.
374 : * @param details details about the wire transfer.
375 : * @param json detailed response from the HTTPD, or NULL if
376 : * reply was not in JSON.
377 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
378 : */
379 : static enum GNUNET_GenericReturnValue
380 6 : history_cb (void *cls,
381 : unsigned int http_status,
382 : enum TALER_ErrorCode ec,
383 : uint64_t row_id,
384 : const struct TALER_BANK_DebitDetails *details,
385 : const json_t *json)
386 : {
387 6 : struct TALER_TESTING_Interpreter *is = cls;
388 6 : struct HistoryState *hs = is->commands[is->ip].cls;
389 :
390 : (void) row_id;
391 6 : if (NULL == details)
392 : {
393 4 : hs->hh = NULL;
394 4 : if ( (MHD_HTTP_NOT_FOUND == http_status) &&
395 0 : (0 == hs->total) )
396 : {
397 : /* not found is OK for empty history */
398 0 : TALER_TESTING_interpreter_next (is);
399 0 : return GNUNET_OK;
400 : }
401 4 : if ( (hs->results_obtained != hs->total) ||
402 4 : (GNUNET_YES == hs->failed) ||
403 : (MHD_HTTP_NO_CONTENT != http_status) )
404 : {
405 0 : GNUNET_break (0);
406 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
407 : "Expected history of length %u, got %llu;"
408 : " HTTP status code: %u/%d, failed: %d\n",
409 : hs->total,
410 : (unsigned long long) hs->results_obtained,
411 : http_status,
412 : (int) ec,
413 : hs->failed);
414 0 : print_expected (hs->h,
415 : hs->total,
416 : UINT_MAX);
417 0 : TALER_TESTING_interpreter_fail (is);
418 0 : return GNUNET_SYSERR;
419 : }
420 4 : TALER_TESTING_interpreter_next (is);
421 4 : return GNUNET_OK;
422 : }
423 2 : if (MHD_HTTP_OK != http_status)
424 : {
425 0 : hs->hh = NULL;
426 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
427 : "Unwanted response code from /history/outgoing: %u\n",
428 : http_status);
429 0 : TALER_TESTING_interpreter_fail (is);
430 0 : return GNUNET_SYSERR;
431 : }
432 :
433 : /* check current element */
434 2 : if (GNUNET_OK !=
435 2 : check_result (hs->h,
436 2 : hs->total,
437 2 : hs->results_obtained,
438 : details))
439 : {
440 : char *acc;
441 :
442 0 : GNUNET_break (0);
443 0 : acc = json_dumps (json,
444 : JSON_COMPACT);
445 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
446 : "Result %u was `%s'\n",
447 : (unsigned int) hs->results_obtained++,
448 : acc);
449 0 : if (NULL != acc)
450 0 : free (acc);
451 0 : hs->failed = GNUNET_YES;
452 0 : hs->hh = NULL;
453 0 : TALER_TESTING_interpreter_fail (is);
454 0 : return GNUNET_SYSERR;
455 : }
456 2 : hs->results_obtained++;
457 2 : return GNUNET_OK;
458 : }
459 :
460 :
461 : /**
462 : * Run the command.
463 : *
464 : * @param cls closure.
465 : * @param cmd the command to execute.
466 : * @param is the interpreter state.
467 : */
468 : static void
469 4 : history_run (void *cls,
470 : const struct TALER_TESTING_Command *cmd,
471 : struct TALER_TESTING_Interpreter *is)
472 : {
473 4 : struct HistoryState *hs = cls;
474 4 : uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX;
475 : const uint64_t *row_ptr;
476 :
477 : (void) cmd;
478 : /* Get row_id from trait. */
479 4 : if (NULL != hs->start_row_reference)
480 : {
481 : const struct TALER_TESTING_Command *history_cmd;
482 :
483 : history_cmd
484 0 : = TALER_TESTING_interpreter_lookup_command (is,
485 : hs->start_row_reference);
486 :
487 0 : if (NULL == history_cmd)
488 0 : TALER_TESTING_FAIL (is);
489 0 : if (GNUNET_OK !=
490 0 : TALER_TESTING_get_trait_row (history_cmd,
491 : &row_ptr))
492 0 : TALER_TESTING_FAIL (is);
493 : else
494 0 : row_id = *row_ptr;
495 0 : TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
496 : (unsigned long long) row_id);
497 : }
498 4 : hs->total = build_history (is, &hs->h);
499 8 : hs->hh = TALER_BANK_debit_history (is->ctx,
500 4 : &hs->auth,
501 : row_id,
502 4 : hs->num_results,
503 4 : GNUNET_TIME_UNIT_ZERO,
504 : &history_cb,
505 : is);
506 4 : GNUNET_assert (NULL != hs->hh);
507 : }
508 :
509 :
510 : /**
511 : * Free the state from a "history" CMD, and possibly cancel
512 : * a pending operation thereof.
513 : *
514 : * @param cls closure.
515 : * @param cmd the command which is being cleaned up.
516 : */
517 : static void
518 4 : history_cleanup (void *cls,
519 : const struct TALER_TESTING_Command *cmd)
520 : {
521 4 : struct HistoryState *hs = cls;
522 :
523 : (void) cmd;
524 4 : if (NULL != hs->hh)
525 : {
526 0 : TALER_LOG_WARNING ("/history/outgoing did not complete\n");
527 0 : TALER_BANK_debit_history_cancel (hs->hh);
528 : }
529 6 : for (unsigned int off = 0; off<hs->total; off++)
530 : {
531 2 : GNUNET_free (hs->h[off].c_url);
532 2 : GNUNET_free (hs->h[off].d_url);
533 : }
534 4 : GNUNET_free (hs->h);
535 4 : GNUNET_free (hs);
536 4 : }
537 :
538 :
539 : struct TALER_TESTING_Command
540 4 : TALER_TESTING_cmd_bank_debits (const char *label,
541 : const struct TALER_BANK_AuthenticationData *auth,
542 : const char *start_row_reference,
543 : long long num_results)
544 : {
545 : struct HistoryState *hs;
546 :
547 4 : hs = GNUNET_new (struct HistoryState);
548 4 : hs->account_url = auth->wire_gateway_url;
549 4 : hs->start_row_reference = start_row_reference;
550 4 : hs->num_results = num_results;
551 4 : hs->auth = *auth;
552 :
553 : {
554 4 : struct TALER_TESTING_Command cmd = {
555 : .label = label,
556 : .cls = hs,
557 : .run = &history_run,
558 : .cleanup = &history_cleanup
559 : };
560 :
561 4 : return cmd;
562 : }
563 : }
564 :
565 :
566 : /* end of testing_api_cmd_bank_history_debit.c */
|