From 9443c10d7feb0d91323869dd08ec61ca781564f4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 11 Jan 2020 15:19:56 +0100 Subject: major refactoring, eliminating wire-plugins and moving towards new bank API. main code compiles, testcases known to fail, code sure not to fully work yet --- src/bank-lib/Makefile.am | 13 +- src/bank-lib/bank_api_admin.c | 71 +- src/bank-lib/bank_api_common.c | 86 +-- src/bank-lib/bank_api_common.h | 12 + src/bank-lib/bank_api_credit.c | 315 ++++++++ src/bank-lib/bank_api_debit.c | 317 ++++++++ src/bank-lib/bank_api_history.c | 445 ----------- src/bank-lib/bank_api_parse.c | 54 +- src/bank-lib/bank_api_reject.c | 242 ------ src/bank-lib/bank_api_transaction.c | 368 +++++++++ src/bank-lib/fakebank.c | 728 +++++++++++------- src/bank-lib/fakebank.h | 283 +------ src/bank-lib/fakebank_history.c | 204 +---- src/bank-lib/taler-bank-transfer.c | 55 +- src/bank-lib/test_bank_api.c | 2 +- src/bank-lib/testing_api_cmd_history.c | 1004 ------------------------- src/bank-lib/testing_api_cmd_history_credit.c | 758 +++++++++++++++++++ src/bank-lib/testing_api_cmd_history_debit.c | 758 +++++++++++++++++++ src/bank-lib/testing_api_cmd_reject.c | 223 ------ 19 files changed, 3160 insertions(+), 2778 deletions(-) create mode 100644 src/bank-lib/bank_api_credit.c create mode 100644 src/bank-lib/bank_api_debit.c delete mode 100644 src/bank-lib/bank_api_history.c delete mode 100644 src/bank-lib/bank_api_reject.c create mode 100644 src/bank-lib/bank_api_transaction.c delete mode 100644 src/bank-lib/testing_api_cmd_history.c create mode 100644 src/bank-lib/testing_api_cmd_history_credit.c create mode 100644 src/bank-lib/testing_api_cmd_history_debit.c delete mode 100644 src/bank-lib/testing_api_cmd_reject.c (limited to 'src/bank-lib') diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am index 3bb863232..371f5e6d6 100644 --- a/src/bank-lib/Makefile.am +++ b/src/bank-lib/Makefile.am @@ -39,8 +39,9 @@ libtalerbank_la_LDFLAGS = \ libtalerbank_la_SOURCES = \ bank_api_admin.c \ bank_api_common.c bank_api_common.h \ - bank_api_history.c \ - bank_api_reject.c \ + bank_api_credit.c \ + bank_api_debit.c \ + bank_api_transaction.c \ bank_api_parse.c libtalerbank_la_LIBADD = \ $(top_builddir)/src/json/libtalerjson.la \ @@ -55,10 +56,10 @@ libtalerfakebank_la_LDFLAGS = \ -version-info 0:0:0 \ -no-undefined libtalerfakebank_la_SOURCES = \ - fakebank_history.c \ - fakebank.c fakebank.h + fakebank.c libtalerfakebank_la_LIBADD = \ $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/mhd/libtalermhd.la \ -lgnunetjson \ -lgnunetutil \ -ljansson \ @@ -69,8 +70,8 @@ libtalerbanktesting_la_LDFLAGS = \ -version-info 0:0:0 \ -no-undefined libtalerbanktesting_la_SOURCES = \ - testing_api_cmd_history.c \ - testing_api_cmd_reject.c \ + testing_api_cmd_history_credit.c \ + testing_api_cmd_history_debit.c \ testing_api_helpers.c libtalerbanktesting_la_LIBADD = \ $(top_builddir)/src/json/libtalerjson.la \ diff --git a/src/bank-lib/bank_api_admin.c b/src/bank-lib/bank_api_admin.c index 5240a3724..068fd0e0e 100644 --- a/src/bank-lib/bank_api_admin.c +++ b/src/bank-lib/bank_api_admin.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015, 2016, 2017 Taler Systems SA + Copyright (C) 2015--2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -166,13 +166,11 @@ handle_admin_add_incoming_finished (void *cls, * to the operators of the bank. * * @param ctx curl context for the event loop - * @param bank_base_url URL of the bank (used to execute this request) + * @param account_base_url URL of the bank (used to execute this request) * @param auth authentication data to send to the bank - * @param exchange_base_url base URL of the exchange (for tracking) - * @param subject wire transfer subject for the transfer + * @param reserve_pub wire transfer subject for the transfer * @param amount amount that was deposited - * @param debit_account_no account number to withdraw from (53 bits at most) - * @param credit_account_no account number to deposit into (53 bits at most) + * @param credit_account account to deposit into (payto) * @param res_cb the callback to call when the final result for this request is available * @param res_cb_cls closure for the above callback * @return NULL @@ -181,13 +179,12 @@ handle_admin_add_incoming_finished (void *cls, */ struct TALER_BANK_AdminAddIncomingHandle * TALER_BANK_admin_add_incoming (struct GNUNET_CURL_Context *ctx, - const char *bank_base_url, + const char *account_base_url, const struct TALER_BANK_AuthenticationData *auth, - const char *exchange_base_url, - const char *subject, + const struct + TALER_ReservePublicKeyP *reserve_pub, const struct TALER_Amount *amount, - uint64_t debit_account_no, - uint64_t credit_account_no, + const char *credit_account, TALER_BANK_AdminAddIncomingResultCallback res_cb, void *res_cb_cls) { @@ -195,18 +192,10 @@ TALER_BANK_admin_add_incoming (struct GNUNET_CURL_Context *ctx, json_t *admin_obj; CURL *eh; - if (NULL == exchange_base_url) - { - GNUNET_break (0); - return NULL; - } - admin_obj = json_pack ("{s:{s:s}, s:s, s:s, s:o, s:I, s:I}", - "auth", "type", "basic", - "exchange_url", exchange_base_url, - "subject", subject, + admin_obj = json_pack ("{s:o, s:o, s:s}", + "subject", GNUNET_JSON_from_data_auto (reserve_pub), "amount", TALER_JSON_from_amount (amount), - "debit_account", (json_int_t) debit_account_no, - "credit_account", (json_int_t) credit_account_no); + "credit_account", credit_account); if (NULL == admin_obj) { GNUNET_break (0); @@ -215,27 +204,33 @@ TALER_BANK_admin_add_incoming (struct GNUNET_CURL_Context *ctx, aai = GNUNET_new (struct TALER_BANK_AdminAddIncomingHandle); aai->cb = res_cb; aai->cb_cls = res_cb_cls; - aai->request_url = TALER_BANK_path_to_url_ (bank_base_url, + aai->request_url = TALER_BANK_path_to_url_ (account_base_url, "/admin/add/incoming"); - aai->post_ctx.headers = TALER_BANK_make_auth_header_ (auth); - - GNUNET_assert - (NULL != (aai->post_ctx.headers = curl_slist_append - (aai->post_ctx.headers, - "Content-Type: application/json"))); + aai->post_ctx.headers = curl_slist_append + (aai->post_ctx.headers, + "Content-Type: application/json"); eh = curl_easy_init (); - - GNUNET_assert (GNUNET_OK == - TALER_curl_easy_post (&aai->post_ctx, eh, admin_obj)); - + if ( (GNUNET_OK != + TALER_BANK_setup_auth_ (eh, + auth)) || + (CURLE_OK != + curl_easy_setopt (eh, + CURLOPT_URL, + aai->request_url)) || + (GNUNET_OK != + TALER_curl_easy_post (&aai->post_ctx, + eh, + admin_obj)) ) + { + GNUNET_break (0); + TALER_BANK_admin_add_incoming_cancel (aai); + curl_easy_cleanup (eh); + json_decref (admin_obj); + return NULL; + } json_decref (admin_obj); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_URL, - aai->request_url)); - aai->job = GNUNET_CURL_job_add2 (ctx, eh, aai->post_ctx.headers, diff --git a/src/bank-lib/bank_api_common.c b/src/bank-lib/bank_api_common.c index 50769dae2..8a8f4289f 100644 --- a/src/bank-lib/bank_api_common.c +++ b/src/bank-lib/bank_api_common.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015, 2016, 2017 GNUnet e.V. + Copyright (C) 2015-2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -24,67 +24,47 @@ /** - * Append HTTP key-value pair to curl header list. + * Set authentication data in @a easy from @a auth. + * The API currently specifies the use of HTTP basic + * authentication. * - * @param hdr list to append to, can be NULL - * @param key key to append - * @param value value to append - * @return new list, NULL on error + * @param easy curl handle to setup for authentication + * @param auth authentication data to use + * @return #GNUNET_OK in success */ -static struct curl_slist * -append (struct curl_slist *hdr, - const char *key, - const char *value) +int +TALER_BANK_setup_auth_ (CURL *easy, + const struct TALER_BANK_AuthenticationData *auth) { - char *str; - struct curl_slist *ret; - - GNUNET_asprintf (&str, - "%s: %s", - key, - value); - ret = curl_slist_append (hdr, - str); - GNUNET_free (str); - if (NULL == ret) - { - GNUNET_break (0); - curl_slist_free_all (hdr); - return NULL; - } - return ret; -} - - -/** - * Build authentication header from @a auth. - * - * @param auth authentication data to use. - * - * @return NULL on error, otherwise curl headers to use. - */ -struct curl_slist * -TALER_BANK_make_auth_header_ - (const struct TALER_BANK_AuthenticationData *auth) -{ - struct curl_slist *authh; + int ret; + ret = GNUNET_OK; switch (auth->method) { case TALER_BANK_AUTH_NONE: - return NULL; + return GNUNET_OK; case TALER_BANK_AUTH_BASIC: - authh = append (NULL, - "X-Taler-Bank-Username", - auth->details.basic.username); - if (NULL == authh) - return NULL; - authh = append (authh, - "X-Taler-Bank-Password", - auth->details.basic.password); - return authh; + { + char *up; + + GNUNET_asprintf (&up, + "%s:%s", + auth->details.basic.username, + auth->details.basic.password); + if ( (CURLE_OK != + curl_easy_setopt (easy, + CURLOPT_HTTPAUTH, + CURLAUTH_BASIC)) || + (CURLE_OK != + curl_easy_setopt (easy, + CURLOPT_USERPWD, + up)) ) + ret = GNUNET_SYSERR; + GNUNET_free (up); + break; + } } - return NULL; + return ret; } diff --git a/src/bank-lib/bank_api_common.h b/src/bank-lib/bank_api_common.h index fcf2029d4..1120ed94e 100644 --- a/src/bank-lib/bank_api_common.h +++ b/src/bank-lib/bank_api_common.h @@ -39,6 +39,18 @@ struct curl_slist * TALER_BANK_make_auth_header_ (const struct TALER_BANK_AuthenticationData *auth); +/** + * Set authentication data in @a easy from @a auth. + * + * @param easy curl handle to setup for authentication + * @param auth authentication data to use + * @return #GNUNET_OK in success + */ +int +TALER_BANK_setup_auth_ (CURL *easy, + const struct TALER_BANK_AuthenticationData *auth); + + /** * Obtain the URL to use for an API request. * diff --git a/src/bank-lib/bank_api_credit.c b/src/bank-lib/bank_api_credit.c new file mode 100644 index 000000000..ed0a1e2a7 --- /dev/null +++ b/src/bank-lib/bank_api_credit.c @@ -0,0 +1,315 @@ +/* + This file is part of TALER + Copyright (C) 2017--2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/bank_api_history.c + * @brief Implementation of the /history[-range] + * requests of the bank's HTTP API. + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "bank_api_common.h" +#include /* just for HTTP status codes */ +#include "taler_signatures.h" + + +/** + * @brief A /history Handle + */ +struct TALER_BANK_CreditHistoryHandle +{ + + /** + * The url for this request. + */ + char *request_url; + + /** + * The base URL of the bank. + */ + char *bank_base_url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_BANK_CreditResultCallback hcb; + + /** + * Closure for @a cb. + */ + void *hcb_cls; +}; + + +/** + * Parse history given in JSON format and invoke the callback on each item. + * + * @param hh handle to the account history request + * @param history JSON array with the history + * @return #GNUNET_OK if history was valid and @a rhistory and @a balance + * were set, + * #GNUNET_SYSERR if there was a protocol violation in @a history + */ +static int +parse_account_history (struct TALER_BANK_CreditHistoryHandle *hh, + const json_t *history) +{ + json_t *history_array; + + if (NULL == (history_array = json_object_get (history, + "data"))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (! json_is_array (history_array)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + for (unsigned int i = 0; ihcb (hh->hcb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + row_id, + &td, + transaction); + GNUNET_JSON_parse_free (hist_spec); + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /history request. + * + * @param cls the `struct TALER_BANK_CreditHistoryHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_history_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_BANK_CreditHistoryHandle *hh = cls; + enum TALER_ErrorCode ec; + const json_t *j = response; + + hh->job = NULL; + switch (response_code) + { + case 0: + ec = TALER_EC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + parse_account_history (hh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_INVALID_RESPONSE; + break; + } + response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */ + ec = TALER_EC_NONE; + break; + case MHD_HTTP_NO_CONTENT: + ec = TALER_EC_NONE; + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the bank is buggy + (or API version conflict); just pass JSON reply to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_FORBIDDEN: + /* Access denied */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, bank says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + ec = TALER_BANK_parse_ec_ (j); + response_code = 0; + break; + } + hh->hcb (hh->hcb_cls, + response_code, + ec, + 0LLU, + NULL, + j); + TALER_BANK_credit_history_cancel (hh); +} + + +/** + * Request the credit history of the exchange's bank account. + * + * @param ctx curl context for the event loop + * @param bank_base_url URL of the base INCLUDING account number + * @param auth authentication data to use + * @param start_row from which row on do we want to get results, + * use UINT64_MAX for the latest; exclusive + * @param num_results how many results do we want; + * negative numbers to go into the past, positive numbers + * to go into the future starting at @a start_row; + * must not be zero. + * @param hres_cb the callback to call with the transaction + * history + * @param hres_cb_cls closure for the above callback + * @return NULL if the inputs are invalid (i.e. zero value for + * @e num_results). In this case, the callback is not + * called. + */ +struct TALER_BANK_CreditHistoryHandle * +TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx, + const char *bank_base_url, + const struct TALER_BANK_AuthenticationData *auth, + uint64_t start_row, + int64_t num_results, + TALER_BANK_CreditResultCallback hres_cb, + void *hres_cb_cls) +{ + char *url; + struct TALER_BANK_CreditHistoryHandle *hh; + CURL *eh; + + if (0 == num_results) + { + GNUNET_break (0); + return NULL; + } + + if (UINT64_MAX == start_row) + GNUNET_asprintf (&url, + "/history&delta=%lld", + (long long) num_results); + else + GNUNET_asprintf (&url, + "/history&delta=%lld&start=%llu", + (long long) num_results, + start_row); + hh = GNUNET_new (struct TALER_BANK_CreditHistoryHandle); + hh->hcb = hres_cb; + hh->hcb_cls = hres_cb_cls; + hh->bank_base_url = GNUNET_strdup (bank_base_url); + hh->request_url = TALER_BANK_path_to_url_ (bank_base_url, + url); + + eh = curl_easy_init (); + if ( (GNUNET_OK != + TALER_BANK_setup_auth_ (eh, + auth)) || + (CURLE_OK != + curl_easy_setopt (eh, + CURLOPT_URL, + hh->request_url)) ) + { + GNUNET_break (0); + TALER_BANK_credit_history_cancel (hh); + curl_easy_cleanup (eh); + GNUNET_free (url); + return NULL; + } + hh->job = GNUNET_CURL_job_add2 (ctx, + eh, + NULL, + &handle_history_finished, + hh); + GNUNET_free (url); + return hh; +} + + +/** + * Cancel a history request. This function cannot be + * used on a request handle if a response is already + * served for it. + * + * @param hh the history request handle + */ +void +TALER_BANK_credit_history_cancel (struct TALER_BANK_CreditHistoryHandle *hh) +{ + if (NULL != hh->job) + { + GNUNET_CURL_job_cancel (hh->job); + hh->job = NULL; + } + GNUNET_free (hh->request_url); + GNUNET_free (hh->bank_base_url); + GNUNET_free (hh); +} + + +/* end of bank_api_credit.c */ diff --git a/src/bank-lib/bank_api_debit.c b/src/bank-lib/bank_api_debit.c new file mode 100644 index 000000000..848362438 --- /dev/null +++ b/src/bank-lib/bank_api_debit.c @@ -0,0 +1,317 @@ +/* + This file is part of TALER + Copyright (C) 2017--2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/bank_api_history.c + * @brief Implementation of the /history[-range] + * requests of the bank's HTTP API. + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "bank_api_common.h" +#include /* just for HTTP status codes */ +#include "taler_signatures.h" + + +/** + * @brief A /history Handle + */ +struct TALER_BANK_DebitHistoryHandle +{ + + /** + * The url for this request. + */ + char *request_url; + + /** + * The base URL of the bank. + */ + char *bank_base_url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_BANK_DebitResultCallback hcb; + + /** + * Closure for @a cb. + */ + void *hcb_cls; +}; + + +/** + * Parse history given in JSON format and invoke the callback on each item. + * + * @param hh handle to the account history request + * @param history JSON array with the history + * @return #GNUNET_OK if history was valid and @a rhistory and @a balance + * were set, + * #GNUNET_SYSERR if there was a protocol violation in @a history + */ +static int +parse_account_history (struct TALER_BANK_DebitHistoryHandle *hh, + const json_t *history) +{ + json_t *history_array; + + if (NULL == (history_array = json_object_get (history, + "data"))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (! json_is_array (history_array)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + for (unsigned int i = 0; ihcb (hh->hcb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + row_id, + &td, + transaction); + GNUNET_JSON_parse_free (hist_spec); + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /history request. + * + * @param cls the `struct TALER_BANK_DebitHistoryHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_history_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_BANK_DebitHistoryHandle *hh = cls; + enum TALER_ErrorCode ec; + const json_t *j = response; + + hh->job = NULL; + switch (response_code) + { + case 0: + ec = TALER_EC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + parse_account_history (hh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_INVALID_RESPONSE; + break; + } + response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */ + ec = TALER_EC_NONE; + break; + case MHD_HTTP_NO_CONTENT: + ec = TALER_EC_NONE; + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the bank is buggy + (or API version conflict); just pass JSON reply to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_FORBIDDEN: + /* Access denied */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, bank says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + ec = TALER_BANK_parse_ec_ (j); + response_code = 0; + break; + } + hh->hcb (hh->hcb_cls, + response_code, + ec, + 0LLU, + NULL, + j); + TALER_BANK_debit_history_cancel (hh); +} + + +/** + * Request the debit history of the exchange's bank account. + * + * @param ctx curl context for the event loop + * @param bank_base_url URL of the base INCLUDING account number + * @param auth authentication data to use + * @param start_row from which row on do we want to get results, + * use UINT64_MAX for the latest; exclusive + * @param num_results how many results do we want; + * negative numbers to go into the past, positive numbers + * to go into the future starting at @a start_row; + * must not be zero. + * @param hres_cb the callback to call with the transaction + * history + * @param hres_cb_cls closure for the above callback + * @return NULL if the inputs are invalid (i.e. zero value for + * @e num_results). In this case, the callback is not + * called. + */ +struct TALER_BANK_DebitHistoryHandle * +TALER_BANK_debit_history (struct GNUNET_CURL_Context *ctx, + const char *bank_base_url, + const struct TALER_BANK_AuthenticationData *auth, + uint64_t start_row, + int64_t num_results, + TALER_BANK_DebitResultCallback hres_cb, + void *hres_cb_cls) +{ + char *url; + struct TALER_BANK_DebitHistoryHandle *hh; + CURL *eh; + + if (0 == num_results) + { + GNUNET_break (0); + return NULL; + } + + if (UINT64_MAX == start_row) + GNUNET_asprintf (&url, + "/history&delta=%lld", + (long long) num_results); + else + GNUNET_asprintf (&url, + "/history&delta=%lld&start=%llu", + (long long) num_results, + start_row); + hh = GNUNET_new (struct TALER_BANK_DebitHistoryHandle); + hh->hcb = hres_cb; + hh->hcb_cls = hres_cb_cls; + hh->bank_base_url = GNUNET_strdup (bank_base_url); + hh->request_url = TALER_BANK_path_to_url_ (bank_base_url, + url); + + eh = curl_easy_init (); + if ( (GNUNET_OK != + TALER_BANK_setup_auth_ (eh, + auth)) || + (CURLE_OK != + curl_easy_setopt (eh, + CURLOPT_URL, + hh->request_url)) ) + { + GNUNET_break (0); + TALER_BANK_debit_history_cancel (hh); + curl_easy_cleanup (eh); + GNUNET_free (url); + return NULL; + } + hh->job = GNUNET_CURL_job_add2 (ctx, + eh, + NULL, + &handle_history_finished, + hh); + GNUNET_free (url); + return hh; +} + + +/** + * Cancel a history request. This function cannot be + * used on a request handle if a response is already + * served for it. + * + * @param hh the history request handle + */ +void +TALER_BANK_debit_history_cancel (struct TALER_BANK_DebitHistoryHandle *hh) +{ + if (NULL != hh->job) + { + GNUNET_CURL_job_cancel (hh->job); + hh->job = NULL; + } + GNUNET_free (hh->request_url); + GNUNET_free (hh->bank_base_url); + GNUNET_free (hh); +} + + +/* end of bank_api_debit.c */ diff --git a/src/bank-lib/bank_api_history.c b/src/bank-lib/bank_api_history.c deleted file mode 100644 index f5013b85b..000000000 --- a/src/bank-lib/bank_api_history.c +++ /dev/null @@ -1,445 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2017 GNUnet e.V. & Inria - - TALER is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 3, - or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public - License along with TALER; see the file COPYING. If not, - see -*/ -/** - * @file bank-lib/bank_api_history.c - * @brief Implementation of the /history[-range] - * requests of the bank's HTTP API. - * @author Christian Grothoff - * @author Marcello Stanisci - */ -#include "platform.h" -#include "bank_api_common.h" -#include /* just for HTTP status codes */ -#include "taler_signatures.h" - - -/** - * @brief A /history Handle - */ -struct TALER_BANK_HistoryHandle -{ - - /** - * The url for this request. - */ - char *request_url; - - /** - * The base URL of the bank. - */ - char *bank_base_url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * HTTP authentication-related headers for the request. - */ - struct curl_slist *authh; - - /** - * Function to call with the result. - */ - TALER_BANK_HistoryResultCallback hcb; - - /** - * Closure for @a cb. - */ - void *hcb_cls; -}; - - -/** - * Parse history given in JSON format and invoke the callback on each item. - * - * @param hh handle to the account history request - * @param history JSON array with the history - * @return #GNUNET_OK if history was valid and @a rhistory and @a balance - * were set, - * #GNUNET_SYSERR if there was a protocol violation in @a history - */ -static int -parse_account_history (struct TALER_BANK_HistoryHandle *hh, - const json_t *history) -{ - json_t *history_array; - char *bank_hostname; - - if (NULL == (history_array = json_object_get (history, "data"))) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - for (unsigned int i = 0; ibank_base_url, ':'); - GNUNET_assert (NULL != bank_hostname); - bank_hostname += 3; - - GNUNET_asprintf (&td.account_url, - ('/' == bank_hostname[strlen (bank_hostname) - 1]) - ? "payto://x-taler-bank/%s%llu" - : "payto://x-taler-bank/%s/%llu", - bank_hostname, - (unsigned long long) other_account); - hh->hcb (hh->hcb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - direction, - row_id, - &td, - transaction); - GNUNET_free (td.account_url); - GNUNET_JSON_parse_free (hist_spec); - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /history request. - * - * @param cls the `struct TALER_BANK_HistoryHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_history_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_BANK_HistoryHandle *hh = cls; - enum TALER_ErrorCode ec; - const json_t *j = response; - - hh->job = NULL; - switch (response_code) - { - case 0: - ec = TALER_EC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - parse_account_history (hh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - ec = TALER_EC_INVALID_RESPONSE; - break; - } - response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */ - ec = TALER_EC_NONE; - break; - case MHD_HTTP_NO_CONTENT: - ec = TALER_EC_NONE; - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the bank is buggy - (or API version conflict); just pass JSON reply to the application */ - ec = TALER_BANK_parse_ec_ (j); - break; - case MHD_HTTP_FORBIDDEN: - /* Access denied */ - ec = TALER_BANK_parse_ec_ (j); - break; - case MHD_HTTP_UNAUTHORIZED: - /* Nothing really to verify, bank says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - ec = TALER_BANK_parse_ec_ (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - ec = TALER_BANK_parse_ec_ (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - ec = TALER_BANK_parse_ec_ (j); - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - ec = TALER_BANK_parse_ec_ (j); - response_code = 0; - break; - } - hh->hcb (hh->hcb_cls, - response_code, - ec, - TALER_BANK_DIRECTION_NONE, - 0LLU, - NULL, - j); - TALER_BANK_history_cancel (hh); -} - - -/** - * Backend of both the /history[-range] requests. - * - * @param ctx curl context for the event loop - * @param bank_base_url base URL of the bank. - * @param urlargs path + URL arguments. - * @param auth authentication data to use - * @param hres_cb the callback to call with the transaction - * history - * @param hres_cb_cls closure for the above callback - * @return NULL if the inputs are invalid (i.e. zero value for - * @e num_results). In this case, the callback is not - * called. - */ -static struct TALER_BANK_HistoryHandle * -put_history_job (struct GNUNET_CURL_Context *ctx, - const char *bank_base_url, - const char *urlargs, - const struct TALER_BANK_AuthenticationData *auth, - TALER_BANK_HistoryResultCallback hres_cb, - void *hres_cb_cls) -{ - struct TALER_BANK_HistoryHandle *hh; - CURL *eh; - - hh = GNUNET_new (struct TALER_BANK_HistoryHandle); - hh->hcb = hres_cb; - hh->hcb_cls = hres_cb_cls; - hh->bank_base_url = GNUNET_strdup (bank_base_url); - hh->request_url = TALER_BANK_path_to_url_ (bank_base_url, - urlargs); - - hh->authh = TALER_BANK_make_auth_header_ (auth); - eh = curl_easy_init (); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_URL, - hh->request_url)); - hh->job = GNUNET_CURL_job_add2 (ctx, - eh, - hh->authh, - &handle_history_finished, - hh); - return hh; -} - - -/** - * Convert fixed value 'direction' into string. - * - * @param direction the value to convert. - * @return string representation of @a direction. NULL on error - */ -static const char * -conv_direction (enum TALER_BANK_Direction direction) -{ - if (TALER_BANK_DIRECTION_NONE == direction) - { - /* Should just never happen. */ - GNUNET_break (0); - return NULL; - } - if (TALER_BANK_DIRECTION_BOTH == - (TALER_BANK_DIRECTION_BOTH & direction)) - return "both"; - else if (TALER_BANK_DIRECTION_CREDIT == - (TALER_BANK_DIRECTION_CREDIT & direction)) - return "credit"; - else if (TALER_BANK_DIRECTION_DEBIT == - (TALER_BANK_DIRECTION_BOTH & direction)) /*why use 'both' flag?*/ - return "debit"; - /* Should just never happen. */ - GNUNET_break (0); - return NULL; -} - - -/** - * Convert fixed value 'direction' into string representation - * of the "cancel" argument. - * - * @param direction the value to convert. - * @return string representation of @a direction - */ -static const char * -conv_cancel (enum TALER_BANK_Direction direction) -{ - if (TALER_BANK_DIRECTION_CANCEL == - (TALER_BANK_DIRECTION_CANCEL & direction)) - return "show"; - return "omit"; -} - - -/** - * Request the wire transfer history of a bank account. - * - * @param ctx curl context for the event loop - * @param bank_base_url URL of the bank (used to execute this - * request) - * @param auth authentication data to use - * @param account_number which account number should we query - * @param direction what kinds of wire transfers should be - * returned - * @param ascending if GNUNET_YES, history elements will - * be returned in chronological order. - * @param start_row from which row on do we want to get results, - * use UINT64_MAX for the latest; exclusive - * @param num_results how many results do we want; - * negative numbers to go into the past, positive numbers - * to go into the future starting at @a start_row; - * must not be zero. - * @param hres_cb the callback to call with the transaction - * history - * @param hres_cb_cls closure for the above callback - * @return NULL if the inputs are invalid (i.e. zero value for - * @e num_results). In this case, the callback is not - * called. - */ -struct TALER_BANK_HistoryHandle * -TALER_BANK_history (struct GNUNET_CURL_Context *ctx, - const char *bank_base_url, - const struct TALER_BANK_AuthenticationData *auth, - uint64_t account_number, - enum TALER_BANK_Direction direction, - unsigned int ascending, - uint64_t start_row, - int64_t num_results, - TALER_BANK_HistoryResultCallback hres_cb, - void *hres_cb_cls) -{ - struct TALER_BANK_HistoryHandle *hh; - char *url; - - if (0 == num_results) - { - GNUNET_break (0); - return NULL; - } - - if (UINT64_MAX == start_row) - GNUNET_asprintf (&url, - "/history?auth=basic&account_number=%llu&delta=%lld&direction=%s&cancelled=%s&ordering=%s", - (unsigned long long) account_number, - (long long) num_results, - conv_direction (direction), - conv_cancel (direction), - (GNUNET_YES == ascending) ? "ascending" : "descending"); - else - GNUNET_asprintf (&url, - "/history?auth=basic&account_number=%llu&delta=%lld&direction=%s&cancelled=%s&ordering=%s&start=%llu", - (unsigned long long) account_number, - (long long) num_results, - conv_direction (direction), - conv_cancel (direction), - (GNUNET_YES == ascending) ? "ascending" : "descending", - start_row); - hh = put_history_job (ctx, - bank_base_url, - url, - auth, - hres_cb, - hres_cb_cls); - - GNUNET_free (url); - return hh; -} - - -/** - * Cancel a history request. This function cannot be - * used on a request handle if a response is already - * served for it. - * - * @param hh the history request handle - */ -void -TALER_BANK_history_cancel (struct TALER_BANK_HistoryHandle *hh) -{ - if (NULL != hh->job) - { - GNUNET_CURL_job_cancel (hh->job); - hh->job = NULL; - } - curl_slist_free_all (hh->authh); - GNUNET_free (hh->request_url); - GNUNET_free (hh->bank_base_url); - GNUNET_free (hh); -} - - -/* end of bank_api_history.c */ diff --git a/src/bank-lib/bank_api_parse.c b/src/bank-lib/bank_api_parse.c index 582e2a7dd..86288802d 100644 --- a/src/bank-lib/bank_api_parse.c +++ b/src/bank-lib/bank_api_parse.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2018 Taler Systems SA + Copyright (C) 2018-2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -23,6 +23,58 @@ #include "taler_bank_service.h" +/** + * Convenience method for parsing configuration section with bank account data. + * + * @param cfg configuration to parse + * @param section the section with the configuration data + * @param acc[out] set to the account details + * @return #GNUNET_OK on success + */ +int +TALER_BANK_account_parse_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, + struct TALER_Account *acc) +{ + char *account_url; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "URL", + &account_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "URL"); + return GNUNET_SYSERR; + } + if (TALER_EC_NONE != + TALER_WIRE_payto_to_account (account_url, + acc)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "URL", + "Malformed payto:// URL for x-taler-bank method"); + GNUNET_free (account_url); + return GNUNET_SYSERR; + } + if (TALER_PAC_X_TALER_BANK != acc->type) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "URL", + "Malformed payto:// URL for x-taler-bank method"); + GNUNET_free (account_url); + TALER_WIRE_account_free (acc); + return GNUNET_SYSERR; + } + GNUNET_free (account_url); + return GNUNET_OK; +} + + /** * Parse configuration section with bank authentication data. * diff --git a/src/bank-lib/bank_api_reject.c b/src/bank-lib/bank_api_reject.c deleted file mode 100644 index 3f181bc31..000000000 --- a/src/bank-lib/bank_api_reject.c +++ /dev/null @@ -1,242 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015, 2016, 2017 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - -*/ -/** - * @file bank-lib/bank_api_reject.c - * @brief Implementation of the /reject request of the bank's HTTP API - * @author Christian Grothoff - */ -#include "platform.h" -#include "bank_api_common.h" -#include /* just for HTTP status codes */ -#include "taler_signatures.h" - - -/** - * @brief A /reject Handle - */ -struct TALER_BANK_RejectHandle -{ - - /** - * The url for this request. - */ - char *request_url; - - /** - * JSON encoding of the request to POST. - */ - char *json_enc; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * HTTP authentication-related headers for the request. - */ - struct curl_slist *authh; - - /** - * Function to call with the result. - */ - TALER_BANK_RejectResultCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * Function called when we're done processing the - * HTTP /reject request. - * - * @param cls the `struct TALER_BANK_RejectHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reject_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_BANK_RejectHandle *rh = cls; - enum TALER_ErrorCode ec; - const json_t *j = response; - - rh->job = NULL; - switch (response_code) - { - case 0: - ec = TALER_EC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - GNUNET_break_op (0); - response_code = 0; - ec = TALER_EC_INVALID_RESPONSE; - break; - case MHD_HTTP_NO_CONTENT: - ec = TALER_EC_NONE; - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the bank is buggy - (or API version conflict); just pass JSON reply to the application */ - ec = TALER_BANK_parse_ec_ (j); - break; - case MHD_HTTP_FORBIDDEN: - /* Access denied */ - ec = TALER_BANK_parse_ec_ (j); - break; - case MHD_HTTP_UNAUTHORIZED: - /* Nothing really to verify, bank says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - ec = TALER_BANK_parse_ec_ (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - ec = TALER_BANK_parse_ec_ (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - ec = TALER_BANK_parse_ec_ (j); - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - ec = TALER_BANK_parse_ec_ (j); - response_code = 0; - break; - } - rh->cb (rh->cb_cls, - response_code, - ec); - TALER_BANK_reject_cancel (rh); -} - - -/** - * Request rejection of a wire transfer, marking it as cancelled and voiding - * its effects. - * - * @param ctx curl context for the event loop - * @param bank_base_url URL of the bank (used to execute this request) - * @param auth authentication data to use - * @param account_number which account number should we query - * @param rowid transfer to reject - * @param rcb the callback to call with the operation result - * @param rcb_cls closure for @a rcb - * @return NULL - * if the inputs are invalid. - * In this case, the callback is not called. - */ -struct TALER_BANK_RejectHandle * -TALER_BANK_reject (struct GNUNET_CURL_Context *ctx, - const char *bank_base_url, - const struct TALER_BANK_AuthenticationData *auth, - uint64_t account_number, - uint64_t rowid, - TALER_BANK_RejectResultCallback rcb, - void *rcb_cls) -{ - struct TALER_BANK_RejectHandle *rh; - json_t *reject_obj; - CURL *eh; - - reject_obj = json_pack ("{s:{s:s}, s:I, s:I}", - "auth", "type", "basic", - "row_id", (json_int_t) rowid, - "account_number", (json_int_t) account_number); - if (NULL == reject_obj) - { - GNUNET_break (0); - return NULL; - } - rh = GNUNET_new (struct TALER_BANK_RejectHandle); - rh->cb = rcb; - rh->cb_cls = rcb_cls; - rh->request_url = TALER_BANK_path_to_url_ (bank_base_url, - "/reject"); - rh->authh = TALER_BANK_make_auth_header_ (auth); - /* Append content type header here, can't do it in GNUNET_CURL_job_add - as that would override the CURLOPT_HTTPHEADER instead of appending. */ - { - struct curl_slist *ext; - - ext = curl_slist_append (rh->authh, - "Content-Type: application/json"); - if (NULL == ext) - GNUNET_break (0); - else - rh->authh = ext; - } - eh = curl_easy_init (); - GNUNET_assert (NULL != (rh->json_enc = - json_dumps (reject_obj, - JSON_COMPACT))); - json_decref (reject_obj); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_URL, - rh->request_url)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDS, - rh->json_enc)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDSIZE, - strlen (rh->json_enc))); - rh->job = GNUNET_CURL_job_add2 (ctx, - eh, - rh->authh, - &handle_reject_finished, - rh); - return rh; -} - - -/** - * Cancel an reject request. This function cannot be used on a request - * handle if the response was is already served for it. - * - * @param rh the reject request handle - */ -void -TALER_BANK_reject_cancel (struct TALER_BANK_RejectHandle *rh) -{ - if (NULL != rh->job) - { - GNUNET_CURL_job_cancel (rh->job); - rh->job = NULL; - } - curl_slist_free_all (rh->authh); - GNUNET_free (rh->request_url); - GNUNET_free (rh->json_enc); - GNUNET_free (rh); -} - - -/* end of bank_api_reject.c */ diff --git a/src/bank-lib/bank_api_transaction.c b/src/bank-lib/bank_api_transaction.c new file mode 100644 index 000000000..177328482 --- /dev/null +++ b/src/bank-lib/bank_api_transaction.c @@ -0,0 +1,368 @@ +/* + This file is part of TALER + Copyright (C) 2015--2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + +*/ +/** + * @file bank-lib/bank_api_transaction.c + * @brief Implementation of the /transaction/ requests of the bank's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include "bank_api_common.h" +#include /* just for HTTP status codes */ +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "taler_bank_service.h" + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Data structure serialized in the prepare stage. + */ +struct WirePackP +{ + /** + * Random unique identifier for the request. + */ + struct GNUNET_HashCode request_uid; + + /** + * Amount to be transferred. + */ + struct TALER_AmountNBO amount; + + /** + * Wire transfer identifier to use. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /** + * Length of the payto:// URL of the target account, + * including 0-terminator, in network byte order. + */ + uint32_t account_len GNUNET_PACKED; + + /** + * Length of the exchange's base URL, + * including 0-terminator, in network byte order. + */ + uint32_t exchange_url_len GNUNET_PACKED; + +}; + +GNUNET_NETWORK_STRUCT_END + +/** + * Prepare for exeuction of a wire transfer. + * + * @param destination_account_url payto:// URL identifying where to send the money + * @param amount amount to transfer, already rounded + * @param exchange_base_url base URL of this exchange (included in subject + * to facilitate use of tracking API by merchant backend) + * @param wtid wire transfer identifier to use + * @param buf[out] set to transaction data to persist, NULL on error + * @param buf_size[out] set to number of bytes in @a buf, 0 on error + */ +void +TALER_BANK_prepare_wire_transfer (const char *destination_account_url, + const struct TALER_Amount *amount, + const char *exchange_base_url, + const struct + TALER_WireTransferIdentifierRawP *wtid, + void **buf, + size_t *buf_size) +{ + struct WirePackP *wp; + size_t d_len = strlen (destination_account_url) + 1; + size_t u_len = strlen (exchange_base_url) + 1; + char *end; + + *buf_size = sizeof (*wp) + d_len + u_len; + wp = GNUNET_malloc (*buf_size); + GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE, + &wp->request_uid); + TALER_amount_hton (&wp->amount, + amount); + wp->wtid = *wtid; + wp->account_len = htonl ((uint32_t) d_len); + wp->exchange_url_len = htonl ((uint32_t) u_len); + end = (char *) &wp[1]; + memcpy (end, + destination_account_url, + d_len); + memcpy (end + d_len, + exchange_base_url, + u_len); + *buf = (char *) wp; +} + + +/** + * @brief An transaction Handle + */ +struct TALER_BANK_WireExecuteHandle +{ + + /** + * The url for this request. + */ + char *request_url; + + /** + * POST context. + */ + struct TEAH_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_BANK_ConfirmationCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * Function called when we're done processing the + * HTTP /transaction request. + * + * @param cls the `struct TALER_BANK_WireExecuteHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_transaction_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_BANK_WireExecuteHandle *weh = cls; + uint64_t row_id = UINT64_MAX; + struct GNUNET_TIME_Absolute timestamp; + enum TALER_ErrorCode ec; + const json_t *j = response; + + weh->job = NULL; + timestamp = GNUNET_TIME_UNIT_FOREVER_ABS; + switch (response_code) + { + case 0: + ec = TALER_EC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ("row_id", + &row_id), + GNUNET_JSON_spec_absolute_time ("timestamp", + ×tamp), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_INVALID_RESPONSE; + break; + } + ec = TALER_EC_NONE; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the bank is buggy + (or API version conflict); just pass JSON reply to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_FORBIDDEN: + /* Access denied */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, bank says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_NOT_ACCEPTABLE: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + ec = TALER_BANK_parse_ec_ (j); + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + ec = TALER_BANK_parse_ec_ (j); + response_code = 0; + break; + } + weh->cb (weh->cb_cls, + response_code, + ec, + row_id, + timestamp); + TALER_BANK_execute_wire_transfer_cancel (weh); +} + + +/** + * Execute a wire transfer. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param buf buffer with the prepared execution details + * @param buf_size number of bytes in @a buf + * @param cc function to call upon success + * @param cc_cls closure for @a cc + * @return NULL on error + */ +struct TALER_BANK_WireExecuteHandle * +TALER_BANK_execute_wire_transfer (struct GNUNET_CURL_Context *ctx, + const char *bank_base_url, + const struct + TALER_BANK_AuthenticationData *auth, + const void *buf, + size_t buf_size, + TALER_BANK_ConfirmationCallback cc, + void *cc_cls) +{ + struct TALER_BANK_WireExecuteHandle *weh; + json_t *transaction_obj; + CURL *eh; + const struct WirePackP *wp = buf; + uint32_t d_len; + uint32_t u_len; + const char *destination_account_url; + const char *exchange_base_url; + struct TALER_Amount amount; + + if (sizeof (*wp) > buf_size) + { + GNUNET_break (0); + return NULL; + } + d_len = ntohl (wp->account_len); + u_len = ntohl (wp->exchange_url_len); + if (sizeof (*wp) + d_len + u_len != buf_size) + { + GNUNET_break (0); + return NULL; + } + destination_account_url = (const char *) &wp[1]; + exchange_base_url = destination_account_url + d_len; + if (NULL == bank_base_url) + { + GNUNET_break (0); + return NULL; + } + TALER_amount_ntoh (&amount, + &wp->amount); + transaction_obj = json_pack ("{s:o, s:o, s:s, s:o, s:o, s:s}", + "request_uid", GNUNET_JSON_from_data_auto ( + &wp->request_uid), + "amount", TALER_JSON_from_amount (&amount), + "exchange_url", exchange_base_url, + "wtid", GNUNET_JSON_from_data_auto (&wp->wtid), + "credit_account", destination_account_url); + if (NULL == transaction_obj) + { + GNUNET_break (0); + return NULL; + } + weh = GNUNET_new (struct TALER_BANK_WireExecuteHandle); + weh->cb = cc; + weh->cb_cls = cc_cls; + weh->request_url = TALER_BANK_path_to_url_ (bank_base_url, + "/transaction"); + weh->post_ctx.headers = curl_slist_append + (weh->post_ctx.headers, + "Content-Type: application/json"); + + eh = curl_easy_init (); + if ( (GNUNET_OK != + TALER_BANK_setup_auth_ (eh, + auth)) || + (CURLE_OK != + curl_easy_setopt (eh, + CURLOPT_URL, + weh->request_url)) || + (GNUNET_OK != + TALER_curl_easy_post (&weh->post_ctx, + eh, + transaction_obj)) ) + { + GNUNET_break (0); + TALER_BANK_execute_wire_transfer_cancel (weh); + curl_easy_cleanup (eh); + json_decref (transaction_obj); + return NULL; + } + json_decref (transaction_obj); + + weh->job = GNUNET_CURL_job_add2 (ctx, + eh, + weh->post_ctx.headers, + &handle_transaction_finished, + weh); + return weh; +} + + +/** + * Cancel a wire transfer. This function cannot be used on a request handle + * if a response is already served for it. + * + * @param weh the wire transfer request handle + */ +void +TALER_BANK_execute_wire_transfer_cancel (struct + TALER_BANK_WireExecuteHandle *weh) +{ + if (NULL != weh->job) + { + GNUNET_CURL_job_cancel (weh->job); + weh->job = NULL; + } + TALER_curl_easy_post_finished (&weh->post_ctx); + GNUNET_free (weh->request_url); + GNUNET_free (weh); +} + + +/* end of bank_api_transaction.c */ diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c index d8ea392b5..1706ca8fb 100644 --- a/src/bank-lib/fakebank.c +++ b/src/bank-lib/fakebank.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2016, 2017, 2018 Inria and GNUnet e.V. + (C) 2016-2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -25,7 +25,7 @@ #include "platform.h" #include "taler_fakebank_lib.h" #include "taler_bank_service.h" -#include "fakebank.h" +#include "taler_mhd_lib.h" /** * Maximum POST request size (for /admin/add/incoming) @@ -33,6 +33,65 @@ #define REQUEST_BUFFER_MAX (4 * 1024) +/** + * Details about a transcation we (as the simulated bank) received. + */ +struct Transaction +{ + /** + * We store transactions in a DLL. + */ + struct Transaction *next; + + /** + * We store transactions in a DLL. + */ + struct Transaction *prev; + + /** + * Amount to be transferred. + */ + struct TALER_Amount amount; + + /** + * Account to debit. + */ + char *debit_account; + + /** + * Account to credit. + */ + char *credit_account; + + /** + * Subject of the transfer. + */ + char *subject; + + /** + * Base URL of the exchange. + */ + char *exchange_base_url; + + /** + * When did the transaction happen? + */ + struct GNUNET_TIME_Absolute date; + + /** + * Number of this transaction. + */ + uint64_t row_id; + + /** + * Has this transaction been subjected to #TALER_FAKEBANK_check() + * and should thus no longer be counted in + * #TALER_FAKEBANK_check_empty()? + */ + int checked; +}; + + /** * Handle for the fake bank. */ @@ -63,6 +122,11 @@ struct TALER_FAKEBANK_Handle */ uint64_t serial_counter; + /** + * Our port number. + */ + uint16_t port; + #if EPOLL_SUPPORT /** * Boxed @e mhd_fd. @@ -95,8 +159,8 @@ struct TALER_FAKEBANK_Handle int TALER_FAKEBANK_check (struct TALER_FAKEBANK_Handle *h, const struct TALER_Amount *want_amount, - uint64_t want_debit, - uint64_t want_credit, + const char *want_debit, + const char *want_credit, const char *exchange_base_url, char **subject) { @@ -151,8 +215,8 @@ TALER_FAKEBANK_check (struct TALER_FAKEBANK_Handle *h, */ uint64_t TALER_FAKEBANK_make_transfer (struct TALER_FAKEBANK_Handle *h, - uint64_t debit_account, - uint64_t credit_account, + const char *debit_account, + const char *credit_account, const struct TALER_Amount *amount, const char *subject, const char *exchange_base_url) @@ -160,15 +224,15 @@ TALER_FAKEBANK_make_transfer (struct TALER_FAKEBANK_Handle *h, struct Transaction *t; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Making transfer from %llu to %llu over %s and subject %s; for exchange: %s\n", - (unsigned long long) debit_account, - (unsigned long long) credit_account, + "Making transfer from %s to %s over %s and subject %s; for exchange: %s\n", + debit_account, + credit_account, TALER_amount2s (amount), subject, exchange_base_url); t = GNUNET_new (struct Transaction); - t->debit_account = debit_account; - t->credit_account = credit_account; + t->debit_account = GNUNET_strdup (debit_account); + t->credit_account = GNUNET_strdup (credit_account); t->amount = *amount; t->exchange_base_url = GNUNET_strdup (exchange_base_url); t->row_id = ++h->serial_counter; @@ -182,31 +246,6 @@ TALER_FAKEBANK_make_transfer (struct TALER_FAKEBANK_Handle *h, } -/** - * Reject incoming wire transfer to account @a credit_account - * as identified by @a rowid. - * - * @param h fake bank handle - * @param rowid identifies transfer to reject - * @param credit_account account number of owner of credited account - * @return #GNUNET_YES on success, #GNUNET_NO if the wire transfer was not found - */ -int -TALER_FAKEBANK_reject_transfer (struct TALER_FAKEBANK_Handle *h, - uint64_t rowid, - uint64_t credit_account) -{ - for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next) - if ( (t->row_id == rowid) && - (t->credit_account == credit_account) ) - { - t->rejected = GNUNET_YES; - return GNUNET_YES; - } - return GNUNET_NO; -} - - /** * Check that no wire transfers were ordered (or at least none * that have not been taken care of via #TALER_FAKEBANK_check()). @@ -223,8 +262,7 @@ TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h) t = h->transactions_head; while (NULL != t) { - if ( (GNUNET_YES != t->checked) && - (GNUNET_YES != t->rejected) ) + if (GNUNET_YES != t->checked) break; t = t->next; } @@ -234,16 +272,15 @@ TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h) "Expected empty transaction set, but I have:\n"); while (NULL != t) { - if ( (GNUNET_YES != t->checked) && - (GNUNET_YES != t->rejected) ) + if (GNUNET_YES != t->checked) { char *s; s = TALER_amount_to_string (&t->amount); fprintf (stderr, - "%llu -> %llu (%s) from %s\n", - (unsigned long long) t->debit_account, - (unsigned long long) t->credit_account, + "%s -> %s (%s) from %s\n", + t->debit_account, + t->credit_account, s, t->exchange_base_url); GNUNET_free (s); @@ -270,6 +307,8 @@ TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h) h->transactions_tail, t); GNUNET_free (t->subject); + GNUNET_free (t->debit_account); + GNUNET_free (t->credit_account); GNUNET_free (t->exchange_base_url); GNUNET_free (t); } @@ -290,62 +329,6 @@ TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h) } -/** - * Create and queue a bank error message with the HTTP response - * code @a response_code on connection @a connection. - * - * @param connection where to queue the reply - * @param response_code http status code to use - * @param ec taler error code to use - * @param message human readable error message - * @return MHD status code - */ -static int -create_bank_error (struct MHD_Connection *connection, - unsigned int response_code, - enum TALER_ErrorCode ec, - const char *message) -{ - json_t *json; - struct MHD_Response *resp; - void *json_str; - size_t json_len; - int ret; - - json = json_pack ("{s:s, s:I}", - "error", - message, - "ec", - (json_int_t) ec); - json_str = json_dumps (json, - JSON_INDENT (2)); - json_decref (json); - if (NULL == json_str) - { - GNUNET_break (0); - return MHD_NO; - } - json_len = strlen (json_str); - resp = MHD_create_response_from_buffer (json_len, - json_str, - MHD_RESPMEM_MUST_FREE); - if (NULL == resp) - { - GNUNET_break (0); - free (json_str); - return MHD_NO; - } - (void) MHD_add_response_header (resp, - MHD_HTTP_HEADER_CONTENT_TYPE, - "application/json"); - ret = MHD_queue_response (connection, - response_code, - resp); - MHD_destroy_response (resp); - return ret; -} - - /** * Function called whenever MHD is done with a request. If the * request was a POST, we may have stored a `struct Buffer *` in the @@ -394,8 +377,6 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, { enum GNUNET_JSON_PostResult pr; json_t *json; - struct MHD_Response *resp; - int ret; uint64_t row_id; pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, @@ -422,15 +403,14 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, } { const char *subject; - uint64_t debit_account; - uint64_t credit_account; + const char *debit_account; + const char *credit_account; const char *base_url; struct TALER_Amount amount; - char *amount_s; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("subject", &subject), - GNUNET_JSON_spec_uint64 ("debit_account", &debit_account), - GNUNET_JSON_spec_uint64 ("credit_account", &credit_account), + GNUNET_JSON_spec_string ("debit_account", &debit_account), + GNUNET_JSON_spec_string ("credit_account", &credit_account), TALER_JSON_spec_amount ("amount", &amount), GNUNET_JSON_spec_string ("exchange_url", &base_url), GNUNET_JSON_spec_end () @@ -450,80 +430,49 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, &amount, subject, base_url); - amount_s = TALER_amount_to_string (&amount); GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Receiving incoming wire transfer: %llu->%llu, subject: %s, amount: %s, from %s\n", - (unsigned long long) debit_account, - (unsigned long long) credit_account, + "Receiving incoming wire transfer: %s->%s, subject: %s, amount: %s, from %s\n", + debit_account, + credit_account, subject, - amount_s, + TALER_amount2s (&amount), base_url); - GNUNET_free (amount_s); } json_decref (json); /* Finally build response object */ - { - void *json_str; - size_t json_len; - - json = json_pack ("{s:I, s:o}", - "row_id", - (json_int_t) row_id, - "timestamp", GNUNET_JSON_from_time_abs (GNUNET_TIME_UNIT_ZERO_ABS)); /*dummy tmp */ - - json_str = json_dumps (json, - JSON_INDENT (2)); - json_decref (json); - if (NULL == json_str) - { - GNUNET_break (0); - return MHD_NO; - } - json_len = strlen (json_str); - resp = MHD_create_response_from_buffer (json_len, - json_str, - MHD_RESPMEM_MUST_FREE); - if (NULL == resp) - { - GNUNET_break (0); - free (json_str); - return MHD_NO; - } - (void) MHD_add_response_header (resp, - MHD_HTTP_HEADER_CONTENT_TYPE, - "application/json"); - } - ret = MHD_queue_response (connection, - MHD_HTTP_OK, - resp); - MHD_destroy_response (resp); - return ret; + return TALER_MHD_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:I, s:o}", + "row_id", + (json_int_t) row_id, + "timestamp", GNUNET_JSON_from_time_abs ( + GNUNET_TIME_UNIT_ZERO_ABS)); /*dummy tmp */ } /** - * Handle incoming HTTP request for /reject. + * Handle incoming HTTP request for /transaction. * * @param h the fakebank handle * @param connection the connection + * @param account account making the transaction * @param upload_data request data * @param upload_data_size size of @a upload_data in bytes * @param con_cls closure for request (a `struct Buffer *`) * @return MHD result code */ static int -handle_reject (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) +handle_transaction (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) { enum GNUNET_JSON_PostResult pr; json_t *json; - struct MHD_Response *resp; - int ret; - int found; + uint64_t row_id; pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, connection, @@ -548,13 +497,25 @@ handle_reject (struct TALER_FAKEBANK_Handle *h, break; } { - uint64_t row_id; - uint64_t credit_account; + struct GNUNET_HashCode uuid; + struct TALER_WireTransferIdentifierRawP wtid; + const char *credit_account; + const char *base_url; + struct TALER_Amount amount; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("row_id", &row_id), - GNUNET_JSON_spec_uint64 ("account_number", &credit_account), + GNUNET_JSON_spec_fixed_auto ("request_uid", + &uuid), + TALER_JSON_spec_amount ("amount", + &amount), + GNUNET_JSON_spec_string ("exchange_base_url", + &base_url), + GNUNET_JSON_spec_fixed_auto ("wtid", + &wtid), + GNUNET_JSON_spec_string ("credit_account", + &credit_account), GNUNET_JSON_spec_end () }; + if (GNUNET_OK != GNUNET_JSON_parse (json, spec, @@ -564,31 +525,38 @@ handle_reject (struct TALER_FAKEBANK_Handle *h, json_decref (json); return MHD_NO; } - found = TALER_FAKEBANK_reject_transfer (h, - row_id, - credit_account); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Rejected wire transfer #%llu (to %llu)\n", - (unsigned long long) row_id, - (unsigned long long) credit_account); + { + char *subject; + + subject = GNUNET_STRINGS_data_to_string_alloc (&wtid, + sizeof (wtid)); + // FIXME: use uuid here!!! + row_id = TALER_FAKEBANK_make_transfer (h, + account, + credit_account, + &amount, + subject, + base_url); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Receiving incoming wire transfer: %s->%s, subject: %s, amount: %s, from %s\n", + account, + credit_account, + subject, + TALER_amount2s (&amount), + base_url); + GNUNET_free (subject); + } } json_decref (json); - if (GNUNET_OK != found) - return create_bank_error - (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_TRANSACTION_NOT_FOUND, - "transaction unknown"); - /* finally build regular response */ - resp = MHD_create_response_from_buffer (0, - NULL, - MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_response (connection, - MHD_HTTP_NO_CONTENT, - resp); - MHD_destroy_response (resp); - return ret; + /* Finally build response object */ + return TALER_MHD_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:I, s:o}", + "row_id", + (json_int_t) row_id, + "timestamp", GNUNET_JSON_from_time_abs ( + GNUNET_TIME_UNIT_ZERO_ABS)); /*dummy tmp */ } @@ -626,75 +594,245 @@ handle_home_page (struct TALER_FAKEBANK_Handle *h, /** - * Handle incoming HTTP request for /history + * This is the "base" structure for both the /history and the + * /history-range API calls. + */ +struct HistoryArgs +{ + + /** + * Bank account number of the requesting client. + */ + uint64_t account_number; + + /** + * Index of the starting transaction. + */ + uint64_t start_idx; + + /** + * Requested number of results and order + * (positive: ascending, negative: descending) + */ + int64_t delta; + + /** + * Timeout for long polling. + */ + struct GNUNET_TIME_Relative lp_timeout; + + /** + * #GNUNET_YES if starting point was given. + */ + int have_start; + +}; + + +/** + * Parse URL history arguments, of _both_ APIs: + * /history/incoming and /history/outgoing. + * + * @param connection MHD connection. + * @param function_name name of the caller. + * @param ha[out] will contain the parsed values. + * @return GNUNET_OK only if the parsing succeedes. + */ +static int +parse_history_common_args (struct MHD_Connection *connection, + struct HistoryArgs *ha) +{ + const char *start; + const char *delta; + const char *long_poll_ms; + unsigned long long lp_timeout; + unsigned long long sval; + long long d; + + start = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "start"); + ha->have_start = (NULL != start); + delta = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "delta"); + long_poll_ms = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "long_poll_ms"); + lp_timeout = 0; + if ( (NULL == delta) || + (1 != sscanf (delta, + "%lld", + &d)) || + ( (NULL != long_poll_ms) && + (1 != sscanf (long_poll_ms, + "%llu", + &lp_timeout)) ) || + ( (NULL != start) && + (1 != sscanf (start, + "%llu", + &sval)) ) ) + { + /* Fail if one of the above failed. */ + /* Invalid request, given that this is fakebank we impolitely + * just kill the connection instead of returning a nice error. + */ + GNUNET_break (0); + return GNUNET_NO; + } + if (NULL == start) + ha->start_idx = (d > 0) ? 0 : UINT64_MAX; + else + ha->start_idx = (uint64_t) sval; + ha->delta = (int64_t) d; + ha->lp_timeout + = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + lp_timeout); + return GNUNET_OK; +} + + +/** + * Handle incoming HTTP request for /history/incoming * * @param h the fakebank handle * @param connection the connection - * @param con_cls place to store state, not used + * @param account which account the request is about * @return MHD result code */ static int -handle_history (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - void **con_cls) +handle_credit_history (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account) { struct HistoryArgs ha; - struct HistoryRangeIds hri; - const char *start; - const char *delta; struct Transaction *pos; + json_t *history; - (void) con_cls; if (GNUNET_OK != - TFH_parse_history_common_args (connection, - &ha)) + parse_history_common_args (connection, + &ha)) { GNUNET_break (0); return MHD_NO; } - start = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "start"); - delta = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "delta"); - if ( ((NULL != start) && (1 != sscanf (start, - "%llu", - &hri.start))) || - (NULL == delta) || (1 != sscanf (delta, - "%lld", - &hri.count)) ) + if (! ha.have_start) + { + pos = (0 > ha.delta) + ? h->transactions_tail + : h->transactions_head; + } + else if (NULL != h->transactions_head) + { + for (pos = h->transactions_head; + NULL != pos; + pos = pos->next) + if (pos->row_id == ha.start_idx) + break; + if (NULL == pos) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid start specified, transaction %llu not known!\n", + (unsigned long long) ha.start_idx); + return MHD_NO; + } + /* range is exclusive, skip the matching entry */ + if (ha.delta > 0) + pos = pos->next; + if (ha.delta < 0) + pos = pos->prev; + } + else + { + /* list is empty */ + pos = NULL; + } + history = json_array (); + while ( (0 != ha.delta) && + (NULL != pos) ) + { + if (0 == strcasecmp (pos->credit_account, + account)) + { + json_t *trans; + + trans = json_pack + ("{s:I, s:o, s:o, s:s, s:s, s:s}", + "row_id", (json_int_t) pos->row_id, + "date", GNUNET_JSON_from_time_abs (pos->date), + "amount", TALER_JSON_from_amount (&pos->amount), + "credit_account", account, + "debit_account", pos->debit_account, + "wtid", pos->subject /* we "know" it is OK */); + GNUNET_assert (0 == + json_array_append_new (history, + trans)); + if (ha.delta > 0) + ha.delta--; + else + ha.delta++; + } + if (ha.delta > 0) + pos = pos->prev; + else + pos = pos->next; + } + return TALER_MHD_reply_json (connection, + history, + MHD_HTTP_OK); +} + + +/** + * Handle incoming HTTP request for /history/incoming + * + * @param h the fakebank handle + * @param connection the connection + * @param account which account the request is about + * @return MHD result code + */ +static int +handle_debit_history (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account) +{ + struct HistoryArgs ha; + struct Transaction *pos; + json_t *history; + + if (GNUNET_OK != + parse_history_common_args (connection, + &ha)) { GNUNET_break (0); return MHD_NO; } - ha.range = &hri; - if (NULL == start) + if (! ha.have_start) { - pos = 0 > hri.count ? - h->transactions_tail : h->transactions_head; + pos = (0 > ha.delta) + ? h->transactions_tail + : h->transactions_head; } else if (NULL != h->transactions_head) { for (pos = h->transactions_head; NULL != pos; pos = pos->next) - if (pos->row_id == hri.start) + if (pos->row_id == ha.start_idx) break; if (NULL == pos) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid range specified," - " transaction %llu not known!\n", - (unsigned long long) hri.start); + "Invalid start specified, transaction %llu not known!\n", + (unsigned long long) ha.start_idx); return MHD_NO; } /* range is exclusive, skip the matching entry */ - if (hri.count > 0) + if (ha.delta > 0) pos = pos->next; - if (hri.count < 0) + if (ha.delta < 0) pos = pos->prev; } else @@ -702,56 +840,77 @@ handle_history (struct TALER_FAKEBANK_Handle *h, /* list is empty */ pos = NULL; } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "/history, start row (0 == no transactions exist): %llu\n", - NULL != pos ? pos->row_id : 0LL); - return TFH_build_history_response (connection, - pos, - &ha, - &TFH_handle_history_skip, - &TFH_handle_history_step, - &TFH_handle_history_advance); + history = json_array (); + while ( (0 != ha.delta) && + (NULL != pos) ) + { + if (0 == strcasecmp (pos->debit_account, + account)) + { + json_t *trans; + + trans = json_pack + ("{s:I, s:o, s:o, s:s, s:s, s:s}", + "row_id", (json_int_t) pos->row_id, + "date", GNUNET_JSON_from_time_abs (pos->date), + "amount", TALER_JSON_from_amount (&pos->amount), + "credit_account", pos->credit_account, + "debit_account", account, + "reserve_pub", pos->subject /* we "know" it is OK */); + GNUNET_assert (0 == + json_array_append_new (history, + trans)); + if (ha.delta > 0) + ha.delta--; + else + ha.delta++; + } + if (ha.delta > 0) + pos = pos->prev; + else + pos = pos->next; + } + return TALER_MHD_reply_json (connection, + history, + MHD_HTTP_OK); } /** * Handle incoming HTTP request. * - * @param cls a `struct TALER_FAKEBANK_Handle` + * @param h our handle * @param connection the connection * @param url the requested url * @param method the method (POST, GET, ...) - * @param version HTTP version (ignored) + * @param account which account should process the request * @param upload_data request data * @param upload_data_size size of @a upload_data in bytes * @param con_cls closure for request (a `struct Buffer *`) * @return MHD result code */ static int -handle_mhd_request (void *cls, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) +serve (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account, + const char *url, + const char *method, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) { - struct TALER_FAKEBANK_Handle *h = cls; - - (void) version; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Fakebank, serving: %s\n", url); - if ( (0 == strcasecmp (url, - "/")) && + if ( (0 == strcmp (url, + "/")) && (0 == strcasecmp (method, MHD_HTTP_METHOD_GET)) ) return handle_home_page (h, connection, con_cls); - if ( (0 == strcasecmp (url, - "/admin/add/incoming")) && + if ( (0 == strcmp (url, + "/admin/add/incoming")) && (0 == strcasecmp (method, MHD_HTTP_METHOD_POST)) ) return handle_admin_add_incoming (h, @@ -759,22 +918,33 @@ handle_mhd_request (void *cls, upload_data, upload_data_size, con_cls); - if ( (0 == strcasecmp (url, - "/reject")) && + if ( (0 == strcmp (url, + "/transaction")) && + (NULL != account) && (0 == strcasecmp (method, MHD_HTTP_METHOD_POST)) ) - return handle_reject (h, - connection, - upload_data, - upload_data_size, - con_cls); - if ( (0 == strcasecmp (url, - "/history")) && + return handle_transaction (h, + connection, + account, + upload_data, + upload_data_size, + con_cls); + if ( (0 == strcmp (url, + "/history/incoming")) && + (NULL != account) && + (0 == strcasecmp (method, + MHD_HTTP_METHOD_GET)) ) + return handle_credit_history (h, + connection, + account); + if ( (0 == strcmp (url, + "/history/outgoing")) && + (NULL != account) && (0 == strcasecmp (method, MHD_HTTP_METHOD_GET)) ) - return handle_history (h, - connection, - con_cls); + return handle_debit_history (h, + connection, + account); /* Unexpected URL path, just close the connection. */ /* we're rather impolite here, but it's a testcase. */ @@ -785,6 +955,55 @@ handle_mhd_request (void *cls, } +/** + * Handle incoming HTTP request. + * + * @param cls a `struct TALER_FAKEBANK_Handle` + * @param connection the connection + * @param url the requested url + * @param method the method (POST, GET, ...) + * @param version HTTP version (ignored) + * @param upload_data request data + * @param upload_data_size size of @a upload_data in bytes + * @param con_cls closure for request (a `struct Buffer *`) + * @return MHD result code + */ +static int +handle_mhd_request (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + struct TALER_FAKEBANK_Handle *h = cls; + char *account = NULL; + char *end; + int ret; + + (void) version; + if ( (strlen (url) > 1) && + (NULL != (end = strchr (url + 1, '/'))) ) + { + account = GNUNET_strndup (url + 1, + end - url - 1); + url = end; + } + ret = serve (h, + connection, + account, + url, + method, + upload_data, + upload_data_size, + con_cls); + GNUNET_free_non_null (account); + return ret; +} + + /** * Task run whenever HTTP server operations are pending. * @@ -918,6 +1137,7 @@ TALER_FAKEBANK_start (uint16_t port) struct TALER_FAKEBANK_Handle *h; h = GNUNET_new (struct TALER_FAKEBANK_Handle); + h->port = port; h->mhd_bank = MHD_start_daemon (MHD_USE_DEBUG #if EPOLL_SUPPORT | MHD_USE_EPOLL_INTERNAL_THREAD diff --git a/src/bank-lib/fakebank.h b/src/bank-lib/fakebank.h index cc2359014..c52902f14 100644 --- a/src/bank-lib/fakebank.h +++ b/src/bank-lib/fakebank.h @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2016, 2017, 2018 Inria and GNUnet e.V. + (C) 2016-2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -19,7 +19,7 @@ /** * @file bank-lib/fakebank.h - * @brief definitions for the "/history[-range]" layer. + * @brief definitions for the "/history" layer. * @author Marcello Stanisci */ @@ -29,283 +29,4 @@ #include #include "taler_bank_service.h" -/** - * Details about a transcation we (as the simulated bank) received. - */ -struct Transaction -{ - /** - * We store transactions in a DLL. - */ - struct Transaction *next; - - /** - * We store transactions in a DLL. - */ - struct Transaction *prev; - - /** - * Amount to be transferred. - */ - struct TALER_Amount amount; - - /** - * Account to debit. - */ - uint64_t debit_account; - - /** - * Account to credit. - */ - uint64_t credit_account; - - /** - * Subject of the transfer. - */ - char *subject; - - /** - * Base URL of the exchange. - */ - char *exchange_base_url; - - /** - * When did the transaction happen? - */ - struct GNUNET_TIME_Absolute date; - - /** - * Number of this transaction. - */ - long long unsigned int row_id; - - /** - * Flag set if the transfer was rejected. - */ - int rejected; - - /** - * Has this transaction been subjected to #TALER_FAKEBANK_check() - * and should thus no longer be counted in - * #TALER_FAKEBANK_check_empty()? - */ - int checked; -}; - - -/****************************************** - * Definitions for "/history" start here. * - ******************************************/ - -/** - * Needed to implement ascending/descending ordering - * of /history results. - */ -struct HistoryElement -{ - - /** - * History JSON element. - */ - json_t *element; - - /** - * Previous element. - */ - struct HistoryElement *prev; - - /** - * Next element. - */ - struct HistoryElement *next; -}; - - -/** - * Values to implement the "/history-range" range. - */ -struct HistoryRangeDates -{ - /** - * Oldest row in the results. - */ - struct GNUNET_TIME_Absolute start; - - /** - * Youngest row in the results. - */ - struct GNUNET_TIME_Absolute end; -}; - -/** - * Values to implement the "/history" range. - */ -struct HistoryRangeIds -{ - - /** - * (Exclusive) row ID for the result set. - */ - unsigned long long start; - - /** - * How many transactions we want in the result set. If - * negative/positive, @a start will be strictly younger/older - * of any element in the result set. - */ - long long count; -}; - - -/** - * This is the "base" structure for both the /history and the - * /history-range API calls. - */ -struct HistoryArgs -{ - - /** - * Direction asked by the client: CREDIT / DEBIT / BOTH / CANCEL. - */ - enum TALER_BANK_Direction direction; - - /** - * Bank account number of the requesting client. - */ - unsigned long long account_number; - - /** - * Ordering of the results. - */ - unsigned int ascending; - - /** - * Overloaded type that indicates the "range" to be returned - * in the results; this can be either a date range, or a - * starting row id + the count. - */ - void *range; -}; - - -/** - * Type for a function that decides whether or not - * the history-building loop should iterate once again. - * Typically called from inside the 'while' condition. - * - * @param ha history argument. - * @param pos current position. - * @return GNUNET_YES if the iteration shuold go on. - */ -typedef int (*CheckAdvance)(const struct HistoryArgs *ha, - const struct Transaction *pos); - -/** - * Type for a function that steps over the next element - * in the list of all transactions, after the current @a pos - * _got_ included in the result. - */ -typedef struct Transaction * (*Step)(const struct HistoryArgs *ha, - const struct Transaction *pos); - -/* - * Type for a function that steps over the next element - * in the list of all transactions, after the current @a pos - * did _not_ get included in the result. - */ -typedef struct Transaction * (*Skip)(const struct HistoryArgs *ha, - const struct Transaction *pos); - -/** - * Actual history response builder. - * - * @param pos first (included) element in the result set. - * @param ha history arguments. - * @param caller_name which function is building the history. - * @return MHD_YES / MHD_NO, after having enqueued the response - * object into MHD. - */ -int -TFH_build_history_response (struct MHD_Connection *connection, - struct Transaction *pos, - struct HistoryArgs *ha, - Skip skip, - Step step, - CheckAdvance advance); - - -/** - * Parse URL history arguments, of _both_ APIs: - * /history and /history-range. - * - * @param connection MHD connection. - * @param function_name name of the caller. - * @param ha[out] will contain the parsed values. - * @return GNUNET_OK only if the parsing succeedes. - */ -int -TFH_parse_history_common_args (struct MHD_Connection *connection, - struct HistoryArgs *ha); - - -/** - * Decides whether the history builder will advance or not - * to the next element. - * - * @param ha history args - * @return GNUNET_YES/NO to advance/not-advance. - */ -int -TFH_handle_history_advance (const struct HistoryArgs *ha, - const struct Transaction *pos); - -/** - * Iterates on the "next" element to be processed. To - * be used when the current element does not get inserted in - * the result. - * - * @param ha history arguments. - * @param pos current element being processed. - * @return the next element to be processed. - */ -struct Transaction * -TFH_handle_history_skip (const struct HistoryArgs *ha, - const struct Transaction *pos); - -/** - * Iterates on the "next" element to be processed. To - * be used when the current element _gets_ inserted in the result. - * - * @param ha history arguments. - * @param pos current element being processed. - * @return the next element to be processed. - */ -struct Transaction * -TFH_handle_history_step (const struct HistoryArgs *ha, - const struct Transaction *pos); - -/** - * Decides whether the history builder will advance or not - * to the next element. - * - * @param ha history args - * @return GNUNET_YES/NO to advance/not-advance. - */ -int -TFH_handle_history_range_advance (const struct HistoryArgs *ha, - const struct Transaction *pos); - -/** - * Iterates towards the "next" element to be processed. To - * be used when the current element does not get inserted in - * the result. - * - * @param ha history arguments. - * @param pos current element being processed. - * @return the next element to be processed. - */ -struct Transaction * -TFH_handle_history_range_skip (const struct HistoryArgs *ha, - const struct Transaction *pos); - #endif diff --git a/src/bank-lib/fakebank_history.c b/src/bank-lib/fakebank_history.c index f4c615c52..2781cdca8 100644 --- a/src/bank-lib/fakebank_history.c +++ b/src/bank-lib/fakebank_history.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2016, 2017, 2018 Inria and GNUnet e.V. + (C) 2016-2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -19,92 +19,15 @@ /** * @file bank-lib/fakebank_history.c - * @brief definitions for the "/history[-range]" layer. + * @brief definitions for the "/history" layer. * @author Marcello Stanisci */ - #include "platform.h" #include #include "taler_json_lib.h" #include "fakebank.h" -/** - * Decides whether the history builder will advance or not - * to the next element. - * - * @param ha history args - * @return GNUNET_YES/NO to advance/not-advance. - */ -int -TFH_handle_history_advance (const struct HistoryArgs *ha, - const struct Transaction *pos) -{ - const struct HistoryRangeIds *hri = ha->range; - - return (NULL != pos) && (0 != hri->count); -} - - -/** - * Iterates on the "next" element to be processed. To - * be used when the current element does not get inserted in - * the result. - * - * @param ha history arguments. - * @param pos current element being processed. - * @return the next element to be processed. - */ -struct Transaction * -TFH_handle_history_skip (const struct HistoryArgs *ha, - const struct Transaction *pos) -{ - const struct HistoryRangeIds *hri = ha->range; - - if (hri->count > 0) - return pos->next; - if (hri->count < 0) - return pos->prev; - return NULL; -} - - -/** - * Iterates on the "next" element to be processed. To - * be used when the current element _gets_ inserted in the result. - * - * @param ha history arguments. - * @param pos current element being processed. - * @return the next element to be processed. - */ -struct Transaction * -TFH_handle_history_step (const struct HistoryArgs *ha, - const struct Transaction *pos) -{ - struct HistoryRangeIds *hri = ha->range; - - if (hri->count > 0) - { - hri->count--; - return pos->next; - } - if (hri->count < 0) - { - hri->count++; - return pos->prev; - } - return NULL; -} - -/** - * Actual history response builder. - * - * @param pos first (included) element in the result set, NULL if history is empty - * @param ha history arguments. - * @param caller_name which function is building the history. - * @return MHD_YES / MHD_NO, after having enqueued the response - * object into MHD. - */ int TFH_build_history_response (struct MHD_Connection *connection, struct Transaction *pos, @@ -257,126 +180,3 @@ TFH_build_history_response (struct MHD_Connection *connection, } return ret; } - - -/** - * Parse URL history arguments, of _both_ APIs: - * /history and /history-range. - * - * @param connection MHD connection. - * @param function_name name of the caller. - * @param ha[out] will contain the parsed values. - * @return GNUNET_OK only if the parsing succeedes. - */ -int -TFH_parse_history_common_args (struct MHD_Connection *connection, - struct HistoryArgs *ha) -{ - /** - * @variable - * Just check if given and == "basic", no need to keep around. - */ - const char *auth; - - /** - * All those will go into the structure, after parsing. - */ - const char *direction; - const char *cancelled; - const char *ordering; - const char *account_number; - - - auth = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "auth"); - direction = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "direction"); - cancelled = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "cancelled"); - ordering = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "ordering"); - account_number = MHD_lookup_connection_value - (connection, - MHD_GET_ARGUMENT_KIND, - "account_number"); - - /* Fail if one of the above failed. */ - if ( (NULL == direction) || - (NULL == cancelled) || - ( (0 != strcasecmp (cancelled, - "OMIT")) && - (0 != strcasecmp (cancelled, - "SHOW")) ) || - ( (0 != strcasecmp (direction, - "BOTH")) && - (0 != strcasecmp (direction, - "CREDIT")) && - (0 != strcasecmp (direction, - "DEBIT")) ) || - (1 != sscanf (account_number, - "%llu", - &ha->account_number)) || - ( (NULL == auth) || (0 != strcasecmp (auth, - "basic")) ) ) - { - /* Invalid request, given that this is fakebank we impolitely - * just kill the connection instead of returning a nice error. - */ - GNUNET_break (0); - return GNUNET_NO; - } - - if (0 == strcasecmp (direction, - "CREDIT")) - { - ha->direction = TALER_BANK_DIRECTION_CREDIT; - } - else if (0 == strcasecmp (direction, - "DEBIT")) - { - ha->direction = TALER_BANK_DIRECTION_DEBIT; - } - else if (0 == strcasecmp (direction, - "BOTH")) - { - ha->direction = TALER_BANK_DIRECTION_BOTH; - } - - /* Direction is invalid. */ - else - { - GNUNET_break (0); - return GNUNET_NO; - } - - if (0 == strcasecmp (cancelled, - "OMIT")) - { - /* nothing */ - } - else if (0 == strcasecmp (cancelled, - "SHOW")) - { - ha->direction |= TALER_BANK_DIRECTION_CANCEL; - } - - /* Cancel-showing policy is invalid. */ - else - { - GNUNET_break (0); - return GNUNET_NO; - } - - if ((NULL != ordering) - && (0 == strcmp ("ascending", - ordering))) - ha->ascending = GNUNET_YES; - else - ha->ascending = GNUNET_NO; - - return GNUNET_OK; -} diff --git a/src/bank-lib/taler-bank-transfer.c b/src/bank-lib/taler-bank-transfer.c index 625545b6b..c52c4b38d 100644 --- a/src/bank-lib/taler-bank-transfer.c +++ b/src/bank-lib/taler-bank-transfer.c @@ -25,9 +25,9 @@ #include "taler_bank_service.h" /** - * Bank URL. + * Account base URL. */ -static char *bank_url; +static char *account_base_url; /** * Amount to transfer. @@ -35,14 +35,9 @@ static char *bank_url; static struct TALER_Amount amount; /** - * Debit account number. + * Credit account payto://-URI. */ -static unsigned long long debit_account_no; - -/** - * Credit account number. - */ -static unsigned long long credit_account_no; +static char *credit_account; /** * Wire transfer subject. @@ -168,11 +163,23 @@ run (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg) { struct TALER_BANK_AuthenticationData auth; + struct TALER_ReservePublicKeyP reserve_pub; (void) cls; (void) args; (void) cfgfile; (void) cfg; + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (subject, + strlen (subject), + &reserve_pub, + sizeof (reserve_pub))) + { + fprintf (stderr, + "Error: wire transfer subject must be a reserve public key\n"); + return; + } + ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, &rc); GNUNET_assert (NULL != ctx); @@ -182,13 +189,11 @@ run (void *cls, auth.details.basic.username = username; auth.details.basic.password = password; op = TALER_BANK_admin_add_incoming (ctx, - bank_url, + account_base_url, &auth, - "https://exchange.com/legacy", - subject, + &reserve_pub, &amount, - debit_account_no, - credit_account_no, + credit_account, &res_cb, NULL); GNUNET_SCHEDULER_add_shutdown (&do_shutdown, @@ -219,26 +224,20 @@ main (int argc, char *const *argv) (GNUNET_GETOPT_option_string ('b', "bank", "URL", - "base URL of the bank", - &bank_url)), + "base URL of the account at the bank", + &account_base_url)), GNUNET_GETOPT_option_help ("Deposit funds into a Taler reserve"), GNUNET_GETOPT_option_mandatory - (GNUNET_GETOPT_option_ulong ('C', - "credit", - "ACCOUNT", - "number of the bank account to credit", - &credit_account_no)), - GNUNET_GETOPT_option_mandatory - (GNUNET_GETOPT_option_ulong ('D', - "debit", - "ACCOUNT", - "number of the bank account to debit", - &debit_account_no)), + (GNUNET_GETOPT_option_string ('C', + "credit", + "ACCOUNT", + "payto URL of the bank account to credit", + &credit_account)), GNUNET_GETOPT_option_mandatory (GNUNET_GETOPT_option_string ('s', "subject", "STRING", - "specifies the wire transfer subject", + "specifies the wire transfer subject (must be a reserve public key)", &subject)), GNUNET_GETOPT_option_mandatory (GNUNET_GETOPT_option_string ('u', diff --git a/src/bank-lib/test_bank_api.c b/src/bank-lib/test_bank_api.c index d15984a9c..087e44848 100644 --- a/src/bank-lib/test_bank_api.c +++ b/src/bank-lib/test_bank_api.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2016, 2017 GNUnet e.V. + Copyright (C) 2016-2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as diff --git a/src/bank-lib/testing_api_cmd_history.c b/src/bank-lib/testing_api_cmd_history.c deleted file mode 100644 index dc5cd2d99..000000000 --- a/src/bank-lib/testing_api_cmd_history.c +++ /dev/null @@ -1,1004 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2018 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 3, or - (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public - License along with TALER; see the file COPYING. If not, see - -*/ - -/** - * @file bank-lib/testing_api_cmd_history.c - * @brief command to check the /history API from the bank. - * @author Marcello Stanisci - */ - -#include "platform.h" -#include "taler_json_lib.h" -#include -#include "taler_exchange_service.h" -#include "taler_testing_lib.h" -#include "taler_testing_bank_lib.h" -#include "taler_fakebank_lib.h" -#include "taler_bank_service.h" -#include "taler_fakebank_lib.h" - - -/** - * State for a "history" CMD. - */ -struct HistoryState -{ - /** - * Base URL of the bank offering the "history" operation. - */ - const char *bank_url; - - /** - * Account number to ask the history for. - */ - uint64_t account_no; - - /** - * Which type of records we are interested: in-transfers - * / out-transfers / rejected transfers. - */ - enum TALER_BANK_Direction direction; - - /** - * First row number we want in the result. - */ - const char *start_row_reference; - - /** - * How many rows we want in the result, _at most_. - */ - unsigned long long num_results; - - /** - * Handle to a pending "history" operation. - */ - struct TALER_BANK_HistoryHandle *hh; - - /** - * Expected number of results (= rows). - */ - uint64_t results_obtained; - - /** - * Set to GNUNET_YES if the callback detects something - * unexpected. - */ - int failed; - - /** - * If GNUNET_YES, this parameter will ask for results in - * chronological order. - */ - unsigned int ascending; - - /********************************** - * Following defs are specific to * - * the "/history-range" version. * - **********************************/ - - /** - * Last row number we want in the result. Only used - * as a trait source when using the /history-range API. - */ - const char *end_row_reference; - - /** - * Start date for /history-range. - */ - struct GNUNET_TIME_Absolute start_date; - - /** - * End date for /history-range. - */ - struct GNUNET_TIME_Absolute end_date; -}; - -/** - * Item in the transaction history, as reconstructed from the - * command history. - */ -struct History -{ - - /** - * Wire details. - */ - struct TALER_BANK_TransferDetails details; - - /** - * Serial ID of the wire transfer. - */ - uint64_t row_id; - - /** - * Direction of the transfer. - */ - enum TALER_BANK_Direction direction; - -}; - - -/** - * Array mapping bank account numbers to login credentials. - */ -extern struct TALER_BANK_AuthenticationData AUTHS[]; - - -/** - * Offer internal data to other commands. - * - * @param cls closure. - * @param ret[out] set to the wanted data. - * @param trait name of the trait. - * @param index index number of the traits to be returned. - * - * @return #GNUNET_OK on success - */ -static int -history_traits (void *cls, - const void **ret, - const char *trait, - unsigned int index) -{ - (void) cls; - (void) ret; - (void) trait; - (void) index; - /* Must define this function because some callbacks - * look for certain traits on _all_ the commands. */ - return GNUNET_SYSERR; -} - - -/** - * Test if the CMD at offset @a off has been /rejected, and - * is indeed a wire transfer CMD. - * - * @param is interpreter state (where we are right now) - * @param off offset of the command to test for rejection. - * - * @return GNUNET_YES if the command at @a off was cancelled. - */ -static int -test_cancelled (struct TALER_TESTING_Interpreter *is, - unsigned int off) -{ - const char *rejected_reference; - const struct TALER_TESTING_Command *current_cmd; - - current_cmd = &is->commands[off]; - TALER_LOG_INFO ("Is `%s' rejected?\n", - current_cmd->label); - for (int i = 0; iip; i++) - { - const struct TALER_TESTING_Command *c = &is->commands[i]; - - - /* XXX: Errors reported here are NOT fatal */ - - /* Rejected wire transfers have a non-NULL reference to a - * reject command to mark them as rejected. So errors - * about "reject traits" not found are NOT fatal here */ - if (GNUNET_OK != TALER_TESTING_get_trait_rejected - (c, 0, &rejected_reference)) - continue; - - TALER_LOG_INFO ("Command `%s' was rejected by `%s'.\n", - current_cmd->label, - c->label); - - if (0 == strcmp (rejected_reference, - current_cmd->label)) - return GNUNET_YES; - } - return GNUNET_NO; -} - - -/** - * Free history @a h of length @a h_len. - * - * @param h history array to free. - * @param h_len number of entries in @a h. - */ -static void -free_history (struct History *h, - uint64_t h_len) -{ - for (uint64_t off = 0; offstart_date.abs_value_us) - { - const struct GNUNET_TIME_Absolute *timestamp; - - GNUNET_assert (GNUNET_OK == - TALER_TESTING_get_trait_absolute_time (pos, - 0, - ×tamp)); - GNUNET_assert (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != - hs->end_date.abs_value_us); - return timestamp->abs_value_us >= hs->end_date.abs_value_us; - } - return total >= hs->num_results; -} - - -/** - * This function constructs the list of history elements that - * interest the account number of the caller. It has two main - * loops: the first to figure out how many history elements have - * to be allocated, and the second to actually populate every - * element. - * - * This command has a limitation currently: it orders the history - * list with descending elements if and only if the 'delta' was - * given negative; and will order the list with ascending elements - * if and only if the 'delta' was given positive. Therefore, - * for now it is NOT possible to test such a "/history" request: - * "/history?auth=basic&direction=both&delta=10&ordering=descending" - * - * @param is interpreter state (supposedly having the - * current CMD pointing at a "history" CMD). - * @param[out] rh history array to initialize. - * - * @return number of entries in @a rh. - */ -static uint64_t -build_history (struct TALER_TESTING_Interpreter *is, - struct History **rh) -{ - struct HistoryState *hs = is->commands[is->ip].cls; - uint64_t total; - struct History *h; - const struct TALER_TESTING_Command *add_incoming_cmd; - int inc; - unsigned int start; - unsigned int end; - - /** - * @var turns GNUNET_YES whenever either no 'start' value was - * given for the history query, or the given value is found - * in the list of all the CMDs. - */int ok; - const uint64_t *row_id_start = NULL; - - if (NULL != hs->start_row_reference) - { - TALER_LOG_INFO - ("`%s': start row given via reference `%s'\n", - TALER_TESTING_interpreter_get_current_label (is), - hs->start_row_reference); - add_incoming_cmd = TALER_TESTING_interpreter_lookup_command - (is, hs->start_row_reference); - GNUNET_assert (NULL != add_incoming_cmd); - GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_uint64 - (add_incoming_cmd, 0, &row_id_start)); - } - - GNUNET_assert ((0 != hs->num_results) || /* "/history" */ - (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != /* "/history-range" */ - hs->start_date.abs_value_us)); - - if (0 == is->ip) - { - TALER_LOG_DEBUG ("Checking history at first CMD..\n"); - *rh = NULL; - return 0; - } - - /* AKA 'delta'. */ - if (hs->num_results > 0) - { - inc = 1; /* _inc_rement */ - start = 0; - end = is->ip - 1; - } - else - { - inc = -1; - start = is->ip - 1; - end = 0; - } - - total = 0; - ok = GNUNET_NO; - - if (NULL == row_id_start) - ok = GNUNET_YES; - - /* This loop counts how many commands _later than "start"_ belong - * to the history of the caller. This is stored in the @var total - * variable. */ - for (unsigned int off = start; off != end + inc; off += inc) - { - const struct TALER_TESTING_Command *pos = &is->commands[off]; - int cancelled; - const uint64_t *row_id; - - /** - * The following command allows us to skip over those CMDs - * that do not offer a "row_id" trait. Such skipped CMDs are - * not interesting for building a history. - */if (GNUNET_OK != TALER_TESTING_get_trait_uint64 (pos, - 0, - &row_id)) - continue; - - /* Seek "/history" starting row. */ - if (NULL != row_id_start) - { - if (*row_id_start == *row_id) - { - /* Doesn't count, start is excluded from output. */ - total = 0; - ok = GNUNET_YES; - continue; - } - } - - /* Seek "/history-range" starting row, _if_ that's the case */ - if ((GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != - hs->start_date.abs_value_us) && (GNUNET_YES != ok)) - { - const struct GNUNET_TIME_Absolute *timestamp; - - TALER_TESTING_get_trait_absolute_time (pos, - 0, - ×tamp); - TALER_LOG_DEBUG - ("Seeking first row, start vs timestamp: %llu vs %llu\n", - (long long unsigned int) hs->start_date.abs_value_us, - (long long unsigned int) timestamp->abs_value_us); - - if (hs->start_date.abs_value_us <= timestamp->abs_value_us) - { - total = 0; - ok = GNUNET_YES; - continue; - } - } - - /* when 'start' was _not_ given, then ok == GNUNET_YES */ - if (GNUNET_NO == ok) - continue; /* skip until we find the marker */ - - TALER_LOG_DEBUG ("Found first row\n"); - - if (build_history_hit_limit (total, - hs, - pos)) - { - TALER_LOG_DEBUG ("Hit history limit\n"); - break; - } - - cancelled = test_cancelled (is, off); - - if ( (GNUNET_YES == cancelled) && - (0 == (hs->direction & TALER_BANK_DIRECTION_CANCEL)) ) - { - TALER_LOG_INFO ("Ignoring canceled wire" - " transfer from history\n"); - continue; - } - - const uint64_t *credit_account_no; - const uint64_t *debit_account_no; - - GNUNET_assert - (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT - (pos, &credit_account_no)); - - GNUNET_assert - (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT - (pos, &debit_account_no)); - - TALER_LOG_INFO ("Potential history element:" - " %llu->%llu; my account: %llu\n", - (unsigned long long) *debit_account_no, - (unsigned long long) *credit_account_no, - (unsigned long long) hs->account_no); - - if ( ( (0 != (hs->direction & TALER_BANK_DIRECTION_CREDIT)) && - (hs->account_no == *credit_account_no)) || - ( (0 != (hs->direction & TALER_BANK_DIRECTION_DEBIT)) && - (hs->account_no == *debit_account_no)) ) - { - TALER_LOG_INFO ("+1 my history\n"); - total++; /* found matching record */ - } - } - - GNUNET_assert (GNUNET_YES == ok); - - if (0 == total) - { - TALER_LOG_DEBUG ("Checking history at first CMD.. (2)\n"); - *rh = NULL; - return 0; - } - - - GNUNET_assert (total < UINT_MAX); - h = GNUNET_new_array ((unsigned int) total, - struct History); - total = 0; - ok = GNUNET_NO; - if (NULL == row_id_start) - ok = GNUNET_YES; - - /** - * This loop _only_ populates the array of history elements. - */ - for (unsigned int off = start; off != end + inc; off += inc) - { - const struct TALER_TESTING_Command *pos = &is->commands[off]; - int cancelled; - const uint64_t *row_id; - char *bank_hostname; - const uint64_t *credit_account_no; - const uint64_t *debit_account_no; - - if (GNUNET_OK != TALER_TESTING_GET_TRAIT_ROW_ID - (pos, &row_id)) - continue; - - if (NULL != row_id_start) - { - - if (*row_id_start == *row_id) - { - /** - * Warning: this zeroing is superfluous, as - * total doesn't get incremented if 'start' - * was given and couldn't be found. - */total = 0; - ok = GNUNET_YES; - continue; - } - } - - /* Seek "/history-range" starting row, _if_ that's the case */ - if ((GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != - hs->start_date.abs_value_us) && (GNUNET_YES != ok)) - { - const struct GNUNET_TIME_Absolute *timestamp; - - TALER_TESTING_get_trait_absolute_time (pos, - 0, - ×tamp); - TALER_LOG_DEBUG - ("Seeking first row, start vs timestamp (2): %llu vs %llu\n", - (long long unsigned int) hs->start_date.abs_value_us, - (long long unsigned int) timestamp->abs_value_us); - - if (hs->start_date.abs_value_us <= timestamp->abs_value_us) - { - total = 0; - ok = GNUNET_YES; - continue; - } - } - - TALER_LOG_INFO ("Found first row (2)\n"); - - if (GNUNET_NO == ok) - { - TALER_LOG_INFO ("Skip on `%s'\n", - pos->label); - continue; /* skip until we find the marker */ - } - - if (build_history_hit_limit (total, - hs, - pos)) - { - TALER_LOG_INFO ("Hit history limit (2)\n"); - break; - } - - GNUNET_assert - (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT - (pos, &credit_account_no)); - - GNUNET_assert - (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT - (pos, &debit_account_no)); - - TALER_LOG_INFO ("Potential history bit:" - " %llu->%llu; my account: %llu\n", - (unsigned long long) *debit_account_no, - (unsigned long long) *credit_account_no, - (unsigned long long) hs->account_no); - - /** - * Discard transactions where the audited account played - * _both_ the credit and the debit roles, but _only if_ - * the audit goes on both directions.. This needs more - * explaination! - */if ( ( (0 != (hs->direction & TALER_BANK_DIRECTION_CREDIT)) && - (hs->account_no == *credit_account_no)) && - ( (0 != (hs->direction & TALER_BANK_DIRECTION_DEBIT)) && - (hs->account_no == *debit_account_no)) ) - { - GNUNET_break (0); - continue; - } - - cancelled = test_cancelled (is, off); - if ( (GNUNET_YES == cancelled) && - (0 == (hs->direction & TALER_BANK_DIRECTION_CANCEL)) ) - { - TALER_LOG_WARNING ("`%s' was cancelled\n", - TALER_TESTING_interpreter_get_current_label - (is)); - continue; - } - - bank_hostname = strchr (hs->bank_url, ':'); - GNUNET_assert (NULL != bank_hostname); - bank_hostname += 3; - - /* Next two blocks only put the 'direction' and 'banking' - * information. */ - - /* Asked for credit, and account got the credit. */ - if ( (0 != (hs->direction & TALER_BANK_DIRECTION_CREDIT)) && - (hs->account_no == *credit_account_no)) - { - h[total].direction = TALER_BANK_DIRECTION_CREDIT; - if (GNUNET_YES == cancelled) - h[total].direction |= TALER_BANK_DIRECTION_CANCEL; - - GNUNET_asprintf - (&h[total].details.account_url, - ('/' == bank_hostname[strlen (bank_hostname) - 1]) - ? "payto://x-taler-bank/%s%llu" - : "payto://x-taler-bank/%s/%llu", - bank_hostname, - (unsigned long long) *debit_account_no); - } - - /* Asked for debit, and account got the debit. */ - if ( (0 != (hs->direction & TALER_BANK_DIRECTION_DEBIT)) && - (hs->account_no == *debit_account_no)) - { - h[total].direction = TALER_BANK_DIRECTION_DEBIT; - if (GNUNET_YES == cancelled) - h[total].direction |= TALER_BANK_DIRECTION_CANCEL; - - GNUNET_asprintf - (&h[total].details.account_url, - ('/' == bank_hostname[strlen (bank_hostname) - 1]) - ? "payto://x-taler-bank/%s%llu" - : "payto://x-taler-bank/%s/%llu", - bank_hostname, - (unsigned long long) *credit_account_no); - } - - /* This block _completes_ the information of the current item, - * with amount / subject / exchange URL. */ - if ( ( (0 != (hs->direction & TALER_BANK_DIRECTION_CREDIT)) && - (hs->account_no == *credit_account_no)) || - ( (0 != (hs->direction & TALER_BANK_DIRECTION_DEBIT)) && - (hs->account_no == *debit_account_no)) ) - { - const struct TALER_Amount *amount; - const char *subject; - const char *exchange_url; - - GNUNET_assert - (GNUNET_OK == TALER_TESTING_get_trait_amount_obj - (pos, 0, &amount)); - - GNUNET_assert - (GNUNET_OK == TALER_TESTING_get_trait_transfer_subject - (pos, 0, &subject)); - - GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_url - (pos, 0, &exchange_url)); - - h[total].details.amount = *amount; - - h[total].row_id = *row_id; - GNUNET_asprintf (&h[total].details.wire_transfer_subject, - "%s %s", - subject, - exchange_url); - TALER_LOG_INFO ("+1-bit of my history\n"); - total++; - } - } - *rh = h; - return total; -} - - -/** - * Compute how many results we expect to be returned for - * the current command at @a is. - * - * @param is the interpreter state to inspect. - * @return number of results expected. - */ -static uint64_t -compute_result_count (struct TALER_TESTING_Interpreter *is) -{ - uint64_t total; - struct History *h; - - total = build_history (is, &h); - free_history (h, total); - return total; -} - - -/** - * Check that the "/history" response matches the - * CMD whose offset in the list of CMDs is @a off. - * - * @param is the interpreter state. - * @param off the offset (of the CMD list) where the command - * to check is. - * @param dir the expected direction of the transaction. - * @param details the expected transaction details. - * - * @return #GNUNET_OK if the transaction is what we expect. - */ -static int -check_result (struct TALER_TESTING_Interpreter *is, - unsigned int off, - enum TALER_BANK_Direction dir, - const struct TALER_BANK_TransferDetails *details) -{ - uint64_t total; - struct History *h; - - total = build_history (is, &h); - if (off >= total) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Test says history has at most %u" - " results, but got result #%u to check\n", - (unsigned int) total, - off); - print_expected (h, total, off); - return GNUNET_SYSERR; - } - if (h[off].direction != dir) - { - GNUNET_break (0); - print_expected (h, total, off); - free_history (h, - total); - return GNUNET_SYSERR; - } - - if ( (0 != strcmp (h[off].details.wire_transfer_subject, - details->wire_transfer_subject)) || - (0 != TALER_amount_cmp (&h[off].details.amount, - &details->amount)) || - (0 != strcasecmp (h[off].details.account_url, - details->account_url)) ) - { - GNUNET_break (0); - print_expected (h, total, off); - free_history (h, - total); - return GNUNET_SYSERR; - } - free_history (h, - total); - return GNUNET_OK; -} - - -/** - * This callback will (1) check that the HTTP response code - * is acceptable and (2) that the history is consistent. The - * consistency is checked by going through all the past CMDs, - * reconstructing then the expected history as of those, and - * finally check it against what the bank returned. - * - * @param cls closure. - * @param http_status HTTP response code, #MHD_HTTP_OK (200) - * for successful status request 0 if the bank's reply is - * bogus (fails to follow the protocol), - * #MHD_HTTP_NO_CONTENT if there are no more results; on - * success the last callback is always of this status - * (even if `abs(num_results)` were already returned). - * @param ec taler status code. - * @param dir direction of the transfer. - * @param row_id monotonically increasing counter corresponding to - * the transaction. - * @param details details about the wire transfer. - * @param json detailed response from the HTTPD, or NULL if - * reply was not in JSON. - */ -static void -history_cb (void *cls, - unsigned int http_status, - enum TALER_ErrorCode ec, - enum TALER_BANK_Direction dir, - uint64_t row_id, - const struct TALER_BANK_TransferDetails *details, - const json_t *json) -{ - struct TALER_TESTING_Interpreter *is = cls; - struct HistoryState *hs = is->commands[is->ip].cls; - - (void) row_id; - /*NOTE: "204 No Content" is used to signal the end of results.*/ - if (MHD_HTTP_NO_CONTENT == http_status) - { - hs->hh = NULL; - if ( (hs->results_obtained != compute_result_count (is)) || - (GNUNET_YES == hs->failed) ) - { - uint64_t total; - struct History *h; - - GNUNET_break (0); - total = build_history (is, &h); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Expected history of length %llu, got %llu;" - " HTTP status code: %u/%d, failed: %d\n", - (unsigned long long) total, - (unsigned long long) hs->results_obtained, - http_status, - (int) ec, - hs->failed); - print_expected (h, - total, - UINT_MAX); - free_history (h, - total); - TALER_TESTING_interpreter_fail (is); - return; - } - TALER_TESTING_interpreter_next (is); - return; - } - - if (MHD_HTTP_OK != http_status) - { - hs->hh = NULL; - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unwanted response code from /history[-range]: %u\n", - http_status); - TALER_TESTING_interpreter_fail (is); - return; - } - - /* check current element */ - if (GNUNET_OK != check_result (is, - hs->results_obtained, - dir, - details)) - { - GNUNET_break (0); - - { - char *acc; - - acc = json_dumps (json, - JSON_COMPACT); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Result %u was `%s'\n", - (unsigned int) hs->results_obtained++, - acc); - if (NULL != acc) - free (acc); - } - - hs->failed = GNUNET_YES; - return; - } - hs->results_obtained++; -} - - -/** - * Run the command. - * - * @param cls closure. - * @param cmd the command to execute. - * @param is the interpreter state. - */ -static void -history_run (void *cls, - const struct TALER_TESTING_Command *cmd, - struct TALER_TESTING_Interpreter *is) -{ - struct HistoryState *hs = cls; - uint64_t row_id = UINT64_MAX; - const uint64_t *row_id_ptr = &row_id; - struct TALER_BANK_AuthenticationData *auth; - - (void) cmd; - /* Get row_id from trait. */ - if (NULL != hs->start_row_reference) - { - const struct TALER_TESTING_Command *history_cmd; - - history_cmd = TALER_TESTING_interpreter_lookup_command - (is, hs->start_row_reference); - - if (NULL == history_cmd) - TALER_TESTING_FAIL (is); - - if (GNUNET_OK != TALER_TESTING_get_trait_uint64 (history_cmd, - 0, - &row_id_ptr)) - TALER_TESTING_FAIL (is); - row_id = *row_id_ptr; - - TALER_LOG_DEBUG ("row id (from trait) is %llu\n", - (unsigned long long) row_id); - } - - auth = &AUTHS[hs->account_no - 1]; - hs->hh = TALER_BANK_history (is->ctx, - hs->bank_url, - auth, - hs->account_no, - hs->direction, - hs->ascending, - row_id, - hs->num_results, - &history_cb, - is); - GNUNET_assert (NULL != hs->hh); -} - - -/** - * Free the state from a "history" CMD, and possibly cancel - * a pending operation thereof. - * - * @param cls closure. - * @param cmd the command which is being cleaned up. - */ -static void -history_cleanup (void *cls, - const struct TALER_TESTING_Command *cmd) -{ - struct HistoryState *hs = cls; - - (void) cmd; - if (NULL != hs->hh) - { - TALER_LOG_WARNING ("/history did not complete\n"); - TALER_BANK_history_cancel (hs->hh); - } - GNUNET_free (hs); -} - - -/** - * Make a "history" CMD. - * - * @param label command label. - * @param bank_url base URL of the bank offering the "history" - * operation. - * @param account_no bank account number to ask the history for. - * @param direction which direction this operation is interested. - * @param ascending if #GNUNET_YES, the bank will return the rows - * in ascending (= chronological) order. - * @param start_row_reference reference to a command that can - * offer a row identifier, to be used as the starting row - * to accept in the result. - * @param num_results how many rows we want in the result. - * @return the command. - */ -struct TALER_TESTING_Command -TALER_TESTING_cmd_bank_history (const char *label, - const char *bank_url, - uint64_t account_no, - enum TALER_BANK_Direction direction, - unsigned int ascending, - const char *start_row_reference, - unsigned long long num_results) -{ - struct HistoryState *hs; - - hs = GNUNET_new (struct HistoryState); - hs->bank_url = bank_url; - hs->account_no = account_no; - hs->direction = direction; - hs->start_row_reference = start_row_reference; - hs->num_results = num_results; - hs->ascending = ascending; - hs->start_date = GNUNET_TIME_UNIT_FOREVER_ABS; - hs->end_date = GNUNET_TIME_UNIT_FOREVER_ABS; - - { - struct TALER_TESTING_Command cmd = { - .label = label, - .cls = hs, - .run = &history_run, - .cleanup = &history_cleanup, - .traits = &history_traits - }; - - return cmd; - } -} - - -/* end of testing_api_cmd_history.c */ diff --git a/src/bank-lib/testing_api_cmd_history_credit.c b/src/bank-lib/testing_api_cmd_history_credit.c new file mode 100644 index 000000000..5c2b34d06 --- /dev/null +++ b/src/bank-lib/testing_api_cmd_history_credit.c @@ -0,0 +1,758 @@ +/* + This file is part of TALER + Copyright (C) 2018-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + +*/ +/** + * @file bank-lib/testing_api_cmd_history.c + * @brief command to check the /history API from the bank. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "taler_exchange_service.h" +#include "taler_testing_lib.h" +#include "taler_testing_bank_lib.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" + + +/** + * State for a "history" CMD. + */ +struct HistoryState +{ + /** + * Base URL of the account offering the "history" operation. + */ + char *account_url; + + /** + * Reference to command defining the + * first row number we want in the result. + */ + const char *start_row_reference; + + /** + * How many rows we want in the result, _at most_, + * and ascending/descending. + */ + long long num_results; + + /** + * Handle to a pending "history" operation. + */ + struct TALER_BANK_CreditHistoryHandle *hh; + + /** + * Expected number of results (= rows). + */ + uint64_t results_obtained; + + /** + * Set to GNUNET_YES if the callback detects something + * unexpected. + */ + int failed; + +}; + + +/** + * Item in the transaction history, as reconstructed from the + * command history. + */ +struct History +{ + + /** + * Wire details. + */ + struct TALER_BANK_CreditDetails details; + + /** + * Serial ID of the wire transfer. + */ + uint64_t row_id; + + /** + * URL to free. + */ + char *url; +}; + + +/** + * Offer internal data to other commands. + * + * @param cls closure. + * @param ret[out] set to the wanted data. + * @param trait name of the trait. + * @param index index number of the traits to be returned. + * + * @return #GNUNET_OK on success + */ +static int +history_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + (void) cls; + (void) ret; + (void) trait; + (void) index; + /* Must define this function because some callbacks + * look for certain traits on _all_ the commands. */ + return GNUNET_SYSERR; +} + + +/** + * Free history @a h of length @a h_len. + * + * @param h history array to free. + * @param h_len number of entries in @a h. + */ +static void +free_history (struct History *h, + uint64_t h_len) +{ + for (uint64_t off = 0; off= hs->num_results; +} + + +/** + * This function constructs the list of history elements that + * interest the account number of the caller. It has two main + * loops: the first to figure out how many history elements have + * to be allocated, and the second to actually populate every + * element. + * + * @param is interpreter state (supposedly having the + * current CMD pointing at a "history" CMD). + * @param[out] rh history array to initialize. + * + * @return number of entries in @a rh. + */ +static uint64_t +build_history (struct TALER_TESTING_Interpreter *is, + struct History **rh) +{ + struct HistoryState *hs = is->commands[is->ip].cls; + uint64_t total; + struct History *h; + const struct TALER_TESTING_Command *add_incoming_cmd; + int inc; + unsigned int start; + unsigned int end; + + /** + * @var turns GNUNET_YES whenever either no 'start' value was + * given for the history query, or the given value is found + * in the list of all the CMDs. + */int ok; + const uint64_t *row_id_start = NULL; + + if (NULL != hs->start_row_reference) + { + TALER_LOG_INFO + ("`%s': start row given via reference `%s'\n", + TALER_TESTING_interpreter_get_current_label (is), + hs->start_row_reference); + add_incoming_cmd = TALER_TESTING_interpreter_lookup_command + (is, hs->start_row_reference); + GNUNET_assert (NULL != add_incoming_cmd); + GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_uint64 + (add_incoming_cmd, 0, &row_id_start)); + } + + GNUNET_assert (0 != hs->num_results); + if (0 == is->ip) + { + TALER_LOG_DEBUG ("Checking history at first CMD..\n"); + *rh = NULL; + return 0; + } + + /* AKA 'delta'. */ + if (hs->num_results > 0) + { + inc = 1; /* _inc_rement */ + start = 0; + end = is->ip - 1; + } + else + { + inc = -1; + start = is->ip - 1; + end = 0; + } + + total = 0; + ok = GNUNET_NO; + + if (NULL == row_id_start) + ok = GNUNET_YES; + + /* This loop counts how many commands _later than "start"_ belong + * to the history of the caller. This is stored in the @var total + * variable. */ + for (unsigned int off = start; off != end + inc; off += inc) + { + const struct TALER_TESTING_Command *pos = &is->commands[off]; + const uint64_t *row_id; + const char *credit_account; + const char *debit_account; + + /** + * The following command allows us to skip over those CMDs + * that do not offer a "row_id" trait. Such skipped CMDs are + * not interesting for building a history. + */if (GNUNET_OK != TALER_TESTING_get_trait_uint64 (pos, + 0, + &row_id)) + continue; + + /* Seek "/history" starting row. */ + if (NULL != row_id_start) + { + if (*row_id_start == *row_id) + { + /* Doesn't count, start is excluded from output. */ + total = 0; + ok = GNUNET_YES; + continue; + } + } + + /* when 'start' was _not_ given, then ok == GNUNET_YES */ + if (GNUNET_NO == ok) + continue; /* skip until we find the marker */ + + TALER_LOG_DEBUG ("Found first row\n"); + + if (build_history_hit_limit (total, + hs, + pos)) + { + TALER_LOG_DEBUG ("Hit history limit\n"); + break; + } + + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT + (pos, &credit_account)); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT + (pos, &debit_account)); + + TALER_LOG_INFO ("Potential history element:" + " %s->%s; my account: %s\n", + debit_account, + credit_account, + hs->account_url); + + if (0 == strcasecmp (hs->account_url, + credit_account)) + { + TALER_LOG_INFO ("+1 my history\n"); + total++; /* found matching record */ + } + } + + GNUNET_assert (GNUNET_YES == ok); + + if (0 == total) + { + TALER_LOG_DEBUG ("Checking history at first CMD.. (2)\n"); + *rh = NULL; + return 0; + } + + + GNUNET_assert (total < UINT_MAX); + h = GNUNET_new_array ((unsigned int) total, + struct History); + total = 0; + ok = GNUNET_NO; + if (NULL == row_id_start) + ok = GNUNET_YES; + + /** + * This loop _only_ populates the array of history elements. + */ + for (unsigned int off = start; off != end + inc; off += inc) + { + const struct TALER_TESTING_Command *pos = &is->commands[off]; + const uint64_t *row_id; + char *bank_hostname; + const char *credit_account; + const char *debit_account; + + if (GNUNET_OK != TALER_TESTING_GET_TRAIT_ROW_ID + (pos, &row_id)) + continue; + + if (NULL != row_id_start) + { + + if (*row_id_start == *row_id) + { + /** + * Warning: this zeroing is superfluous, as + * total doesn't get incremented if 'start' + * was given and couldn't be found. + */total = 0; + ok = GNUNET_YES; + continue; + } + } + + TALER_LOG_INFO ("Found first row (2)\n"); + + if (GNUNET_NO == ok) + { + TALER_LOG_INFO ("Skip on `%s'\n", + pos->label); + continue; /* skip until we find the marker */ + } + + if (build_history_hit_limit (total, + hs, + pos)) + { + TALER_LOG_INFO ("Hit history limit (2)\n"); + break; + } + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT + (pos, &credit_account)); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT + (pos, &debit_account)); + + TALER_LOG_INFO ("Potential history bit:" + " %s->%s; my account: %s\n", + debit_account, + credit_account, + hs->account_url); + + /** + * Discard transactions where the audited account played + * _both_ the credit and the debit roles, but _only if_ + * the audit goes on both directions.. This needs more + * explaination! + */if (0 == strcasecmp (hs->account_url, + credit_account)) + { + GNUNET_break (0); + continue; + } + + bank_hostname = strchr (hs->account_url, ':'); + GNUNET_assert (NULL != bank_hostname); + bank_hostname += 3; + + /* Next two blocks only put the 'direction' and 'banking' + * information. */ + + /* Asked for credit, and account got the credit. */ + if (0 == strcasecmp (hs->account_url, + credit_account)) + { + h[total].url = GNUNET_strdup (debit_account); + h[total].details.account_url = h[total].url; + } + + /* This block _completes_ the information of the current item, + * with amount / subject / exchange URL. */ + if (0 == strcasecmp (hs->account_url, + credit_account)) + { + const struct TALER_Amount *amount; + const struct TALER_ReservePublicKeyP *reserve_pub; + const char *account_url; + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_amount_obj + (pos, 0, &amount)); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_reserve_pub + (pos, 0, &reserve_pub)); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_url + (pos, 1, + &account_url)); + h[total].details.amount = *amount; + h[total].row_id = *row_id; + h[total].details.reserve_pub = *reserve_pub; + h[total].details.account_url = account_url; + TALER_LOG_INFO ("+1-bit of my history\n"); + total++; + } + } + *rh = h; + return total; +} + + +/** + * Compute how many results we expect to be returned for + * the current command at @a is. + * + * @param is the interpreter state to inspect. + * @return number of results expected. + */ +static uint64_t +compute_result_count (struct TALER_TESTING_Interpreter *is) +{ + uint64_t total; + struct History *h; + + total = build_history (is, &h); + free_history (h, total); + return total; +} + + +/** + * Check that the "/history" response matches the + * CMD whose offset in the list of CMDs is @a off. + * + * @param is the interpreter state. + * @param off the offset (of the CMD list) where the command + * to check is. + * @param dir the expected direction of the transaction. + * @param details the expected transaction details. + * + * @return #GNUNET_OK if the transaction is what we expect. + */ +static int +check_result (struct TALER_TESTING_Interpreter *is, + unsigned int off, + const struct TALER_BANK_CreditDetails *details) +{ + uint64_t total; + struct History *h; + + total = build_history (is, &h); + if (off >= total) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test says history has at most %u" + " results, but got result #%u to check\n", + (unsigned int) total, + off); + print_expected (h, + total, + off); + return GNUNET_SYSERR; + } + if ( (0 != GNUNET_memcmp (&h[off].details.reserve_pub, + &details->reserve_pub)) || + (0 != TALER_amount_cmp (&h[off].details.amount, + &details->amount)) || + (0 != strcasecmp (h[off].details.account_url, + details->account_url)) ) + { + GNUNET_break (0); + print_expected (h, + total, + off); + free_history (h, + total); + return GNUNET_SYSERR; + } + free_history (h, + total); + return GNUNET_OK; +} + + +/** + * This callback will (1) check that the HTTP response code + * is acceptable and (2) that the history is consistent. The + * consistency is checked by going through all the past CMDs, + * reconstructing then the expected history as of those, and + * finally check it against what the bank returned. + * + * @param cls closure. + * @param http_status HTTP response code, #MHD_HTTP_OK (200) + * for successful status request 0 if the bank's reply is + * bogus (fails to follow the protocol), + * #MHD_HTTP_NO_CONTENT if there are no more results; on + * success the last callback is always of this status + * (even if `abs(num_results)` were already returned). + * @param ec taler status code. + * @param dir direction of the transfer. + * @param row_id monotonically increasing counter corresponding to + * the transaction. + * @param details details about the wire transfer. + * @param json detailed response from the HTTPD, or NULL if + * reply was not in JSON. + * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration + */ +static int +history_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint64_t row_id, + const struct TALER_BANK_CreditDetails *details, + const json_t *json) +{ + struct TALER_TESTING_Interpreter *is = cls; + struct HistoryState *hs = is->commands[is->ip].cls; + + (void) row_id; + if (MHD_HTTP_OK != http_status) + { + hs->hh = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unwanted response code from /history: %u\n", + http_status); + TALER_TESTING_interpreter_fail (is); + return GNUNET_SYSERR; + } + if (NULL == details) + { + hs->hh = NULL; + if ( (hs->results_obtained != compute_result_count (is)) || + (GNUNET_YES == hs->failed) ) + { + uint64_t total; + struct History *h; + + GNUNET_break (0); + total = build_history (is, &h); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected history of length %llu, got %llu;" + " HTTP status code: %u/%d, failed: %d\n", + (unsigned long long) total, + (unsigned long long) hs->results_obtained, + http_status, + (int) ec, + hs->failed); + print_expected (h, + total, + UINT_MAX); + free_history (h, + total); + TALER_TESTING_interpreter_fail (is); + return GNUNET_SYSERR; + } + TALER_TESTING_interpreter_next (is); + return GNUNET_OK; + } + + /* check current element */ + if (GNUNET_OK != check_result (is, + hs->results_obtained, + details)) + { + char *acc; + + GNUNET_break (0); + acc = json_dumps (json, + JSON_COMPACT); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Result %u was `%s'\n", + (unsigned int) hs->results_obtained++, + acc); + if (NULL != acc) + free (acc); + hs->failed = GNUNET_YES; + return GNUNET_SYSERR; + } + hs->results_obtained++; + return GNUNET_OK; +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +history_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct HistoryState *hs = cls; + uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX; + const uint64_t *row_ptr; + + (void) cmd; + /* Get row_id from trait. */ + if (NULL != hs->start_row_reference) + { + const struct TALER_TESTING_Command *history_cmd; + + history_cmd = TALER_TESTING_interpreter_lookup_command + (is, hs->start_row_reference); + + if (NULL == history_cmd) + TALER_TESTING_FAIL (is); + + if (GNUNET_OK != + TALER_TESTING_get_trait_uint64 (history_cmd, + 0, + &row_ptr)) + TALER_TESTING_FAIL (is); + else + row_id = *row_ptr; + TALER_LOG_DEBUG ("row id (from trait) is %llu\n", + (unsigned long long) row_id); + } + + hs->hh = TALER_BANK_credit_history (is->ctx, + hs->account_url, + NULL, + row_id, + hs->num_results, + &history_cb, + is); + GNUNET_assert (NULL != hs->hh); +} + + +/** + * Free the state from a "history" CMD, and possibly cancel + * a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +history_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct HistoryState *hs = cls; + + (void) cmd; + if (NULL != hs->hh) + { + TALER_LOG_WARNING ("/history did not complete\n"); + TALER_BANK_credit_history_cancel (hs->hh); + } + GNUNET_free (hs->account_url); + GNUNET_free (hs); +} + + +/** + * Make a "history" CMD. + * + * @param label command label. + * @param account_url base URL of the account offering the "history" + * operation. + * @param start_row_reference reference to a command that can + * offer a row identifier, to be used as the starting row + * to accept in the result. + * @param num_results how many rows we want in the result. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_bank_credits (const char *label, + const char *account_url, + const char *start_row_reference, + long long num_results) +{ + struct HistoryState *hs; + + hs = GNUNET_new (struct HistoryState); + hs->account_url = GNUNET_strdup (account_url); + hs->start_row_reference = start_row_reference; + hs->num_results = num_results; + + { + struct TALER_TESTING_Command cmd = { + .label = label, + .cls = hs, + .run = &history_run, + .cleanup = &history_cleanup, + .traits = &history_traits + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_credit_history.c */ diff --git a/src/bank-lib/testing_api_cmd_history_debit.c b/src/bank-lib/testing_api_cmd_history_debit.c new file mode 100644 index 000000000..93f84da01 --- /dev/null +++ b/src/bank-lib/testing_api_cmd_history_debit.c @@ -0,0 +1,758 @@ +/* + This file is part of TALER + Copyright (C) 2018-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + +*/ + +/** + * @file bank-lib/testing_api_cmd_history.c + * @brief command to check the /history API from the bank. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "taler_exchange_service.h" +#include "taler_testing_lib.h" +#include "taler_testing_bank_lib.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" + + +/** + * State for a "history" CMD. + */ +struct HistoryState +{ + /** + * Base URL of the account offering the "history" operation. + */ + const char *account_url; + + /** + * Reference to command defining the + * first row number we want in the result. + */ + const char *start_row_reference; + + /** + * How many rows we want in the result, _at most_, + * and ascending/descending. + */ + long long num_results; + + /** + * Handle to a pending "history" operation. + */ + struct TALER_BANK_DebitHistoryHandle *hh; + + /** + * Expected number of results (= rows). + */ + uint64_t results_obtained; + + /** + * Set to GNUNET_YES if the callback detects something + * unexpected. + */ + int failed; + +}; + + +/** + * Item in the transaction history, as reconstructed from the + * command history. + */ +struct History +{ + + /** + * Wire details. + */ + struct TALER_BANK_DebitDetails details; + + /** + * Serial ID of the wire transfer. + */ + uint64_t row_id; + + /** + * URL to free. + */ + char *url; +}; + + +/** + * Offer internal data to other commands. + * + * @param cls closure. + * @param ret[out] set to the wanted data. + * @param trait name of the trait. + * @param index index number of the traits to be returned. + * + * @return #GNUNET_OK on success + */ +static int +history_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + (void) cls; + (void) ret; + (void) trait; + (void) index; + /* Must define this function because some callbacks + * look for certain traits on _all_ the commands. */ + return GNUNET_SYSERR; +} + + +/** + * Free history @a h of length @a h_len. + * + * @param h history array to free. + * @param h_len number of entries in @a h. + */ +static void +free_history (struct History *h, + uint64_t h_len) +{ + for (uint64_t off = 0; off= hs->num_results; +} + + +/** + * This function constructs the list of history elements that + * interest the account number of the caller. It has two main + * loops: the first to figure out how many history elements have + * to be allocated, and the second to actually populate every + * element. + * + * @param is interpreter state (supposedly having the + * current CMD pointing at a "history" CMD). + * @param[out] rh history array to initialize. + * + * @return number of entries in @a rh. + */ +static uint64_t +build_history (struct TALER_TESTING_Interpreter *is, + struct History **rh) +{ + struct HistoryState *hs = is->commands[is->ip].cls; + uint64_t total; + struct History *h; + const struct TALER_TESTING_Command *add_incoming_cmd; + int inc; + unsigned int start; + unsigned int end; + + /** + * @var turns GNUNET_YES whenever either no 'start' value was + * given for the history query, or the given value is found + * in the list of all the CMDs. + */int ok; + const uint64_t *row_id_start = NULL; + + if (NULL != hs->start_row_reference) + { + TALER_LOG_INFO + ("`%s': start row given via reference `%s'\n", + TALER_TESTING_interpreter_get_current_label (is), + hs->start_row_reference); + add_incoming_cmd = TALER_TESTING_interpreter_lookup_command + (is, hs->start_row_reference); + GNUNET_assert (NULL != add_incoming_cmd); + GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_uint64 + (add_incoming_cmd, 0, &row_id_start)); + } + + GNUNET_assert (0 != hs->num_results); + if (0 == is->ip) + { + TALER_LOG_DEBUG ("Checking history at first CMD..\n"); + *rh = NULL; + return 0; + } + + /* AKA 'delta'. */ + if (hs->num_results > 0) + { + inc = 1; /* _inc_rement */ + start = 0; + end = is->ip - 1; + } + else + { + inc = -1; + start = is->ip - 1; + end = 0; + } + + total = 0; + ok = GNUNET_NO; + + if (NULL == row_id_start) + ok = GNUNET_YES; + + /* This loop counts how many commands _later than "start"_ belong + * to the history of the caller. This is stored in the @var total + * variable. */ + for (unsigned int off = start; off != end + inc; off += inc) + { + const struct TALER_TESTING_Command *pos = &is->commands[off]; + const uint64_t *row_id; + const char *debit_account; + const char *credit_account; + + /** + * The following command allows us to skip over those CMDs + * that do not offer a "row_id" trait. Such skipped CMDs are + * not interesting for building a history. + */if (GNUNET_OK != TALER_TESTING_get_trait_uint64 (pos, + 0, + &row_id)) + continue; + + /* Seek "/history" starting row. */ + if (NULL != row_id_start) + { + if (*row_id_start == *row_id) + { + /* Doesn't count, start is excluded from output. */ + total = 0; + ok = GNUNET_YES; + continue; + } + } + + /* when 'start' was _not_ given, then ok == GNUNET_YES */ + if (GNUNET_NO == ok) + continue; /* skip until we find the marker */ + + TALER_LOG_DEBUG ("Found first row\n"); + + if (build_history_hit_limit (total, + hs, + pos)) + { + TALER_LOG_DEBUG ("Hit history limit\n"); + break; + } + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT + (pos, &debit_account)); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT + (pos, &credit_account)); + + TALER_LOG_INFO ("Potential history element:" + " %s->%s; my account: %s\n", + debit_account, + credit_account, + hs->account_url); + + if (0 == strcasecmp (hs->account_url, + debit_account)) + { + TALER_LOG_INFO ("+1 my history\n"); + total++; /* found matching record */ + } + } + + GNUNET_assert (GNUNET_YES == ok); + + if (0 == total) + { + TALER_LOG_DEBUG ("Checking history at first CMD.. (2)\n"); + *rh = NULL; + return 0; + } + + + GNUNET_assert (total < UINT_MAX); + h = GNUNET_new_array ((unsigned int) total, + struct History); + total = 0; + ok = GNUNET_NO; + if (NULL == row_id_start) + ok = GNUNET_YES; + + /** + * This loop _only_ populates the array of history elements. + */ + for (unsigned int off = start; off != end + inc; off += inc) + { + const struct TALER_TESTING_Command *pos = &is->commands[off]; + const uint64_t *row_id; + char *bank_hostname; + const char *credit_account; + const char *debit_account; + + if (GNUNET_OK != TALER_TESTING_GET_TRAIT_ROW_ID + (pos, &row_id)) + continue; + + if (NULL != row_id_start) + { + + if (*row_id_start == *row_id) + { + /** + * Warning: this zeroing is superfluous, as + * total doesn't get incremented if 'start' + * was given and couldn't be found. + */total = 0; + ok = GNUNET_YES; + continue; + } + } + + TALER_LOG_INFO ("Found first row (2)\n"); + + if (GNUNET_NO == ok) + { + TALER_LOG_INFO ("Skip on `%s'\n", + pos->label); + continue; /* skip until we find the marker */ + } + + if (build_history_hit_limit (total, + hs, + pos)) + { + TALER_LOG_INFO ("Hit history limit (2)\n"); + break; + } + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT + (pos, &debit_account)); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT + (pos, &credit_account)); + + TALER_LOG_INFO ("Potential history bit:" + " %s->%s; my account: %s\n", + debit_account, + credit_account, + hs->account_url); + + /** + * Discard transactions where the audited account played + * _both_ the debit and the debit roles, but _only if_ + * the audit goes on both directions.. This needs more + * explaination! + */if (0 == strcasecmp (hs->account_url, + debit_account)) + { + GNUNET_break (0); + continue; + } + + bank_hostname = strchr (hs->account_url, ':'); + GNUNET_assert (NULL != bank_hostname); + bank_hostname += 3; + + /* Next two blocks only put the 'direction' and 'banking' + * information. */ + + /* Asked for debit, and account got the debit. */ + if (0 == strcasecmp (hs->account_url, + debit_account)) + { + h[total].url = GNUNET_strdup (credit_account); + h[total].details.account_url = h[total].url; + } + + /* This block _completes_ the information of the current item, + * with amount / subject / exchange URL. */ + if (0 == strcasecmp (hs->account_url, + debit_account)) + { + const struct TALER_Amount *amount; + const struct TALER_WireTransferIdentifierRawP *wtid; + const char *account_url; + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_amount_obj + (pos, 0, &amount)); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_wtid + (pos, 0, &wtid)); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_url + (pos, 1, + &account_url)); + h[total].details.amount = *amount; + h[total].row_id = *row_id; + h[total].details.wtid = *wtid; + h[total].details.account_url = account_url; + TALER_LOG_INFO ("+1-bit of my history\n"); + total++; + } + } + *rh = h; + return total; +} + + +/** + * Compute how many results we expect to be returned for + * the current command at @a is. + * + * @param is the interpreter state to inspect. + * @return number of results expected. + */ +static uint64_t +compute_result_count (struct TALER_TESTING_Interpreter *is) +{ + uint64_t total; + struct History *h; + + total = build_history (is, &h); + free_history (h, total); + return total; +} + + +/** + * Check that the "/history" response matches the + * CMD whose offset in the list of CMDs is @a off. + * + * @param is the interpreter state. + * @param off the offset (of the CMD list) where the command + * to check is. + * @param dir the expected direction of the transaction. + * @param details the expected transaction details. + * + * @return #GNUNET_OK if the transaction is what we expect. + */ +static int +check_result (struct TALER_TESTING_Interpreter *is, + unsigned int off, + const struct TALER_BANK_DebitDetails *details) +{ + uint64_t total; + struct History *h; + + total = build_history (is, &h); + if (off >= total) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test says history has at most %u" + " results, but got result #%u to check\n", + (unsigned int) total, + off); + print_expected (h, + total, + off); + return GNUNET_SYSERR; + } + if ( (0 != GNUNET_memcmp (&h[off].details.wtid, + &details->wtid)) || + (0 != TALER_amount_cmp (&h[off].details.amount, + &details->amount)) || + (0 != strcasecmp (h[off].details.account_url, + details->account_url)) ) + { + GNUNET_break (0); + print_expected (h, + total, + off); + free_history (h, + total); + return GNUNET_SYSERR; + } + free_history (h, + total); + return GNUNET_OK; +} + + +/** + * This callback will (1) check that the HTTP response code + * is acceptable and (2) that the history is consistent. The + * consistency is checked by going through all the past CMDs, + * reconstructing then the expected history as of those, and + * finally check it against what the bank returned. + * + * @param cls closure. + * @param http_status HTTP response code, #MHD_HTTP_OK (200) + * for successful status request 0 if the bank's reply is + * bogus (fails to follow the protocol), + * #MHD_HTTP_NO_CONTENT if there are no more results; on + * success the last callback is always of this status + * (even if `abs(num_results)` were already returned). + * @param ec taler status code. + * @param dir direction of the transfer. + * @param row_id monotonically increasing counter corresponding to + * the transaction. + * @param details details about the wire transfer. + * @param json detailed response from the HTTPD, or NULL if + * reply was not in JSON. + * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration + */ +static int +history_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint64_t row_id, + const struct TALER_BANK_DebitDetails *details, + const json_t *json) +{ + struct TALER_TESTING_Interpreter *is = cls; + struct HistoryState *hs = is->commands[is->ip].cls; + + (void) row_id; + if (MHD_HTTP_OK != http_status) + { + hs->hh = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unwanted response code from /history: %u\n", + http_status); + TALER_TESTING_interpreter_fail (is); + return GNUNET_SYSERR; + } + if (NULL == details) + { + hs->hh = NULL; + if ( (hs->results_obtained != compute_result_count (is)) || + (GNUNET_YES == hs->failed) ) + { + uint64_t total; + struct History *h; + + GNUNET_break (0); + total = build_history (is, &h); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected history of length %llu, got %llu;" + " HTTP status code: %u/%d, failed: %d\n", + (unsigned long long) total, + (unsigned long long) hs->results_obtained, + http_status, + (int) ec, + hs->failed); + print_expected (h, + total, + UINT_MAX); + free_history (h, + total); + TALER_TESTING_interpreter_fail (is); + return GNUNET_SYSERR; + } + TALER_TESTING_interpreter_next (is); + return GNUNET_OK; + } + + /* check current element */ + if (GNUNET_OK != check_result (is, + hs->results_obtained, + details)) + { + char *acc; + + GNUNET_break (0); + acc = json_dumps (json, + JSON_COMPACT); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Result %u was `%s'\n", + (unsigned int) hs->results_obtained++, + acc); + if (NULL != acc) + free (acc); + hs->failed = GNUNET_YES; + return GNUNET_SYSERR; + } + hs->results_obtained++; + return GNUNET_OK; +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +history_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct HistoryState *hs = cls; + uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX; + const uint64_t *row_ptr; + + (void) cmd; + /* Get row_id from trait. */ + if (NULL != hs->start_row_reference) + { + const struct TALER_TESTING_Command *history_cmd; + + history_cmd = TALER_TESTING_interpreter_lookup_command + (is, hs->start_row_reference); + + if (NULL == history_cmd) + TALER_TESTING_FAIL (is); + + if (GNUNET_OK != + TALER_TESTING_get_trait_uint64 (history_cmd, + 0, + &row_ptr)) + TALER_TESTING_FAIL (is); + else + row_id = *row_ptr; + TALER_LOG_DEBUG ("row id (from trait) is %llu\n", + (unsigned long long) row_id); + } + + hs->hh = TALER_BANK_debit_history (is->ctx, + hs->account_url, + NULL, + row_id, + hs->num_results, + &history_cb, + is); + GNUNET_assert (NULL != hs->hh); +} + + +/** + * Free the state from a "history" CMD, and possibly cancel + * a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +history_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct HistoryState *hs = cls; + + (void) cmd; + if (NULL != hs->hh) + { + TALER_LOG_WARNING ("/history did not complete\n"); + TALER_BANK_debit_history_cancel (hs->hh); + } + GNUNET_free (hs); +} + + +/** + * Make a "history" CMD. + * + * @param label command label. + * @param account_url base URL of the account offering the "history" + * operation. + * @param start_row_reference reference to a command that can + * offer a row identifier, to be used as the starting row + * to accept in the result. + * @param num_results how many rows we want in the result. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_bank_debits (const char *label, + const char *account_url, + const char *start_row_reference, + long long num_results) +{ + struct HistoryState *hs; + + hs = GNUNET_new (struct HistoryState); + hs->account_url = account_url; + hs->start_row_reference = start_row_reference; + hs->num_results = num_results; + + { + struct TALER_TESTING_Command cmd = { + .label = label, + .cls = hs, + .run = &history_run, + .cleanup = &history_cleanup, + .traits = &history_traits + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_history_debit.c */ diff --git a/src/bank-lib/testing_api_cmd_reject.c b/src/bank-lib/testing_api_cmd_reject.c deleted file mode 100644 index 01c189f16..000000000 --- a/src/bank-lib/testing_api_cmd_reject.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2018 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 3, or - (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public - License along with TALER; see the file COPYING. If not, see - -*/ - -/** - * @file bank-lib/testing_api_cmd_reject.c - * @brief command to check the /reject API from the bank. - * @author Marcello Stanisci - */ - -#include "platform.h" -#include "taler_json_lib.h" -#include -#include "taler_exchange_service.h" -#include "taler_testing_lib.h" -#include "taler_fakebank_lib.h" -#include "taler_bank_service.h" -#include "taler_fakebank_lib.h" - - -/** - * State for a "reject" CMD. - */ -struct RejectState -{ - - /** - * Handle of a ongoing "reject" operation. - */ - struct TALER_BANK_RejectHandle *rh; - - /** - * Reference to any command that can offer a wire - * transfer "row id" and its credit account so as - * to give input data to the "reject" operation. - */ - const char *deposit_reference; - - /** - * Base URL of the bank implementing the "reject" - * operation. - */ - const char *bank_url; -}; - -/** - * Check that the response code from the "reject" opetation - * is acceptable, namely it equals "204 No Content". - * - * @param cls closure. - * @param http_status HTTP response code. - * @param ec taler-specific error code. - */ -static void -reject_cb (void *cls, - unsigned int http_status, - enum TALER_ErrorCode ec) -{ - struct TALER_TESTING_Interpreter *is = cls; - struct RejectState *rs = is->commands[is->ip].cls; - - rs->rh = NULL; - if (MHD_HTTP_NO_CONTENT != http_status) - { - GNUNET_break (0); - fprintf (stderr, - "Unexpected response code %u/%d\n", - http_status, - (int) ec); - TALER_TESTING_interpreter_fail (is); - return; - } - TALER_TESTING_interpreter_next (is); -} - - -/** - * Cleanup the state of a "reject" CMD, and possibly - * cancel a pending operation thereof. - * - * @param cls closure. - * @param cmd the command. - */ -static void -reject_cleanup (void *cls, - const struct TALER_TESTING_Command *cmd) -{ - struct RejectState *rs = cls; - - (void) cmd; - if (NULL != rs->rh) - { - TALER_LOG_WARNING ("/reject did not complete\n"); - TALER_BANK_reject_cancel (rs->rh); - } - GNUNET_free (rs); -} - - -/** - * Run the command. - * - * @param cls closure. - * @param cmd the command to execute. - * @param is the interpreter state. - */ -static void -reject_run (void *cls, - const struct TALER_TESTING_Command *cmd, - struct TALER_TESTING_Interpreter *is) -{ - struct RejectState *rs = cls; - const struct TALER_TESTING_Command *deposit_cmd; - const uint64_t *credit_account; - const uint64_t *row_id; - extern struct TALER_BANK_AuthenticationData AUTHS[]; - - (void) cmd; - deposit_cmd - = TALER_TESTING_interpreter_lookup_command (is, - rs->deposit_reference); - if (NULL == deposit_cmd) - TALER_TESTING_FAIL (is); - GNUNET_assert (GNUNET_OK == - TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT (deposit_cmd, - &credit_account)); - GNUNET_assert (GNUNET_OK == - TALER_TESTING_GET_TRAIT_ROW_ID (deposit_cmd, - &row_id)); - TALER_LOG_INFO ("Account %llu rejects deposit\n", - (unsigned long long) *credit_account); - rs->rh = TALER_BANK_reject (is->ctx, - rs->bank_url, - &AUTHS[*credit_account - 1], - *credit_account, - *row_id, - &reject_cb, - is); - GNUNET_assert (NULL != rs->rh); -} - - -/** - * Offer internal data from a "reject" CMD to other commands. - * - * @param cls closure. - * @param ret[out] result. - * @param trait name of the trait. - * @param index index number of the trait to return. - * - * @return #GNUNET_OK on success. - */ -static int -reject_traits (void *cls, - const void **ret, - const char *trait, - unsigned int index) -{ - struct RejectState *rs = cls; - struct TALER_TESTING_Trait traits[] = { - TALER_TESTING_make_trait_rejected (0, rs->deposit_reference), - TALER_TESTING_trait_end () - }; - - return TALER_TESTING_get_trait (traits, - ret, - trait, - index); -} - - -/** - * Create a "reject" CMD. - * - * @param label command label. - * @param bank_url base URL of the bank implementing the - * "reject" operation. - * @param deposit_reference reference to a command that will - * provide a "row id" and credit (bank) account to craft - * the "reject" request. - * @return the command. - */ -struct TALER_TESTING_Command -TALER_TESTING_cmd_bank_reject (const char *label, - const char *bank_url, - const char *deposit_reference) -{ - struct RejectState *rs; - - rs = GNUNET_new (struct RejectState); - rs->bank_url = bank_url; - rs->deposit_reference = deposit_reference; - - { - struct TALER_TESTING_Command cmd = { - .cls = rs, - .run = &reject_run, - .cleanup = &reject_cleanup, - .label = label, - .traits = &reject_traits - }; - - return cmd; - } -} - - -/* end of testing_api_cmd_reject.c */ -- cgit v1.2.3