From 8440de13339a3b38d9efa18b69df409e45cde625 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 12 Nov 2017 15:46:52 +0100 Subject: [PATCH] work on #5077: reserve_pub vs. wtid issue, add reject functionality to wire plugin API (with stub implementations for now) --- src/auditor/taler-wire-auditor.c | 80 +++++-- src/exchange/taler-exchange-wirewatch.c | 110 +++++++++- src/include/taler_error_codes.h | 4 + src/include/taler_wire_plugin.h | 71 +++++- src/wire/plugin_wire_sepa.c | 103 +++++++++ src/wire/plugin_wire_test.c | 202 ++++++++++++++---- src/wire/test_wire_plugin_transactions_test.c | 5 +- 7 files changed, 503 insertions(+), 72 deletions(-) diff --git a/src/auditor/taler-wire-auditor.c b/src/auditor/taler-wire-auditor.c index 4ee92566e..9c7399025 100644 --- a/src/auditor/taler-wire-auditor.c +++ b/src/auditor/taler-wire-auditor.c @@ -70,7 +70,8 @@ static struct GNUNET_CONTAINER_MultiHashMap *in_map; /** * Map with information about outgoing wire transfers. - * Maps hashes of the wire offsets to `struct ReserveOutInfo`s. + * Maps hashes of the wire subjects (in binary encoding) + * to `struct ReserveOutInfo`s. */ static struct GNUNET_CONTAINER_MultiHashMap *out_map; @@ -658,7 +659,9 @@ complain_out_not_found (void *cls, "row", (json_int_t) 0, "amount_wired", TALER_JSON_from_amount (&roi->details.amount), "amount_justified", TALER_JSON_from_amount (&zero), - "wtid", GNUNET_JSON_from_data_auto (&roi->details.reserve_pub), /* #5077 missnomer */ + "wtid", (NULL == roi->details.wtid_s) + ? GNUNET_JSON_from_data_auto (&roi->details.wtid) + : json_string (roi->details.wtid_s), "timestamp", GNUNET_STRINGS_absolute_time_to_string (roi->details.execution_date), "diagnostic", "justification for wire transfer not found")); GNUNET_break (GNUNET_OK == @@ -726,6 +729,7 @@ history_debit_cb (void *cls, const struct TALER_WIRE_TransferDetails *details) { struct ReserveOutInfo *roi; + struct GNUNET_HashCode rowh; if (TALER_BANK_DIRECTION_NONE == dir) { @@ -735,13 +739,36 @@ history_debit_cb (void *cls, check_exchange_wire_out (); return GNUNET_OK; } + if (NULL != details->wtid_s) + { + char *diagnostic; + + GNUNET_CRYPTO_hash (row_off, + row_off_size, + &rowh); + GNUNET_asprintf (&diagnostic, + "malformed wire transfer subject `%s'", + details->wtid_s); + report (report_row_inconsistencies, + json_pack ("{s:s, s:I, s:o, s:o, s:s}", + "table", "bank wire log", + "row", (json_int_t) 0, + "amount", TALER_JSON_from_amount (&details->amount), + "wire_offset_hash", GNUNET_JSON_from_data_auto (&rowh), + "diagnostic", diagnostic)); + /* TODO: report generator currently ignores 'amount' for this + table, maybe use a different table to report this issue! */ + /* TODO: add 'amount' to some total amount that was badly wired! */ + GNUNET_free (diagnostic); + return GNUNET_SYSERR; + } roi = GNUNET_new (struct ReserveOutInfo); - GNUNET_CRYPTO_hash (&details->reserve_pub, /* FIXME (#5077): missnomer */ - sizeof (details->reserve_pub), + GNUNET_CRYPTO_hash (&details->wtid, + sizeof (details->wtid), &roi->subject_hash); roi->details.amount = details->amount; roi->details.execution_date = details->execution_date; - roi->details.reserve_pub = details->reserve_pub; /* FIXME (#5077): missnomer & redundant */ + roi->details.wtid = details->wtid; roi->details.account_details = json_incref ((json_t *) details->account_details); if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put (out_map, @@ -749,13 +776,25 @@ history_debit_cb (void *cls, roi, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) { - GNUNET_break_op (0); /* duplicate wire offset is not allowed! */ + char *diagnostic; + + GNUNET_CRYPTO_hash (row_off, + row_off_size, + &rowh); + GNUNET_asprintf (&diagnostic, + "duplicate wire transfer subject `%s'", + TALER_B2S (&roi->subject_hash)); report (report_row_inconsistencies, - json_pack ("{s:s, s:I, s:o, s:s}", + json_pack ("{s:s, s:I, s:o, s:o, s:s}", "table", "bank wire log", "row", (json_int_t) 0, - "wire_offset_hash", GNUNET_JSON_from_data_auto (&roi->subject_hash), - "diagnostic", "duplicate wire offset")); + "amount", TALER_JSON_from_amount (&details->amount), + "wire_offset_hash", GNUNET_JSON_from_data_auto (&rowh), + "diagnostic", diagnostic)); + /* TODO: report generator currently ignores 'amount' for this + table, maybe use a different table to report this issue! */ + /* TODO: add 'amount' to some total amount that was badly wired! */ + GNUNET_free (diagnostic); return GNUNET_SYSERR; } return GNUNET_OK; @@ -830,7 +869,12 @@ reserve_in_cb (void *cls, rii->row_off_size = wire_reference_size; rii->details.amount = *credit; rii->details.execution_date = execution_date; - rii->details.reserve_pub = *reserve_pub; + /* reserve public key should be the WTID */ + GNUNET_assert (sizeof (rii->details.wtid) == + sizeof (*reserve_pub)); + memcpy (&rii->details.wtid, + reserve_pub, + sizeof (*reserve_pub)); rii->details.account_details = json_incref ((json_t *) sender_account_details); rii->rowid = rowid; if (GNUNET_OK != @@ -875,7 +919,7 @@ complain_in_not_found (void *cls, "row", (json_int_t) rii->rowid, "amount_expected", TALER_JSON_from_amount (&rii->details.amount), "amount_wired", TALER_JSON_from_amount (&zero), - "wtid", GNUNET_JSON_from_data_auto (&rii->details.reserve_pub), /* also reserve_pub, but see #5077 missnomer */ + "wtid", GNUNET_JSON_from_data_auto (&rii->details.wtid), "timestamp", GNUNET_STRINGS_absolute_time_to_string (rii->details.execution_date), "diagnostic", "incoming wire transfer claimed by exchange not found")); GNUNET_break (GNUNET_OK == @@ -966,16 +1010,16 @@ history_credit_cb (void *cls, "diagnostic", "wire reference size missmatch")); return GNUNET_OK; } - if (0 != memcmp (&details->reserve_pub, - &rii->details.reserve_pub, - sizeof (struct TALER_ReservePublicKeyP))) + if (0 != memcmp (&details->wtid, + &rii->details.wtid, + sizeof (struct TALER_WireTransferIdentifierRawP))) { report (report_reserve_in_inconsistencies, json_pack ("{s:I, s:o, s:o, s:o, s:s, s:s}", "row", GNUNET_JSON_from_data (row_off, row_off_size), "amount_exchange_expected", TALER_JSON_from_amount (&rii->details.amount), "amount_wired", TALER_JSON_from_amount (&zero), - "wtid", GNUNET_JSON_from_data_auto (&rii->details.reserve_pub), /* #5077 missnomer */ + "wtid", GNUNET_JSON_from_data_auto (&rii->details.wtid), "timestamp", GNUNET_STRINGS_absolute_time_to_string (rii->details.execution_date), "diagnostic", "wire subject does not match")); GNUNET_break (GNUNET_OK == @@ -987,7 +1031,7 @@ history_credit_cb (void *cls, "row", GNUNET_JSON_from_data (row_off, row_off_size), "amount_exchange_expected", TALER_JSON_from_amount (&zero), "amount_wired", TALER_JSON_from_amount (&details->amount), - "wtid", GNUNET_JSON_from_data_auto (&details->reserve_pub), /* #5077 missnomer */ + "wtid", GNUNET_JSON_from_data_auto (&details->wtid), "timestamp", GNUNET_STRINGS_absolute_time_to_string (details->execution_date), "diagnostic", "wire subject does not match")); @@ -1005,7 +1049,7 @@ history_credit_cb (void *cls, "row", GNUNET_JSON_from_data (row_off, row_off_size), "amount_exchange_expected", TALER_JSON_from_amount (&rii->details.amount), "amount_wired", TALER_JSON_from_amount (&details->amount), - "wtid", GNUNET_JSON_from_data_auto (&details->reserve_pub), /* #5077 missnomer */ + "wtid", GNUNET_JSON_from_data_auto (&details->wtid), "timestamp", GNUNET_STRINGS_absolute_time_to_string (details->execution_date), "diagnostic", "wire amount does not match")); if (0 < TALER_amount_cmp (&details->amount, @@ -1046,7 +1090,7 @@ history_credit_cb (void *cls, json_pack ("{s:s, s:o, s:o}", "amount", TALER_JSON_from_amount (&rii->details.amount), "row", GNUNET_JSON_from_data (row_off, row_off_size), - "wtid", GNUNET_JSON_from_data_auto (&rii->details.reserve_pub))); /* FIXME #5077 missnomer */ + "wtid", GNUNET_JSON_from_data_auto (&rii->details.wtid))); GNUNET_break (GNUNET_OK == TALER_amount_add (&total_missattribution_in, &total_missattribution_in, diff --git a/src/exchange/taler-exchange-wirewatch.c b/src/exchange/taler-exchange-wirewatch.c index 312f8ac5e..ca7f3bad3 100644 --- a/src/exchange/taler-exchange-wirewatch.c +++ b/src/exchange/taler-exchange-wirewatch.c @@ -36,6 +36,23 @@ #define DELAY GNUNET_TIME_UNIT_SECONDS +/** + * Closure for #reject_cb(). + */ +struct RejectContext +{ + /** + * Wire transfer subject that was illformed. + */ + char *wtid_s; + + /** + * Database session that encountered the problem. + */ + struct TALER_EXCHANGEDB_Session *session; +}; + + /** * Handle to the plugin. */ @@ -109,6 +126,11 @@ static struct GNUNET_SCHEDULER_Task *task; */ static struct TALER_WIRE_HistoryHandle *hh; +/** + * Active request to reject a wire transfer. + */ +static struct TALER_WIRE_RejectHandle *rt; + /** * We're being aborted with CTRL-C (or SIGTERM). Shut down. @@ -129,6 +151,15 @@ shutdown_task (void *cls) hh); hh = NULL; } + if (NULL != rt) + { + char *wtid_s; + + wtid_s = wire_plugin->reject_transfer_cancel (wire_plugin->cls, + rt); + rt = NULL; + GNUNET_free (wtid_s); + } TALER_EXCHANGEDB_plugin_unload (db_plugin); db_plugin = NULL; TALER_WIRE_plugin_unload (wire_plugin); @@ -204,6 +235,48 @@ static void find_transfers (void *cls); +/** + * Function called upon completion of the rejection of a wire transfer. + * + * @param cls closure with the `struct RejectContext` + * @param ec error code for the operation + */ +static void +reject_cb (void *cls, + enum TALER_ErrorCode ec) +{ + struct RejectContext *rtc = cls; + enum GNUNET_DB_QueryStatus qs; + + rt = NULL; + if (TALER_EC_NONE != ec) + { + fprintf (stderr, + "Failed to wire back transfer `%s': %d\n", + rtc->wtid_s, + ec); + GNUNET_free (rtc->wtid_s); + db_plugin->rollback (db_plugin->cls, + rtc->session); + GNUNET_free (rtc); + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_free (rtc->wtid_s); + qs = db_plugin->commit (db_plugin->cls, + rtc->session); + GNUNET_free (rtc); + if (0 <= qs) + { + GNUNET_free_non_null (start_off); + start_off = last_row_off; + start_off_size = last_row_off_size; + } + task = GNUNET_SCHEDULER_add_now (&find_transfers, + NULL); +} + + /** * Callbacks of this type are used to serve the result of asking * the bank for the transaction history. @@ -224,6 +297,7 @@ history_cb (void *cls, { struct TALER_EXCHANGEDB_Session *session = cls; enum GNUNET_DB_QueryStatus qs; + struct TALER_ReservePublicKeyP reserve_pub; if (TALER_BANK_DIRECTION_NONE == dir) { @@ -254,13 +328,45 @@ history_cb (void *cls, NULL); return GNUNET_OK; /* will be ignored anyway */ } + if (NULL != details->wtid_s) + { + struct RejectContext *rtc; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Wire transfer over %s has invalid subject `%s', sending it back!\n", + TALER_amount2s (&details->amount), + details->wtid_s); + if (last_row_off_size != row_off_size) + { + GNUNET_free_non_null (last_row_off); + last_row_off = GNUNET_malloc (row_off_size); + } + memcpy (last_row_off, + row_off, + row_off_size); + rtc = GNUNET_new (struct RejectContext); + rtc->session = session; + rtc->wtid_s = GNUNET_strdup (details->wtid_s); + rt = wire_plugin->reject_transfer (wire_plugin->cls, + row_off, + row_off_size, + &reject_cb, + rtc); + return GNUNET_SYSERR; /* will continue later... */ + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Adding wire transfer over %s with subject `%s'\n", TALER_amount2s (&details->amount), - TALER_B2S (&details->reserve_pub)); + TALER_B2S (&details->wtid)); + /* Wire transfer identifier == reserve public key */ + GNUNET_assert (sizeof (reserve_pub) == sizeof (details->wtid)); + memcpy (&reserve_pub, + &details->wtid, + sizeof (reserve_pub)); qs = db_plugin->reserves_in_insert (db_plugin->cls, session, - &details->reserve_pub, + &reserve_pub, &details->amount, details->execution_date, details->account_details, diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h index 65831f4a0..931b5ee1b 100644 --- a/src/include/taler_error_codes.h +++ b/src/include/taler_error_codes.h @@ -63,6 +63,10 @@ enum TALER_ErrorCode */ TALER_EC_INTERNAL_INVARIANT_FAILURE = 5, + /** + * Operation timed out. + */ + TALER_EC_TIMEOUT = 6, /* ********** generic error codes ************* */ diff --git a/src/include/taler_wire_plugin.h b/src/include/taler_wire_plugin.h index 969af3571..6e355baf6 100644 --- a/src/include/taler_wire_plugin.h +++ b/src/include/taler_wire_plugin.h @@ -59,13 +59,17 @@ struct TALER_WIRE_TransferDetails struct GNUNET_TIME_Absolute execution_date; /** - * Reserve public key that was encoded in the wire transfer subject. - * FIXME (#5077): this is incorrect for *outgoing* wire transfers. - * Maybe use `struct TALER_WireTransferIdentifierRawP` here instead? - * OTOH, we might want to make this even more generic in case of - * invalid transfers, so that we can capture those as well! + * Binary data that was encoded in the wire transfer subject, if + * it decoded properly. Otherwise all-zeros and @e wtid_s is set. */ - struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_WireTransferIdentifierRawP wtid; + + /** + * Wire transfer identifer as a string. Set to NULL if the + * identifier was properly Base32 encoded and this @e wtid could be + * set instead. + */ + char *wtid_s; /** * The other account that was involved @@ -93,6 +97,18 @@ typedef int const struct TALER_WIRE_TransferDetails *details); +/** + * Callbacks of this type are used to serve the result of asking + * the bank to reject a wire transfer. + * + * @param cls closure + * @param ec status of the operation, #TALER_EC_NONE on success + */ +typedef void +(*TALER_WIRE_RejectTransferCallback) (void *cls, + enum TALER_ErrorCode ec); + + /** * Handle returned for cancelling a preparation step. */ @@ -308,12 +324,55 @@ struct TALER_WIRE_Plugin /** * Cancel going over the account's history. * + * @param cls plugins' closure * @param whh operation to cancel */ void (*get_history_cancel) (void *cls, struct TALER_WIRE_HistoryHandle *whh); + + /** + * Reject an incoming wire transfer that was obtained from the + * history. This function can be used to transfer funds back to + * the sender if the WTID was malformed (i.e. due to a typo). + * + * Calling `reject_transfer` twice on the same wire transfer should + * be idempotent, i.e. not cause the funds to be wired back twice. + * Furthermore, the transfer should henceforth be removed from the + * results returned by @e get_history. + * + * @param cls plugin's closure + * @param start_off offset of the wire transfer in plugin-specific format + * @param start_off_len number of bytes in @a start_off + * @param rej_cb function to call with the result of the operation + * @param rej_cb_cls closure for @a rej_cb + * @return handle to cancel the operation + */ + struct TALER_WIRE_RejectHandle * + (*reject_transfer)(void *cls, + const void *start_off, + size_t start_off_len, + TALER_WIRE_RejectTransferCallback rej_cb, + void *rej_cb_cls); + + /** + * Cancel ongoing reject operation. Note that the rejection may still + * proceed. Basically, if this function is called, the rejection may + * have happened or not. This function is usually used during shutdown + * or system upgrades. At a later point, the application must call + * @e reject_transfer again for this wire transfer, unless the + * @e get_history shows that the wire transfer no longer exists. + * + * @param cls plugins' closure + * @param rh operation to cancel + * @return closure of the callback of the operation + */ + void * + (*reject_transfer_cancel)(void *cls, + struct TALER_WIRE_RejectHandle *rh); + + }; diff --git a/src/wire/plugin_wire_sepa.c b/src/wire/plugin_wire_sepa.c index 5de3472b9..416acac7c 100644 --- a/src/wire/plugin_wire_sepa.c +++ b/src/wire/plugin_wire_sepa.c @@ -791,6 +791,107 @@ sepa_get_history_cancel (void *cls, } + +/** + * Context for a rejection operation. + */ +struct TALER_WIRE_RejectHandle +{ + /** + * Function to call with the result. + */ + TALER_WIRE_RejectTransferCallback rej_cb; + + /** + * Closure for @e rej_cb. + */ + void *rej_cb_cls; + + /** + * Handle to task for timeout of operation. + */ + struct GNUNET_SCHEDULER_Task *timeout_task; +}; + + +/** + * Rejection operation failed with timeout, notify callback + * and clean up. + * + * @param cls closure with `struct TALER_WIRE_RejectHandle` + */ +static void +timeout_reject (void *cls) +{ + struct TALER_WIRE_RejectHandle *rh = cls; + + rh->timeout_task = NULL; + rh->rej_cb (rh->rej_cb_cls, + TALER_EC_NOT_IMPLEMENTED /* in the future: TALER_EC_TIMEOUT */); + GNUNET_free (rh); +} + + +/** + * Reject an incoming wire transfer that was obtained from the + * history. This function can be used to transfer funds back to + * the sender if the WTID was malformed (i.e. due to a typo). + * + * Calling `reject_transfer` twice on the same wire transfer should + * be idempotent, i.e. not cause the funds to be wired back twice. + * Furthermore, the transfer should henceforth be removed from the + * results returned by @e get_history. + * + * @param cls plugin's closure + * @param start_off offset of the wire transfer in plugin-specific format + * @param start_off_len number of bytes in @a start_off + * @param rej_cb function to call with the result of the operation + * @param rej_cb_cls closure for @a rej_cb + * @return handle to cancel the operation + */ +static struct TALER_WIRE_RejectHandle * +sepa_reject_transfer (void *cls, + const void *start_off, + size_t start_off_len, + TALER_WIRE_RejectTransferCallback rej_cb, + void *rej_cb_cls) +{ + struct TALER_WIRE_RejectHandle *rh; + + GNUNET_break (0); /* not implemented, just a stub! */ + 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); + return rh; +} + + +/** + * Cancel ongoing reject operation. Note that the rejection may still + * proceed. Basically, if this function is called, the rejection may + * have happened or not. This function is usually used during shutdown + * or system upgrades. At a later point, the application must call + * @e reject_transfer again for this wire transfer, unless the + * @e get_history shows that the wire transfer no longer exists. + * + * @param cls plugins' closure + * @param rh operation to cancel + * @return closure of the callback of the operation + */ +static void * +sepa_reject_transfer_cancel (void *cls, + struct TALER_WIRE_RejectHandle *rh) +{ + void *ret = rh->rej_cb_cls; + + GNUNET_SCHEDULER_cancel (rh->timeout_task); + GNUNET_free (rh); + return ret; +} + + /** * Initialize sepa-wire subsystem. * @@ -832,6 +933,8 @@ libtaler_plugin_wire_sepa_init (void *cls) plugin->execute_wire_transfer_cancel = &sepa_execute_wire_transfer_cancel; plugin->get_history = &sepa_get_history; plugin->get_history_cancel = &sepa_get_history_cancel; + plugin->reject_transfer = &sepa_reject_transfer; + plugin->reject_transfer_cancel = &sepa_reject_transfer_cancel; return plugin; } diff --git a/src/wire/plugin_wire_test.c b/src/wire/plugin_wire_test.c index c41bd7e9f..e9d5ad085 100644 --- a/src/wire/plugin_wire_test.c +++ b/src/wire/plugin_wire_test.c @@ -820,52 +820,52 @@ bhist_cb (void *cls, uint64_t bserial_id = GNUNET_htonll (serial_id); struct TALER_WIRE_TransferDetails wd; - if (MHD_HTTP_OK == http_status) - { - char *subject; - char *space; + switch (http_status) { + case MHD_HTTP_OK: + { + char *subject; + char *space; - wd.amount = details->amount; - wd.execution_date = details->execution_date; - subject = GNUNET_strdup (details->wire_transfer_subject); - space = strchr (subject, (int) ' '); - if (NULL != space) - { - /* Space separates the actual wire transfer subject from the - exchange base URL (if present, expected only for outgoing - transactions). So we cut the string off at the space. */ - *space = '\0'; - } - /* NOTE: For a real bank, the subject should include a checksum! */ - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (subject, - strlen (subject), - &wd.reserve_pub, - sizeof (wd.reserve_pub))) - { - GNUNET_break (0); + wd.amount = details->amount; + wd.execution_date = details->execution_date; + subject = GNUNET_strdup (details->wire_transfer_subject); + space = strchr (subject, (int) ' '); + if (NULL != space) + { + /* Space separates the actual wire transfer subject from the + exchange base URL (if present, expected only for outgoing + transactions). So we cut the string off at the space. */ + *space = '\0'; + } + /* NOTE: For a real bank, the subject should include a checksum! */ + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (subject, + strlen (subject), + &wd.wtid, + sizeof (wd.wtid))) + { + /* Ill-formed wire subject, set binary version to all zeros + and pass as a string, this time including the part after + the space. */ + memset (&wd.wtid, + 0, + sizeof (wd.wtid)); + wd.wtid_s = details->wire_transfer_subject; + } GNUNET_free (subject); - /* NOTE: for a "real" bank, we would want to trigger logic to undo the - wire transfer. However, for the "demo" bank, it should currently - be "impossible" to do wire transfers with invalid subjects, and - equally we thus don't need to undo them (and there is no API to do - that nicely either right now). So we don't handle this case for now. */ - return; - } - GNUNET_free (subject); - wd.account_details = details->account_details; + wd.account_details = details->account_details; - if ( (NULL != whh->hres_cb) && - (GNUNET_OK != - whh->hres_cb (whh->hres_cb_cls, - dir, - &bserial_id, - sizeof (bserial_id), - &wd)) ) - whh->hres_cb = NULL; - } - else - { + if ( (NULL != whh->hres_cb) && + (GNUNET_OK != + whh->hres_cb (whh->hres_cb_cls, + dir, + &bserial_id, + sizeof (bserial_id), + &wd)) ) + whh->hres_cb = NULL; + break; + } + case MHD_HTTP_NO_CONTENT: if (NULL != whh->hres_cb) (void) whh->hres_cb (whh->hres_cb_cls, TALER_BANK_DIRECTION_NONE, @@ -874,6 +874,20 @@ bhist_cb (void *cls, NULL); whh->hh = NULL; GNUNET_free (whh); + break; + default: + /* FIXME: consider modifying API to pass more specific error code(s) + back to the application. */ + GNUNET_break (0); + if (NULL != whh->hres_cb) + (void) whh->hres_cb (whh->hres_cb_cls, + TALER_BANK_DIRECTION_NONE, + NULL, + 0, + NULL); + whh->hh = NULL; + GNUNET_free (whh); + break; } } @@ -975,6 +989,106 @@ test_get_history_cancel (void *cls, } +/** + * Context for a rejection operation. + */ +struct TALER_WIRE_RejectHandle +{ + /** + * Function to call with the result. + */ + TALER_WIRE_RejectTransferCallback rej_cb; + + /** + * Closure for @e rej_cb. + */ + void *rej_cb_cls; + + /** + * Handle to task for timeout of operation. + */ + struct GNUNET_SCHEDULER_Task *timeout_task; +}; + + +/** + * Rejection operation failed with timeout, notify callback + * and clean up. + * + * @param cls closure with `struct TALER_WIRE_RejectHandle` + */ +static void +timeout_reject (void *cls) +{ + struct TALER_WIRE_RejectHandle *rh = cls; + + rh->timeout_task = NULL; + rh->rej_cb (rh->rej_cb_cls, + TALER_EC_NOT_IMPLEMENTED /* in the future: TALER_EC_TIMEOUT */); + GNUNET_free (rh); +} + + +/** + * Reject an incoming wire transfer that was obtained from the + * history. This function can be used to transfer funds back to + * the sender if the WTID was malformed (i.e. due to a typo). + * + * Calling `reject_transfer` twice on the same wire transfer should + * be idempotent, i.e. not cause the funds to be wired back twice. + * Furthermore, the transfer should henceforth be removed from the + * results returned by @e get_history. + * + * @param cls plugin's closure + * @param start_off offset of the wire transfer in plugin-specific format + * @param start_off_len number of bytes in @a start_off + * @param rej_cb function to call with the result of the operation + * @param rej_cb_cls closure for @a rej_cb + * @return handle to cancel the operation + */ +static struct TALER_WIRE_RejectHandle * +test_reject_transfer (void *cls, + const void *start_off, + size_t start_off_len, + TALER_WIRE_RejectTransferCallback rej_cb, + void *rej_cb_cls) +{ + struct TALER_WIRE_RejectHandle *rh; + + GNUNET_break (0); /* not implemented, just a stub! */ + 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); + return rh; +} + + +/** + * Cancel ongoing reject operation. Note that the rejection may still + * proceed. Basically, if this function is called, the rejection may + * have happened or not. This function is usually used during shutdown + * or system upgrades. At a later point, the application must call + * @e reject_transfer again for this wire transfer, unless the + * @e get_history shows that the wire transfer no longer exists. + * + * @param cls plugins' closure + * @param rh operation to cancel + * @return closure of the callback of the operation + */ +static void * +test_reject_transfer_cancel (void *cls, + struct TALER_WIRE_RejectHandle *rh) +{ + void *ret = rh->rej_cb_cls; + + GNUNET_SCHEDULER_cancel (rh->timeout_task); + GNUNET_free (rh); + return ret; +} + + /** * Initialize test-wire subsystem. * @@ -1087,6 +1201,8 @@ libtaler_plugin_wire_test_init (void *cls) plugin->execute_wire_transfer_cancel = &test_execute_wire_transfer_cancel; plugin->get_history = &test_get_history; plugin->get_history_cancel = &test_get_history_cancel; + plugin->reject_transfer = &test_reject_transfer; + plugin->reject_transfer_cancel = &test_reject_transfer_cancel; return plugin; } diff --git a/src/wire/test_wire_plugin_transactions_test.c b/src/wire/test_wire_plugin_transactions_test.c index ce31e99ee..26331b5b1 100644 --- a/src/wire/test_wire_plugin_transactions_test.c +++ b/src/wire/test_wire_plugin_transactions_test.c @@ -211,9 +211,8 @@ history_result_cb (void *cls, return GNUNET_SYSERR; } if (0 != memcmp (&wtid, - &details->reserve_pub, - GNUNET_MIN (sizeof (struct TALER_ReservePublicKeyP), - sizeof (wtid)))) + &details->wtid, + sizeof (struct TALER_WireTransferIdentifierRawP))) { GNUNET_break (0); global_ret = GNUNET_SYSERR;