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