From 042616899f89d38167632e3ff24b16469a27fbef Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 6 Dec 2017 19:24:00 +0100 Subject: largely fix #5077 --- src/auditor/taler-wire-auditor.c | 18 ++ src/bank-lib/Makefile.am | 3 +- src/bank-lib/bank_api_admin.c | 21 ++- src/bank-lib/bank_api_common.c | 30 ++- src/bank-lib/bank_api_common.h | 10 + src/bank-lib/bank_api_history.c | 91 ++++++--- src/bank-lib/bank_api_reject.c | 245 ++++++++++++++++++++++++ src/bank-lib/fakebank.c | 256 ++++++++++++++++++++++++-- src/bank-lib/test_bank_api.c | 23 +++ src/bank-lib/test_bank_api_with_fakebank.c | 37 ++++ src/bank-lib/test_bank_interpreter.c | 214 +++++++++++++++++---- src/bank-lib/test_bank_interpreter.h | 29 ++- src/exchange/taler-exchange-wirewatch.c | 8 + src/include/taler_bank_service.h | 77 +++++++- src/include/taler_error_codes.h | 28 +++ src/include/taler_fakebank_lib.h | 17 ++ src/include/taler_wire_plugin.h | 2 + src/wire/plugin_wire_test.c | 74 ++++++-- src/wire/test_wire_plugin_transactions_test.c | 2 + 19 files changed, 1073 insertions(+), 112 deletions(-) create mode 100644 src/bank-lib/bank_api_reject.c diff --git a/src/auditor/taler-wire-auditor.c b/src/auditor/taler-wire-auditor.c index 6d5085ed4..8ba38d39c 100644 --- a/src/auditor/taler-wire-auditor.c +++ b/src/auditor/taler-wire-auditor.c @@ -830,6 +830,7 @@ check_exchange_wire_out () * transactions). * * @param cls closure + * @param ec error code in case something went wrong * @param dir direction of the transfer * @param row_off identification of the position at which we are querying * @param row_off_size number of bytes in @a row_off @@ -838,6 +839,7 @@ check_exchange_wire_out () */ static int history_debit_cb (void *cls, + enum TALER_ErrorCode ec, enum TALER_BANK_Direction dir, const void *row_off, size_t row_off_size, @@ -848,6 +850,13 @@ history_debit_cb (void *cls, if (TALER_BANK_DIRECTION_NONE == dir) { + if (TALER_EC_NONE != ec) + { + /* FIXME: log properly to audit report! */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Error fetching history: %u!\n", + (unsigned int) ec); + } /* end of iteration, now check wire_out to see if it matches #out_map */ hh = NULL; @@ -1069,6 +1078,7 @@ conclude_credit_history () * transactions). * * @param cls closure + * @param ec error code in case something went wrong * @param dir direction of the transfer * @param row_off identification of the position at which we are querying * @param row_off_size number of bytes in @a row_off @@ -1077,6 +1087,7 @@ conclude_credit_history () */ static int history_credit_cb (void *cls, + enum TALER_ErrorCode ec, enum TALER_BANK_Direction dir, const void *row_off, size_t row_off_size, @@ -1087,6 +1098,13 @@ history_credit_cb (void *cls, if (TALER_BANK_DIRECTION_NONE == dir) { + if (TALER_EC_NONE != ec) + { + /* FIXME: log properly to audit report! */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Error fetching history: %u!\n", + (unsigned int) ec); + } /* end of operation */ hh = NULL; conclude_credit_history (); diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am index 7489fe795..da8dd9d39 100644 --- a/src/bank-lib/Makefile.am +++ b/src/bank-lib/Makefile.am @@ -17,7 +17,8 @@ libtalerbank_la_LDFLAGS = \ libtalerbank_la_SOURCES = \ bank_api_admin.c \ bank_api_common.c bank_api_common.h \ - bank_api_history.c + bank_api_history.c \ + bank_api_reject.c libtalerbank_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 cebd93436..4a1732ed1 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 GNUnet e.V. + 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 @@ -79,11 +79,13 @@ handle_admin_add_incoming_finished (void *cls, { struct TALER_BANK_AdminAddIncomingHandle *aai = cls; uint64_t serial_id = UINT64_MAX; + enum TALER_ErrorCode ec; aai->job = NULL; switch (response_code) { case 0: + ec = TALER_EC_INVALID_RESPONSE; break; case MHD_HTTP_OK: { @@ -100,29 +102,36 @@ handle_admin_add_incoming_finished (void *cls, { 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_ (json); break; case MHD_HTTP_FORBIDDEN: /* Access denied */ + ec = TALER_BANK_parse_ec_ (json); 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_ (json); 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_ (json); 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_ (json); break; default: /* unexpected response code */ @@ -130,11 +139,13 @@ handle_admin_add_incoming_finished (void *cls, "Unexpected response code %u\n", (unsigned int) response_code); GNUNET_break (0); + ec = TALER_BANK_parse_ec_ (json); response_code = 0; break; } aai->cb (aai->cb_cls, response_code, + ec, serial_id, json); TALER_BANK_admin_add_incoming_cancel (aai); @@ -151,7 +162,7 @@ handle_admin_add_incoming_finished (void *cls, * @param bank_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 wtid wire transfer identifier for the transfer + * @param subject 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) @@ -166,7 +177,7 @@ TALER_BANK_admin_add_incoming (struct GNUNET_CURL_Context *ctx, const char *bank_base_url, const struct TALER_BANK_AuthenticationData *auth, const char *exchange_base_url, - const struct TALER_WireTransferIdentifierRawP *wtid, + const char *subject, const struct TALER_Amount *amount, uint64_t debit_account_no, uint64_t credit_account_no, @@ -182,10 +193,10 @@ TALER_BANK_admin_add_incoming (struct GNUNET_CURL_Context *ctx, GNUNET_break (0); return NULL; } - admin_obj = json_pack ("{s:{s:s}, s:s, s:o, s:o, s:I, s:I}", + 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, - "wtid", GNUNET_JSON_from_data_auto (wtid), + "subject", subject, "amount", TALER_JSON_from_amount (amount), "debit_account", (json_int_t) debit_account_no, "credit_account", (json_int_t) credit_account_no); diff --git a/src/bank-lib/bank_api_common.c b/src/bank-lib/bank_api_common.c index 738d2a5b8..b4b197492 100644 --- a/src/bank-lib/bank_api_common.c +++ b/src/bank-lib/bank_api_common.c @@ -80,7 +80,7 @@ TALER_BANK_make_auth_header_ (const struct TALER_BANK_AuthenticationData *auth) authh = append (authh, "X-Taler-Bank-Password", auth->details.basic.password); - return authh; + return authh; } return NULL; } @@ -111,5 +111,33 @@ TALER_BANK_path_to_url_ (const char *u, } +/** + * Parse error code given in @a json. + * + * @param json the json to parse + * @return error code, or #TALER_EC_INVALID if not found + */ +enum TALER_ErrorCode +TALER_BANK_parse_ec_ (const json_t *json) +{ + uint32_t ec; + + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ("ec", + &ec), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return TALER_EC_INVALID; + } + return (enum TALER_ErrorCode) ec; +} + /* end of bank_api_common.c */ diff --git a/src/bank-lib/bank_api_common.h b/src/bank-lib/bank_api_common.h index 9d7a7800c..5d6578ce0 100644 --- a/src/bank-lib/bank_api_common.h +++ b/src/bank-lib/bank_api_common.h @@ -51,4 +51,14 @@ TALER_BANK_path_to_url_ (const char *u, const char *path); +/** + * Parse error code given in @a json. + * + * @param json the json to parse + * @return error code, or #TALER_EC_INVALID if not found + */ +enum TALER_ErrorCode +TALER_BANK_parse_ec_ (const json_t *json); + + #endif diff --git a/src/bank-lib/bank_api_history.c b/src/bank-lib/bank_api_history.c index e134f20f4..a6c7dceac 100644 --- a/src/bank-lib/bank_api_history.c +++ b/src/bank-lib/bank_api_history.c @@ -118,22 +118,37 @@ parse_account_history (struct TALER_BANK_HistoryHandle *hh, return GNUNET_SYSERR; } + if (0 == strcasecmp (sign, + "+")) + direction = TALER_BANK_DIRECTION_CREDIT; + else if (0 == strcasecmp (sign, + "-")) + direction = TALER_BANK_DIRECTION_DEBIT; + else if (0 == strcasecmp (sign, + "cancel+")) + direction = TALER_BANK_DIRECTION_CREDIT | TALER_BANK_DIRECTION_CANCEL; + else if (0 == strcasecmp (sign, + "cancel-")) + direction = TALER_BANK_DIRECTION_DEBIT | TALER_BANK_DIRECTION_CANCEL; + else + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (hist_spec); + return GNUNET_SYSERR; + } td.account_details = json_pack ("{s:s, s:s, s:I}", "type", "test", "bank_uri", hh->bank_base_url, "account_number", (json_int_t) other_account); - direction = (0 == strcasecmp (sign, - "+")) - ? TALER_BANK_DIRECTION_CREDIT - : TALER_BANK_DIRECTION_DEBIT; hh->hcb (hh->hcb_cls, MHD_HTTP_OK, + TALER_EC_NONE, direction, serial_id, &td, transaction); - GNUNET_JSON_parse_free (hist_spec); json_decref (td.account_details); + GNUNET_JSON_parse_free (hist_spec); } return GNUNET_OK; } @@ -153,6 +168,7 @@ handle_history_finished (void *cls, const json_t *json) { struct TALER_BANK_HistoryHandle *hh = cls; + enum TALER_ErrorCode ec; hh->job = NULL; switch (response_code) @@ -166,31 +182,38 @@ handle_history_finished (void *cls, { GNUNET_break_op (0); response_code = 0; + ec = TALER_EC_INVALID_RESPONSE; break; } response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */ 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_ (json); break; case MHD_HTTP_FORBIDDEN: /* Access denied */ + ec = TALER_BANK_parse_ec_ (json); 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_ (json); 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_ (json); 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_ (json); break; default: /* unexpected response code */ @@ -198,11 +221,13 @@ handle_history_finished (void *cls, "Unexpected response code %u\n", (unsigned int) response_code); GNUNET_break (0); + ec = TALER_BANK_parse_ec_ (json); response_code = 0; break; } hh->hcb (hh->hcb_cls, response_code, + ec, TALER_BANK_DIRECTION_NONE, 0LLU, NULL, @@ -243,6 +268,8 @@ TALER_BANK_history (struct GNUNET_CURL_Context *ctx, struct TALER_BANK_HistoryHandle *hh; CURL *eh; char *url; + const char *dir; + const char *can; if (0 == num_results) { @@ -254,36 +281,42 @@ TALER_BANK_history (struct GNUNET_CURL_Context *ctx, GNUNET_break (0); return NULL; } + + dir = NULL; + if (TALER_BANK_DIRECTION_BOTH == (TALER_BANK_DIRECTION_BOTH & direction)) + dir = "both"; + else if (TALER_BANK_DIRECTION_CREDIT == (TALER_BANK_DIRECTION_CREDIT & direction)) + dir = "credit"; + else if (TALER_BANK_DIRECTION_DEBIT == (TALER_BANK_DIRECTION_BOTH & direction)) + dir = "debit"; + if (NULL == dir) + { + GNUNET_break (0); + return NULL; + } + if (TALER_BANK_DIRECTION_CANCEL == (TALER_BANK_DIRECTION_CANCEL & direction)) + can = "show"; + else + can = "omit"; if (UINT64_MAX == start_row) { - if (TALER_BANK_DIRECTION_BOTH == direction) - GNUNET_asprintf (&url, - "/history?auth=basic&account_number=%llu&delta=%lld", - (unsigned long long) account_number, - (long long) num_results); - else - GNUNET_asprintf (&url, - "/history?auth=basic&account_number=%llu&delta=%lld&direction=%s", - (unsigned long long) account_number, - (long long) num_results, - (TALER_BANK_DIRECTION_CREDIT == direction) ? "credit" : "debit"); + GNUNET_asprintf (&url, + "/history?auth=basic&account_number=%llu&delta=%lld&direction=%s&cancelled=%s", + (unsigned long long) account_number, + (long long) num_results, + dir, + can); } else { - if (TALER_BANK_DIRECTION_BOTH == direction) - GNUNET_asprintf (&url, - "/history?auth=basic&account_number=%llu&delta=%lld&start=%llu", - (unsigned long long) account_number, - (long long) num_results, - (unsigned long long) start_row); - else - GNUNET_asprintf (&url, - "/history?auth=basic&account_number=%llu&delta=%lld&start=%llu&direction=%s", - (unsigned long long) account_number, - (long long) num_results, - (unsigned long long) start_row, - (TALER_BANK_DIRECTION_CREDIT == direction) ? "credit" : "debit"); + GNUNET_asprintf (&url, + "/history?auth=basic&account_number=%llu&delta=%lld&start=%llu&direction=%s&cancelled=%s", + (unsigned long long) account_number, + (long long) num_results, + (unsigned long long) start_row, + dir, + can); } hh = GNUNET_new (struct TALER_BANK_HistoryHandle); diff --git a/src/bank-lib/bank_api_reject.c b/src/bank-lib/bank_api_reject.c new file mode 100644 index 000000000..c630ccd52 --- /dev/null +++ b/src/bank-lib/bank_api_reject.c @@ -0,0 +1,245 @@ +/* + 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 json parsed JSON result, NULL on error + */ +static void +handle_reject_finished (void *cls, + long response_code, + const json_t *json) +{ + struct TALER_BANK_RejectHandle *rh = cls; + enum TALER_ErrorCode ec; + + 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_ (json); + break; + case MHD_HTTP_FORBIDDEN: + /* Access denied */ + ec = TALER_BANK_parse_ec_ (json); + 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_ (json); + 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_ (json); + 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_ (json); + 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_ (json); + 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, + "credit_account", (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_HTTPHEADER, + rh->authh)); + 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_add (ctx, + eh, + GNUNET_NO, + &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/fakebank.c b/src/bank-lib/fakebank.c index 268924151..67175721a 100644 --- a/src/bank-lib/fakebank.c +++ b/src/bank-lib/fakebank.c @@ -81,6 +81,11 @@ struct Transaction * Number of this transaction. */ uint64_t serial_id; + + /** + * Flag set if the transfer was rejected. + */ + int rejected; }; @@ -219,6 +224,31 @@ 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->serial_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()). @@ -287,6 +317,62 @@ 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 @@ -359,13 +445,13 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, break; } { - const char *wtid; + const char *subject; uint64_t debit_account; uint64_t credit_account; const char *base_url; struct TALER_Amount amount; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("wtid", &wtid), + GNUNET_JSON_spec_string ("subject", &subject), GNUNET_JSON_spec_uint64 ("debit_account", &debit_account), GNUNET_JSON_spec_uint64 ("credit_account", &credit_account), TALER_JSON_spec_amount ("amount", &amount), @@ -385,7 +471,7 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, debit_account, credit_account, &amount, - wtid, + subject, base_url); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Receiving incoming wire transfer: %llu->%llu from %s\n", @@ -433,6 +519,94 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, } +/** + * Handle incoming HTTP request for /reject. + * + * @param h the fakebank handle + * @param connection the connection + * @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) +{ + enum GNUNET_JSON_PostResult pr; + json_t *json; + struct MHD_Response *resp; + int ret; + int found; + + pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, + con_cls, + upload_data, + upload_data_size, + &json); + switch (pr) + { + case GNUNET_JSON_PR_OUT_OF_MEMORY: + GNUNET_break (0); + return MHD_NO; + case GNUNET_JSON_PR_CONTINUE: + return MHD_YES; + case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + GNUNET_break (0); + return MHD_NO; + case GNUNET_JSON_PR_JSON_INVALID: + GNUNET_break (0); + return MHD_NO; + case GNUNET_JSON_PR_SUCCESS: + break; + } + { + uint64_t serial_id; + uint64_t credit_account; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ("row_id", &serial_id), + GNUNET_JSON_spec_uint64 ("credit_account", &credit_account), + GNUNET_JSON_spec_end () + }; + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break (0); + json_decref (json); + return MHD_NO; + } + found = TALER_FAKEBANK_reject_transfer (h, + serial_id, + credit_account); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Rejected wire transfer #%llu (to %llu)\n", + (unsigned long long) serial_id, + (unsigned long long) credit_account); + } + json_decref (json); + + if (GNUNET_OK != found) + return create_bank_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_REJECT_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; +} + + /** * Handle incoming HTTP request for /history * @@ -451,6 +625,7 @@ handle_history (struct TALER_FAKEBANK_Handle *h, const char *start; const char *dir; const char *acc; + const char *cancelled; unsigned long long account_number; unsigned long long start_number; long long count; @@ -469,6 +644,9 @@ handle_history (struct TALER_FAKEBANK_Handle *h, dir = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "direction"); + cancelled = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "cancelled"); start = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "start"); @@ -496,7 +674,14 @@ handle_history (struct TALER_FAKEBANK_Handle *h, (1 != sscanf (start, "%llu", &start_number)) ) || - ( (NULL != dir) && + (NULL == dir) || + (NULL == cancelled) || + ( (0 != strcasecmp (cancelled, + "OMIT")) && + (0 != strcasecmp (cancelled, + "SHOW")) ) || + ( (0 != strcasecmp (dir, + "BOTH")) && (0 != strcasecmp (dir, "CREDIT")) && (0 != strcasecmp (dir, @@ -513,13 +698,40 @@ handle_history (struct TALER_FAKEBANK_Handle *h, dir, (unsigned long long) account_number, start); - if (NULL == dir) - direction = TALER_BANK_DIRECTION_BOTH; - else if (0 == strcasecmp (dir, - "CREDIT")) + if (0 == strcasecmp (dir, + "CREDIT")) + { direction = TALER_BANK_DIRECTION_CREDIT; - else + } + else if (0 == strcasecmp (dir, + "DEBIT")) + { direction = TALER_BANK_DIRECTION_DEBIT; + } + else if (0 == strcasecmp (dir, + "BOTH")) + { + direction = TALER_BANK_DIRECTION_BOTH; + } + else + { + GNUNET_assert (0); + return MHD_NO; + } + if (0 == strcasecmp (cancelled, + "OMIT")) + { + /* nothing */ + } else if (0 == strcasecmp (cancelled, + "SHOW")) + { + direction |= TALER_BANK_DIRECTION_CANCEL; + } + else + { + GNUNET_assert (0); + return MHD_NO; + } if (NULL == start) { if (count > 0) @@ -557,6 +769,7 @@ handle_history (struct TALER_FAKEBANK_Handle *h, { json_t *trans; char *subject; + const char *sign; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Found transaction over %s from %llu to %llu\n", @@ -564,10 +777,12 @@ handle_history (struct TALER_FAKEBANK_Handle *h, (unsigned long long) pos->debit_account, (unsigned long long) pos->credit_account); - if (! ( ( (account_number == pos->debit_account) && - (0 != (direction & TALER_BANK_DIRECTION_DEBIT)) ) || - ( (account_number == pos->credit_account) && - (0 != (direction & TALER_BANK_DIRECTION_CREDIT) ) ) ) ) + if ( (! ( ( (account_number == pos->debit_account) && + (0 != (direction & TALER_BANK_DIRECTION_DEBIT)) ) || + ( (account_number == pos->credit_account) && + (0 != (direction & TALER_BANK_DIRECTION_CREDIT) ) ) ) ) || + ( (0 == (direction & TALER_BANK_DIRECTION_CANCEL)) && + (GNUNET_YES == pos->rejected) ) ) { if (count > 0) pos = pos->next; @@ -580,11 +795,15 @@ handle_history (struct TALER_FAKEBANK_Handle *h, "%s %s", pos->subject, pos->exchange_base_url); + sign = + (account_number == pos->debit_account) + ? (pos->rejected ? "cancel-" : "-") + : (pos->rejected ? "cancel+" : "+"); trans = json_pack ("{s:I, s:o, s:o, s:s, s:I, s:s}", "row_id", (json_int_t) pos->serial_id, "date", GNUNET_JSON_from_time_abs (pos->date), "amount", TALER_JSON_from_amount (&pos->amount), - "sign", (account_number == pos->debit_account) ? "-" : "+", + "sign", sign, "counterpart", (json_int_t) ( (account_number == pos->debit_account) ? pos->credit_account : pos->debit_account), @@ -698,6 +917,15 @@ handle_mhd_request (void *cls, upload_data, upload_data_size, con_cls); + if ( (0 == strcasecmp (url, + "/reject")) && + (0 == strcasecmp (method, + MHD_HTTP_METHOD_POST)) ) + return handle_reject (h, + connection, + upload_data, + upload_data_size, + con_cls); if ( (0 == strcasecmp (url, "/history")) && (0 == strcasecmp (method, diff --git a/src/bank-lib/test_bank_api.c b/src/bank-lib/test_bank_api.c index 19d15ca61..80c462d81 100644 --- a/src/bank-lib/test_bank_api.c +++ b/src/bank-lib/test_bank_api.c @@ -50,6 +50,7 @@ run (void *cls) { .oc = TBI_OC_ADMIN_ADD_INCOMING, .label = "deposit-1", .details.admin_add_incoming.exchange_base_url = "https://exchange.net/", /* bogus */ + .details.admin_add_incoming.subject = "subject 1", .details.admin_add_incoming.expected_response_code = MHD_HTTP_OK, .details.admin_add_incoming.credit_account_no = 1, .details.admin_add_incoming.debit_account_no = 2, /* Ignored */ @@ -58,6 +59,7 @@ run (void *cls) { .oc = TBI_OC_ADMIN_ADD_INCOMING, .label = "deposit-2", .details.admin_add_incoming.exchange_base_url = "https://exchange.net/", /* bogus */ + .details.admin_add_incoming.subject = "subject 2", .details.admin_add_incoming.expected_response_code = MHD_HTTP_OK, .details.admin_add_incoming.credit_account_no = 1, .details.admin_add_incoming.debit_account_no = 2, /* Ignored */ @@ -89,6 +91,27 @@ run (void *cls) .details.history.direction = TALER_BANK_DIRECTION_DEBIT, .details.history.start_row_ref = "deposit-1", .details.history.num_results = 5 }, + { .oc = TBI_OC_REJECT, + .label = "reject-1", + .details.reject.cmd_ref = "deposit-1" }, + { .oc = TBI_OC_HISTORY, + .label = "history-r1", + .details.history.account_number = 2, + .details.history.direction = TALER_BANK_DIRECTION_CREDIT, + .details.history.start_row_ref = NULL, + .details.history.num_results = 5 }, + { .oc = TBI_OC_HISTORY, + .label = "history-r2", + .details.history.account_number = 2, + .details.history.direction = TALER_BANK_DIRECTION_DEBIT, + .details.history.start_row_ref = NULL, + .details.history.num_results = 5 }, + { .oc = TBI_OC_HISTORY, + .label = "history-r3", + .details.history.account_number = 2, + .details.history.direction = TALER_BANK_DIRECTION_BOTH | TALER_BANK_DIRECTION_CANCEL, + .details.history.start_row_ref = NULL, + .details.history.num_results = 5 }, { .oc = TBI_OC_END } }; diff --git a/src/bank-lib/test_bank_api_with_fakebank.c b/src/bank-lib/test_bank_api_with_fakebank.c index e16c3a915..440851b15 100644 --- a/src/bank-lib/test_bank_api_with_fakebank.c +++ b/src/bank-lib/test_bank_api_with_fakebank.c @@ -48,6 +48,7 @@ run (void *cls) /* Add EUR:5.01 to account 1 */ { .oc = TBI_OC_ADMIN_ADD_INCOMING, .label = "debit-1", + .details.admin_add_incoming.subject = "subject 1", .details.admin_add_incoming.expected_response_code = MHD_HTTP_OK, .details.admin_add_incoming.credit_account_no = 1, .details.admin_add_incoming.debit_account_no = 2, @@ -68,6 +69,7 @@ run (void *cls) .details.history.num_results = 5 }, { .oc = TBI_OC_ADMIN_ADD_INCOMING, .label = "debit-2", + .details.admin_add_incoming.subject = "subject 2", .details.admin_add_incoming.expected_response_code = MHD_HTTP_OK, .details.admin_add_incoming.credit_account_no = 3, .details.admin_add_incoming.debit_account_no = 2, @@ -75,6 +77,7 @@ run (void *cls) .details.admin_add_incoming.amount = "KUDOS:3.21" }, { .oc = TBI_OC_ADMIN_ADD_INCOMING, .label = "credit-2", + .details.admin_add_incoming.subject = "credit 2", .details.admin_add_incoming.expected_response_code = MHD_HTTP_OK, .details.admin_add_incoming.credit_account_no = 2, .details.admin_add_incoming.debit_account_no = 3, @@ -105,6 +108,40 @@ run (void *cls) /* check transfer list is now empty */ { .oc = TBI_OC_EXPECT_TRANSFERS_EMPTY, .label = "expect-empty" }, + /* Add EUR:5.01 to account 1 */ + { .oc = TBI_OC_ADMIN_ADD_INCOMING, + .label = "credit-for-reject-1", + .details.admin_add_incoming.subject = "subject 3", + .details.admin_add_incoming.expected_response_code = MHD_HTTP_OK, + .details.admin_add_incoming.credit_account_no = 1, + .details.admin_add_incoming.debit_account_no = 2, + .details.admin_add_incoming.exchange_base_url = "https://exchange.net/", + .details.admin_add_incoming.amount = "KUDOS:5.01" }, + { .oc = TBI_OC_REJECT, + .label = "reject-1", + .details.reject.cmd_ref = "credit-for-reject-1" }, + { .oc = TBI_OC_HISTORY, + .label = "history-r1", + .details.history.account_number = 1, + .details.history.direction = TALER_BANK_DIRECTION_BOTH, + /* range is exclusive, and everything up to and including "credit-2" + was already killed via TBI_OC_EXPECT_TRANSFER and thus won't show + in the history. So to see the rejected transfer, we need to start + looking after "credit-2" */ + .details.history.start_row_ref = NULL, + .details.history.num_results = 5 }, + { .oc = TBI_OC_HISTORY, + .label = "history-r1c", + .details.history.account_number = 1, + .details.history.direction = TALER_BANK_DIRECTION_BOTH | TALER_BANK_DIRECTION_CANCEL, + .details.history.start_row_ref = NULL, + .details.history.num_results = 5 }, + { .oc = TBI_OC_EXPECT_TRANSFER, + .label = "expect-credit-reject-1", + .details.expect_transfer.cmd_ref = "credit-for-reject-1" }, + /* check transfer list is now empty */ + { .oc = TBI_OC_EXPECT_TRANSFERS_EMPTY, + .label = "expect-empty-2" }, { .oc = TBI_OC_END } }; diff --git a/src/bank-lib/test_bank_interpreter.c b/src/bank-lib/test_bank_interpreter.c index f5aee8ee6..fac0ab692 100644 --- a/src/bank-lib/test_bank_interpreter.c +++ b/src/bank-lib/test_bank_interpreter.c @@ -110,7 +110,6 @@ static const struct TBI_Command * find_command (const struct InterpreterState *is, const char *label) { - unsigned int i; const struct TBI_Command *cmd; if (NULL == label) @@ -119,7 +118,7 @@ find_command (const struct InterpreterState *is, "Attempt to lookup command for empty label\n"); return NULL; } - for (i=0;TBI_OC_END != (cmd = &is->commands[i])->oc;i++) + for (unsigned int i=0;TBI_OC_END != (cmd = &is->commands[i])->oc;i++) if ( (NULL != cmd->label) && (0 == strcmp (cmd->label, label)) ) @@ -131,6 +130,63 @@ find_command (const struct InterpreterState *is, } +/** + * Test if the /admin/add/incoming transaction at offset @a off + * has been /rejected. + * + * @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 InterpreterState *is, + unsigned int off) +{ + const struct TBI_Command *cmd = &is->commands[off]; + + for (unsigned int i=0;iip;i++) + { + const struct TBI_Command *c = &is->commands[i]; + + if (TBI_OC_REJECT != c->oc) + continue; + if (0 == strcmp (c->details.reject.cmd_ref, + cmd->label)) + return GNUNET_YES; + } + return GNUNET_NO; +} + + +/** + * Test if the /admin/add/incoming transaction at offset @a off + * has been #TBI_OC_EXPECT_TRANSFER treated, and thus been + * forgotten by the fakebank. + * + * @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_deleted_by_expected (struct InterpreterState *is, + unsigned int off) +{ + const struct TBI_Command *cmd = &is->commands[off]; + + for (unsigned int i=0;iip;i++) + { + const struct TBI_Command *c = &is->commands[i]; + + if (TBI_OC_EXPECT_TRANSFER != c->oc) + continue; + if (0 == strcmp (c->details.expect_transfer.cmd_ref, + cmd->label)) + return GNUNET_YES; + } + return GNUNET_NO; +} + + /** * Item in the transaction history, as reconstructed from the * command history. @@ -214,6 +270,7 @@ build_history (struct InterpreterState *is, for (unsigned int off = start;off != end + inc; off += inc) { const struct TBI_Command *pos = &is->commands[off]; + int cancelled; if (TBI_OC_ADMIN_ADD_INCOMING != pos->oc) continue; @@ -229,6 +286,15 @@ build_history (struct InterpreterState *is, continue; /* skip until we find the marker */ if (total >= cmd->details.history.num_results * inc) break; /* hit limit specified by command */ + if (GNUNET_YES == + test_deleted_by_expected (is, + off)) + continue; + cancelled = test_cancelled (is, + off); + if ( (GNUNET_YES == cancelled) && + (0 == (cmd->details.history.direction & TALER_BANK_DIRECTION_CANCEL)) ) + continue; if ( ( (0 != (cmd->details.history.direction & TALER_BANK_DIRECTION_CREDIT)) && (cmd->details.history.account_number == pos->details.admin_add_incoming.credit_account_no)) || @@ -253,6 +319,7 @@ build_history (struct InterpreterState *is, for (unsigned int off = start;off != end + inc; off += inc) { const struct TBI_Command *pos = &is->commands[off]; + int cancelled; if (TBI_OC_ADMIN_ADD_INCOMING != pos->oc) continue; @@ -268,6 +335,10 @@ build_history (struct InterpreterState *is, continue; /* skip until we find the marker */ if (total >= cmd->details.history.num_results * inc) break; /* hit limit specified by command */ + if (GNUNET_YES == + test_deleted_by_expected (is, + off)) + continue; if ( ( (0 != (cmd->details.history.direction & TALER_BANK_DIRECTION_CREDIT)) && (cmd->details.history.account_number == @@ -280,11 +351,19 @@ build_history (struct InterpreterState *is, continue; } + cancelled = test_cancelled (is, + off); + if ( (GNUNET_YES == cancelled) && + (0 == (cmd->details.history.direction & TALER_BANK_DIRECTION_CANCEL)) ) + continue; + if ( (0 != (cmd->details.history.direction & TALER_BANK_DIRECTION_CREDIT)) && (cmd->details.history.account_number == pos->details.admin_add_incoming.credit_account_no)) { h[total].direction = TALER_BANK_DIRECTION_CREDIT; + if (GNUNET_YES == cancelled) + h[total].direction |= TALER_BANK_DIRECTION_CANCEL; h[total].details.account_details = json_pack ("{s:s, s:s, s:I}", "type", @@ -300,6 +379,8 @@ build_history (struct InterpreterState *is, pos->details.admin_add_incoming.debit_account_no)) { h[total].direction = TALER_BANK_DIRECTION_DEBIT; + if (GNUNET_YES == cancelled) + h[total].direction |= TALER_BANK_DIRECTION_CANCEL; h[total].details.account_details = json_pack ("{s:s, s:s, s:I}", "type", @@ -323,17 +404,10 @@ build_history (struct InterpreterState *is, /* h[total].execution_date; // unknown here */ h[total].serial_id = pos->details.admin_add_incoming.serial_id; - { - char *ws; - - ws = GNUNET_STRINGS_data_to_string_alloc (&pos->details.admin_add_incoming.wtid, - sizeof (struct TALER_WireTransferIdentifierRawP)); - GNUNET_asprintf (&h[total].details.wire_transfer_subject, - "%s %s", - ws, - pos->details.admin_add_incoming.exchange_base_url); - GNUNET_free (ws); - } + GNUNET_asprintf (&h[total].details.wire_transfer_subject, + "%s %s", + pos->details.admin_add_incoming.subject, + pos->details.admin_add_incoming.exchange_base_url); total++; } } @@ -488,18 +562,34 @@ static void interpreter_run (void *cls); +/** + * Run the next command. + * + * @param is interpreter to progress + */ +static void +next (struct InterpreterState *is) +{ + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + /** * Function called upon completion of our /admin/add/incoming request. * * @param cls closure with the interpreter state * @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) + * @param ec taler status code * @param serial_id unique ID of the wire transfer in the bank's records; UINT64_MAX on error * @param json detailed response from the HTTPD, or NULL if reply was not in JSON */ static void add_incoming_cb (void *cls, unsigned int http_status, + enum TALER_ErrorCode ec, uint64_t serial_id, const json_t *json) { @@ -522,9 +612,7 @@ add_incoming_cb (void *cls, fail (is); return; } - is->ip++; - is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, - is); + next (is); } @@ -538,6 +626,7 @@ add_incoming_cb (void *cls, * #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 serial_id monotonically increasing counter corresponding to the transaction * @param details details about the wire transfer @@ -546,6 +635,7 @@ add_incoming_cb (void *cls, static void history_cb (void *cls, unsigned int http_status, + enum TALER_ErrorCode ec, enum TALER_BANK_Direction dir, uint64_t serial_id, const struct TALER_BANK_TransferDetails *details, @@ -580,9 +670,7 @@ history_cb (void *cls, fail (is); return; } - is->ip++; - is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, - is); + next (is); return; } if (GNUNET_OK != @@ -612,6 +700,38 @@ history_cb (void *cls, } +/** + * Callbacks of this type are used to serve the result of asking + * the bank to reject an incoming wire transfer. + * + * @param cls closure + * @param http_status HTTP response code, #MHD_HTTP_NO_CONTENT (204) for successful status request; + * #MHD_HTTP_NOT_FOUND if the rowid is unknown; + * 0 if the bank's reply is bogus (fails to follow the protocol), + * @param ec detailed error code + */ +static void +reject_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec) +{ + struct InterpreterState *is = cls; + struct TBI_Command *cmd = &is->commands[is->ip]; + + cmd->details.reject.rh = NULL; + if (MHD_HTTP_NO_CONTENT != http_status) + { + GNUNET_break (0); + fprintf (stderr, + "Unexpected response code %u:\n", + http_status); + fail (is); + return; + } + next (is); +} + + /** * Run the main interpreter loop that performs bank operations. * @@ -658,15 +778,13 @@ interpreter_run (void *cls) fail (is); return; } - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, - &cmd->details.admin_add_incoming.wtid, - sizeof (cmd->details.admin_add_incoming.wtid)); + GNUNET_break (NULL != cmd->details.admin_add_incoming.subject); cmd->details.admin_add_incoming.aih = TALER_BANK_admin_add_incoming (is->ctx, "http://localhost:8080", &auth, cmd->details.admin_add_incoming.exchange_base_url, - &cmd->details.admin_add_incoming.wtid, + cmd->details.admin_add_incoming.subject, &amount, cmd->details.admin_add_incoming.debit_account_no, cmd->details.admin_add_incoming.credit_account_no, @@ -722,7 +840,6 @@ interpreter_run (void *cls) &amount)); { char *subject; - char *expect; if (GNUNET_OK != TALER_FAKEBANK_check (is->fakebank, @@ -736,22 +853,17 @@ interpreter_run (void *cls) fail (is); return; } - expect = GNUNET_STRINGS_data_to_string_alloc (&ref->details.admin_add_incoming.wtid, - sizeof (ref->details.admin_add_incoming.wtid)); - if (0 != strcmp (subject, expect)) + if (0 != strcmp (ref->details.admin_add_incoming.subject, + subject)) { - GNUNET_free (expect); GNUNET_free (subject); GNUNET_break (0); fail (is); return; } GNUNET_free (subject); - GNUNET_free (expect); } - is->ip++; - is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, - is); + next (is); return; case TBI_OC_EXPECT_TRANSFERS_EMPTY: if (GNUNET_OK != TALER_FAKEBANK_check_empty (is->fakebank)) @@ -760,9 +872,27 @@ interpreter_run (void *cls) fail (is); return; } - is->ip++; - is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, - is); + next (is); + return; + case TBI_OC_REJECT: + ref = find_command (is, + cmd->details.reject.cmd_ref); + GNUNET_assert (NULL != ref); + GNUNET_assert (TBI_OC_ADMIN_ADD_INCOMING == ref->oc); + cmd->details.reject.rh + = TALER_BANK_reject (is->ctx, + "http://localhost:8080", + &auth, + ref->details.admin_add_incoming.credit_account_no, + ref->details.admin_add_incoming.serial_id, + &reject_cb, + is); + if (NULL == cmd->details.reject.rh) + { + GNUNET_break (0); + fail (is); + return; + } return; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -802,7 +932,6 @@ do_shutdown (void *cls) { struct InterpreterState *is = cls; struct TBI_Command *cmd; - unsigned int i; if (NULL != is->timeout_task) { @@ -810,7 +939,7 @@ do_shutdown (void *cls) is->timeout_task = NULL; } - for (i=0;TBI_OC_END != (cmd = &is->commands[i])->oc;i++) + for (unsigned int i=0;TBI_OC_END != (cmd = &is->commands[i])->oc;i++) { switch (cmd->oc) { @@ -843,6 +972,17 @@ do_shutdown (void *cls) break; case TBI_OC_EXPECT_TRANSFERS_EMPTY: break; + case TBI_OC_REJECT: + if (NULL != cmd->details.reject.rh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_BANK_reject_cancel (cmd->details.reject.rh); + cmd->details.reject.rh = NULL; + } + break; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown instruction %d at %u (%s)\n", diff --git a/src/bank-lib/test_bank_interpreter.h b/src/bank-lib/test_bank_interpreter.h index d4e9c1a6c..ed2a00df7 100644 --- a/src/bank-lib/test_bank_interpreter.h +++ b/src/bank-lib/test_bank_interpreter.h @@ -57,7 +57,12 @@ enum TBI_OpCode /** * Expect that we have exhaustively gone over all transfers at fakebank. */ - TBI_OC_EXPECT_TRANSFERS_EMPTY + TBI_OC_EXPECT_TRANSFERS_EMPTY, + + /** + * Reject incoming transfer. + */ + TBI_OC_REJECT }; @@ -110,10 +115,9 @@ struct TBI_Command const char *exchange_base_url; /** - * Wire transfer identifier to use. Initialized to - * a random value. + * Wire transfer subject to use. */ - struct TALER_WireTransferIdentifierRawP wtid; + const char *subject; /** * Which response code do we expect for this command? @@ -186,6 +190,23 @@ struct TBI_Command } expect_transfer; + /** + * Execute /reject operation. + */ + struct { + + /** + * Reference to the matching transfer that is now to be rejected. + */ + const char *cmd_ref; + + /** + * Set to the API's handle during the operation. + */ + struct TALER_BANK_RejectHandle *rh; + + } reject; + } details; }; diff --git a/src/exchange/taler-exchange-wirewatch.c b/src/exchange/taler-exchange-wirewatch.c index ca7f3bad3..7fbc0b883 100644 --- a/src/exchange/taler-exchange-wirewatch.c +++ b/src/exchange/taler-exchange-wirewatch.c @@ -282,6 +282,7 @@ reject_cb (void *cls, * the bank for the transaction history. * * @param cls closure with the `struct TALER_EXCHANGEDB_Session *` + * @param ec taler error code * @param dir direction of the transfer * @param row_off identification of the position at which we are querying * @param row_off_size number of bytes in @a row_off @@ -290,6 +291,7 @@ reject_cb (void *cls, */ static int history_cb (void *cls, + enum TALER_ErrorCode ec, enum TALER_BANK_Direction dir, const void *row_off, size_t row_off_size, @@ -303,6 +305,12 @@ history_cb (void *cls, { hh = NULL; + if (TALER_EC_NONE != ec) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Error fetching history: %u!\n", + (unsigned int) ec); + } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "End of list. Committing progress!\n"); qs = db_plugin->commit (db_plugin->cls, diff --git a/src/include/taler_bank_service.h b/src/include/taler_bank_service.h index 246174df9..6349326a1 100644 --- a/src/include/taler_bank_service.h +++ b/src/include/taler_bank_service.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015, 2016, 2017 GNUnet e.V. & Inria + 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 Affero General Public License as published by the Free Software @@ -25,6 +25,7 @@ #include #include #include "taler_util.h" +#include "taler_error_codes.h" /** @@ -98,12 +99,14 @@ struct TALER_BANK_AdminAddIncomingHandle; * @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) + * @param ec detailed error code * @param serial_id unique ID of the wire transfer in the bank's records; UINT64_MAX on error * @param json detailed response from the HTTPD, or NULL if reply was not in JSON */ typedef void (*TALER_BANK_AdminAddIncomingResultCallback) (void *cls, unsigned int http_status, + enum TALER_ErrorCode ec, uint64_t serial_id, const json_t *json); @@ -118,7 +121,7 @@ typedef void * @param bank_base_url URL of the bank (used to execute this request) * @param auth authentication data to use * @param exchange_base_url base URL of the exchange (for tracking) - * @param wtid wire transfer identifier for the transfer + * @param subject 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) @@ -133,7 +136,7 @@ TALER_BANK_admin_add_incoming (struct GNUNET_CURL_Context *ctx, const char *bank_base_url, const struct TALER_BANK_AuthenticationData *auth, const char *exchange_base_url, - const struct TALER_WireTransferIdentifierRawP *wtid, + const char *subject, const struct TALER_Amount *amount, uint64_t debit_account_no, uint64_t credit_account_no, @@ -174,7 +177,15 @@ enum TALER_BANK_Direction { /** * Return both types of transactions. */ - TALER_BANK_DIRECTION_BOTH = (TALER_BANK_DIRECTION_CREDIT | TALER_BANK_DIRECTION_DEBIT) + TALER_BANK_DIRECTION_BOTH = (TALER_BANK_DIRECTION_CREDIT | TALER_BANK_DIRECTION_DEBIT), + + /** + * Bit mask that is applied to view transactions that have been + * cancelled. The bit is set for cancelled transactions that are + * returned from /history, and must also be set in order for + * cancelled transactions to show up in the /history. + */ + TALER_BANK_DIRECTION_CANCEL = 4 }; @@ -222,6 +233,7 @@ struct TALER_BANK_TransferDetails * #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 detailed error code * @param dir direction of the transfer * @param serial_id monotonically increasing counter corresponding to the transaction * @param details details about the wire transfer @@ -230,6 +242,7 @@ struct TALER_BANK_TransferDetails typedef void (*TALER_BANK_HistoryResultCallback) (void *cls, unsigned int http_status, + enum TALER_ErrorCode ec, enum TALER_BANK_Direction dir, uint64_t serial_id, const struct TALER_BANK_TransferDetails *details, @@ -277,5 +290,61 @@ void TALER_BANK_history_cancel (struct TALER_BANK_HistoryHandle *hh); +/** + * Handle for #TALER_BANK_reject() operation. + */ +struct TALER_BANK_RejectHandle; + + +/** + * Callbacks of this type are used to serve the result of asking + * the bank to reject an incoming wire transfer. + * + * @param cls closure + * @param http_status HTTP response code, #MHD_HTTP_NO_CONTENT (204) for successful status request; + * #MHD_HTTP_NOT_FOUND if the rowid is unknown; + * 0 if the bank's reply is bogus (fails to follow the protocol), + * @param ec detailed error code + */ +typedef void +(*TALER_BANK_RejectResultCallback) (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec); + + +/** + * 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); + + +/** + * 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); + #endif /* _TALER_BANK_SERVICE_H */ diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h index 6047e1643..e8a3aaee8 100644 --- a/src/include/taler_error_codes.h +++ b/src/include/taler_error_codes.h @@ -1488,6 +1488,34 @@ enum TALER_ErrorCode */ TALER_EC_TEST_RSA_SIGN_ERROR = 4005, + /* *************** Taler BANK/FAKEBANK error codes *************** */ + + + /** + * Authentication failed for the /admin/add/incoming request. + * Returned with a status code of MHD_HTTP_FORBIDDEN. + */ + TALER_EC_BANK_TRANSFER_NOT_AUHTORIZED = 4101, + + /** + * Authentication failed for the /history request. + * Returned with a status code of MHD_HTTP_FORBIDDEN. + */ + TALER_EC_BANK_HISTORY_NOT_AUHTORIZED = 4151, + + /** + * The bank could not find the wire transfer that was supposed to + * be rejected. + * Returned with a status code of MHD_HTTP_NOT_FOUND. + */ + TALER_EC_BANK_REJECT_NOT_FOUND = 4250, + + /** + * Authentication failed for the /reject request. + * Returned with a status code of MHD_HTTP_FORBIDDEN. + */ + TALER_EC_BANK_REJECT_NOT_AUHTORIZED = 4251, + /** * End of error code range. diff --git a/src/include/taler_fakebank_lib.h b/src/include/taler_fakebank_lib.h index 3df1e6095..5e0d7d1d1 100644 --- a/src/include/taler_fakebank_lib.h +++ b/src/include/taler_fakebank_lib.h @@ -88,6 +88,8 @@ TALER_FAKEBANK_make_transfer (struct TALER_FAKEBANK_Handle *h, * to the transfer identifier and remove the transaction from the * list. If the transaction was not recorded, return #GNUNET_SYSERR. * + * Rejected transfers do NOT show with "check". + * * @param h bank instance * @param want_amount transfer amount desired * @param want_debit account that should have been debited @@ -106,6 +108,21 @@ TALER_FAKEBANK_check (struct TALER_FAKEBANK_Handle *h, char **subject); +/** + * 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); + + /** * Stop running the fake bank. * diff --git a/src/include/taler_wire_plugin.h b/src/include/taler_wire_plugin.h index 6e355baf6..c0e29609f 100644 --- a/src/include/taler_wire_plugin.h +++ b/src/include/taler_wire_plugin.h @@ -83,6 +83,7 @@ struct TALER_WIRE_TransferDetails * the bank for the transaction history. * * @param cls closure + * @param ec taler error code * @param dir direction of the transfer * @param row_off identification of the position at which we are querying * @param row_off_size number of bytes in @a row_off @@ -91,6 +92,7 @@ struct TALER_WIRE_TransferDetails */ typedef int (*TALER_WIRE_HistoryResultCallback) (void *cls, + enum TALER_ErrorCode ec, enum TALER_BANK_Direction dir, const void *row_off, size_t row_off_size, diff --git a/src/wire/plugin_wire_test.c b/src/wire/plugin_wire_test.c index 1a7443b8a..fa6ba3d5a 100644 --- a/src/wire/plugin_wire_test.c +++ b/src/wire/plugin_wire_test.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2016 GNUnet e.V. & Inria + Copyright (C) 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 @@ -553,12 +553,14 @@ test_prepare_wire_transfer (void *cls, * @param cls closure with the `struct TALER_WIRE_ExecuteHandle` * @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) + * @param ec error code from the bank * @param serial_id unique ID of the wire transfer in the bank's records; UINT64_MAX on error * @param json detailed response from the HTTPD, or NULL if reply was not JSON */ static void execute_cb (void *cls, unsigned int http_status, + enum TALER_ErrorCode ec, uint64_t serial_id, const json_t *json) { @@ -578,13 +580,15 @@ execute_cb (void *cls, } if (NULL != emsg) GNUNET_asprintf (&s, - "%u (%s)", + "%u/%u (%s)", http_status, + (unsigned int) ec, emsg); else GNUNET_asprintf (&s, - "%u", - http_status); + "%u/%u", + http_status, + (unsigned int) ec); eh->cc (eh->cc_cls, (MHD_HTTP_OK == http_status) ? GNUNET_OK : GNUNET_SYSERR, serial_id, @@ -676,6 +680,7 @@ test_execute_wire_transfer (void *cls, char *emsg; const char *json_s; const char *exchange_base_url; + char *wire_s; if (NULL == tc->ctx) { @@ -728,16 +733,19 @@ test_execute_wire_transfer (void *cls, eh = GNUNET_new (struct TALER_WIRE_ExecuteHandle); eh->cc = cc; eh->cc_cls = cc_cls; + wire_s = GNUNET_STRINGS_data_to_string_alloc (&bf.wtid, + sizeof (bf.wtid)); eh->aaih = TALER_BANK_admin_add_incoming (tc->ctx, tc->bank_uri, &tc->auth, exchange_base_url, - &bf.wtid, + wire_s, &amount, (uint64_t) tc->exchange_account_no, (uint64_t) account_no, &execute_cb, eh); + GNUNET_free (wire_s); json_decref (wire); if (NULL == eh->aaih) { @@ -803,6 +811,7 @@ struct TALER_WIRE_HistoryHandle * #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 error code * @param dir direction of the transfer * @param serial_id monotonically increasing counter corresponding to the transaction * @param details details about the wire transfer @@ -811,6 +820,7 @@ struct TALER_WIRE_HistoryHandle static void bhist_cb (void *cls, unsigned int http_status, + enum TALER_ErrorCode ec, enum TALER_BANK_Direction dir, uint64_t serial_id, const struct TALER_BANK_TransferDetails *details, @@ -852,12 +862,17 @@ bhist_cb (void *cls, sizeof (wd.wtid)); wd.wtid_s = details->wire_transfer_subject; } + else + { + wd.wtid_s = NULL; + } GNUNET_free (subject); wd.account_details = details->account_details; if ( (NULL != whh->hres_cb) && (GNUNET_OK != whh->hres_cb (whh->hres_cb_cls, + TALER_EC_NONE, dir, &bserial_id, sizeof (bserial_id), @@ -868,6 +883,7 @@ bhist_cb (void *cls, case MHD_HTTP_NO_CONTENT: if (NULL != whh->hres_cb) (void) whh->hres_cb (whh->hres_cb_cls, + ec, TALER_BANK_DIRECTION_NONE, NULL, 0, @@ -880,6 +896,7 @@ bhist_cb (void *cls, GNUNET_break (0); if (NULL != whh->hres_cb) (void) whh->hres_cb (whh->hres_cb_cls, + ec, TALER_BANK_DIRECTION_NONE, NULL, 0, @@ -1004,26 +1021,32 @@ struct TALER_WIRE_RejectHandle void *rej_cb_cls; /** - * Handle to task for timeout of operation. + * Handle for the reject operation. */ - struct GNUNET_SCHEDULER_Task *timeout_task; + struct TALER_BANK_RejectHandle *brh; }; /** - * Rejection operation failed with timeout, notify callback - * and clean up. + * Callbacks of this type are used to serve the result of asking + * the bank to reject an incoming wire transfer. * - * @param cls closure with `struct TALER_WIRE_RejectHandle` + * @param cls closure + * @param http_status HTTP response code, #MHD_HTTP_NO_CONTENT (204) for successful status request; + * #MHD_HTTP_NOT_FOUND if the rowid is unknown; + * 0 if the bank's reply is bogus (fails to follow the protocol), + * @param ec detailed error code */ static void -timeout_reject (void *cls) +reject_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec) { struct TALER_WIRE_RejectHandle *rh = cls; - rh->timeout_task = NULL; + rh->brh = NULL; rh->rej_cb (rh->rej_cb_cls, - TALER_EC_NOT_IMPLEMENTED /* in the future: TALER_EC_TIMEOUT */); + ec); GNUNET_free (rh); } @@ -1052,14 +1075,30 @@ test_reject_transfer (void *cls, TALER_WIRE_RejectTransferCallback rej_cb, void *rej_cb_cls) { + struct TestClosure *tc = cls; + const uint64_t *rowid_b64 = start_off; struct TALER_WIRE_RejectHandle *rh; - GNUNET_break (0); /* not implemented, just a stub! */ + if (sizeof (uint64_t) != start_off_len) + { + GNUNET_break (0); + return NULL; + } rh = GNUNET_new (struct TALER_WIRE_RejectHandle); rh->rej_cb = rej_cb; rh->rej_cb_cls = rej_cb_cls; - rh->timeout_task = GNUNET_SCHEDULER_add_now (&timeout_reject, - rh); + rh->brh = TALER_BANK_reject (tc->ctx, + tc->bank_uri, + &tc->auth, + (uint64_t) tc->exchange_account_no, + GNUNET_ntohll (*rowid_b64), + &reject_cb, + rh); + if (NULL == rh->brh) + { + GNUNET_free (rh); + return NULL; + } return rh; } @@ -1082,7 +1121,8 @@ test_reject_transfer_cancel (void *cls, { void *ret = rh->rej_cb_cls; - GNUNET_SCHEDULER_cancel (rh->timeout_task); + if (NULL != rh->brh) + TALER_BANK_reject_cancel (rh->brh); GNUNET_free (rh); return ret; } diff --git a/src/wire/test_wire_plugin_transactions_test.c b/src/wire/test_wire_plugin_transactions_test.c index 26331b5b1..a020f13fc 100644 --- a/src/wire/test_wire_plugin_transactions_test.c +++ b/src/wire/test_wire_plugin_transactions_test.c @@ -159,6 +159,7 @@ timeout_cb (void *cls) * the bank for the transaction history. * * @param cls closure + * @param ec taler status code * @param dir direction of the transfer * @param row_off identification of the position at which we are querying * @param row_off_size number of bytes in @a row_off @@ -167,6 +168,7 @@ timeout_cb (void *cls) */ static int history_result_cb (void *cls, + enum TALER_ErrorCode ec, enum TALER_BANK_Direction dir, const void *row_off, size_t row_off_size, -- cgit v1.2.3