/* This file is part of TALER 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 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 plugin_wire_test.c * @brief plugin for the "test" wire method * @author Christian Grothoff */ #include "platform.h" #include "taler_wire_plugin.h" #include "taler_bank_service.h" #include "taler_signatures.h" #include /* only for HTTP status codes */ #include /** * Type of the "cls" argument given to each of the functions in * our API. */ struct TestClosure { /** * Which currency do we support? */ char *currency; /** * URL of our bank. */ char *bank_url; /** * Authentication information. */ struct TALER_BANK_AuthenticationData auth; /** * Handle to the context for sending funds to the bank. */ struct GNUNET_CURL_Context *ctx; /** * Scheduler context for running the @e ctx. */ struct GNUNET_CURL_RescheduleContext *rc; /** * Number of the account that the exchange has at the bank for * transfers. */ unsigned long long exchange_account_no; }; /** * Handle returned by #test_prepare_wire_transfer. */ struct TALER_WIRE_PrepareHandle { /** * Task we use for async execution. */ struct GNUNET_SCHEDULER_Task *task; /** * Test closure we run in. */ struct TestClosure *tc; /** * Wire data for the transfer. */ json_t *wire; /** * Base URL to use for the exchange. */ char *exchange_base_url; /** * Function to call with the serialized data. */ TALER_WIRE_PrepareTransactionCallback ptc; /** * Closure for @e ptc. */ void *ptc_cls; /** * Amount to transfer. */ struct TALER_Amount amount; /** * Subject of the wire transfer. */ struct TALER_WireTransferIdentifierRawP wtid; }; /** * Handle returned by #test_execute_wire_transfer. */ struct TALER_WIRE_ExecuteHandle { /** * Handle to the HTTP request to the bank. */ struct TALER_BANK_AdminAddIncomingHandle *aaih; /** * Function to call with the result. */ TALER_WIRE_ConfirmationCallback cc; /** * Closure for @e cc. */ void *cc_cls; }; /** * Round amount DOWN to the amount that can be transferred via the wire * method. For example, Taler may support 0.000001 EUR as a unit of * payment, but SEPA only supports 0.01 EUR. This function would * round 0.125 EUR to 0.12 EUR in this case. * * @param cls the @e cls of this struct with the plugin-specific state * @param[in,out] amount amount to round down * @return #GNUNET_OK on success, #GNUNET_NO if rounding was unnecessary, * #GNUNET_SYSERR if the amount or currency was invalid */ static int test_amount_round (void *cls, struct TALER_Amount *amount) { struct TestClosure *tc = cls; uint32_t delta; if (NULL == tc->currency) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "taler", "CURRENCY"); return GNUNET_SYSERR; /* not configured with currency */ } if (0 != strcasecmp (amount->currency, tc->currency)) { GNUNET_break (0); return GNUNET_SYSERR; } /* 'test' method supports 1/100 of the unit currency, i.e. 0.01 CUR */ delta = amount->fraction % (TALER_AMOUNT_FRAC_BASE / 100); if (0 == delta) return GNUNET_NO; amount->fraction -= delta; return GNUNET_OK; } /** * Compute purpose for signing. * * @param account number of the account * @param bank_url URL of the bank * @param[out] wsd purpose to be signed */ static void compute_purpose (uint64_t account, const char *bank_url, struct TALER_MasterWireDetailsPS *wsd) { struct GNUNET_HashContext *hc; uint64_t n = GNUNET_htonll (account); wsd->purpose.size = htonl (sizeof (struct TALER_MasterWireDetailsPS)); wsd->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_TEST_DETAILS); hc = GNUNET_CRYPTO_hash_context_start (); GNUNET_CRYPTO_hash_context_read (hc, "test", strlen ("test") + 1); GNUNET_CRYPTO_hash_context_read (hc, &n, sizeof (n)); GNUNET_CRYPTO_hash_context_read (hc, bank_url, strlen (bank_url) + 1); GNUNET_CRYPTO_hash_context_finish (hc, &wsd->h_sepa_details); } /** * Check if the given wire format JSON object is correctly formatted. * Right now, the only thing we require is a field * "account_number" which must contain a positive 53-bit integer. * * @param cls the @e cls of this struct with the plugin-specific state * @param wire the JSON wire format object * @param master_pub public key of the exchange to verify against * @param[out] emsg set to an error message, unless we return #TALER_EC_NONE; * error message must be freed by the caller using GNUNET_free() * @return #TALER_EC_NONE if correctly formatted */ static enum TALER_ErrorCode test_wire_validate (void *cls, const json_t *wire, const struct TALER_MasterPublicKeyP *master_pub, char **emsg) { struct TestClosure *tc = cls; json_error_t error; json_int_t account_no; const char *bank_url; const char *sig_s; struct TALER_MasterWireDetailsPS wsd; struct TALER_MasterSignatureP sig; *emsg = NULL; if (0 != json_unpack_ex ((json_t *) wire, &error, 0, "{s:I, s:s}", "account_number", &account_no, "bank_url", &bank_url)) { char *dump; dump = json_dumps (wire, 0); GNUNET_asprintf (emsg, "JSON parsing failed at %s:%u: %s (%s): %s\n", __FILE__, __LINE__, error.text, error.source, dump); free (dump); return TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_JSON; } if ( (account_no < 0) || (account_no > (1LL << 53)) ) { GNUNET_asprintf (emsg, "Account number %llu outside of permitted range\n", account_no); return TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_ACCOUNT_NUMBER; } if ( (NULL != tc->bank_url) && (0 != strcmp (bank_url, tc->bank_url)) ) { GNUNET_asprintf (emsg, "Wire specifies bank URL `%s', but this exchange only supports `%s'\n", bank_url, tc->bank_url); return TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_BANK; } if (NULL == master_pub) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Skipping signature check as master public key not given\n"); return TALER_EC_NONE; } if (0 != json_unpack_ex ((json_t *) wire, &error, 0, "{s:s}", "sig", &sig_s)) { GNUNET_asprintf (emsg, "Signature check required, but signature is missing\n"); return TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_SIGNATURE; } compute_purpose (account_no, bank_url, &wsd); if (GNUNET_OK != GNUNET_STRINGS_string_to_data (sig_s, strlen (sig_s), &sig, sizeof (sig))) { GNUNET_break (0); return TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_SIGNATURE; } if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_TEST_DETAILS, &wsd.purpose, &sig.eddsa_signature, &master_pub->eddsa_pub)) { GNUNET_asprintf (emsg, "Signature using public key `%s' invalid\n", TALER_B2S (master_pub)); return TALER_EC_DEPOSIT_INVALID_WIRE_FORMAT_SIGNATURE; } return TALER_EC_NONE; } /** * Obtain wire transfer details in the plugin-specific format * from the configuration. * * @param cls closure * @param cfg configuration with details about wire accounts * @param account_name which section in the configuration should we parse * @return NULL if @a cfg fails to have valid wire details for @a account_name */ static json_t * test_get_wire_details (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg, const char *account_name) { struct TestClosure *tc = cls; char *test_wire_file; json_error_t err; json_t *ret; char *emsg; /* Fetch reply */ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, account_name, "TEST_RESPONSE_FILE", &test_wire_file)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, account_name, "TEST_RESPONSE_FILE"); return NULL; } ret = json_load_file (test_wire_file, JSON_REJECT_DUPLICATES, &err); if (NULL == ret) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to parse JSON in %s: %s (%s:%u)\n", test_wire_file, err.text, err.source, err.line); GNUNET_free (test_wire_file); return NULL; } if (TALER_EC_NONE != test_wire_validate (tc, ret, NULL, &emsg)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to validate TEST wire data in %s: %s\n", test_wire_file, emsg); GNUNET_free (emsg); GNUNET_free (test_wire_file); json_decref (ret); return NULL; } GNUNET_free (test_wire_file); return ret; } GNUNET_NETWORK_STRUCT_BEGIN /** * Format we used for serialized transaction data. */ struct BufFormatP { /** * The wire transfer identifier. */ struct TALER_WireTransferIdentifierRawP wtid; /** * The amount. */ struct TALER_AmountNBO amount; /* followed by serialized 'wire' JSON data (0-terminated) */ /* followed by 0-terminated base URL */ }; GNUNET_NETWORK_STRUCT_END /** * Abort preparation of a wire transfer. For example, * because we are shutting down. * * @param cls the @e cls of this struct with the plugin-specific state * @param pth preparation to cancel */ static void test_prepare_wire_transfer_cancel (void *cls, struct TALER_WIRE_PrepareHandle *pth) { if (NULL != pth->task) GNUNET_SCHEDULER_cancel (pth->task); json_decref (pth->wire); GNUNET_free (pth->exchange_base_url); GNUNET_free (pth); } /** * Prepare for exeuction of a wire transfer. Calls the * callback with the serialized state. * * @param cls the `struct TALER_WIRE_PrepareHandle` */ static void do_prepare (void *cls) { struct TALER_WIRE_PrepareHandle *pth = cls; char *wire_enc; size_t len_w; size_t len_b; struct BufFormatP bf; pth->task = NULL; /* serialize the state into a 'buf' */ wire_enc = json_dumps (pth->wire, JSON_COMPACT | JSON_SORT_KEYS); if (NULL == wire_enc) { GNUNET_break (0); pth->ptc (pth->ptc_cls, NULL, 0); test_prepare_wire_transfer_cancel (NULL, pth); return; } len_w = strlen (wire_enc) + 1; len_b = strlen (pth->exchange_base_url) + 1; bf.wtid = pth->wtid; TALER_amount_hton (&bf.amount, &pth->amount); { char buf[sizeof (struct BufFormatP) + len_w + len_b]; memcpy (buf, &bf, sizeof (struct BufFormatP)); memcpy (&buf[sizeof (struct BufFormatP)], wire_enc, len_w); memcpy (&buf[sizeof (struct BufFormatP) + len_w], pth->exchange_base_url, len_b); /* finally give the state back */ pth->ptc (pth->ptc_cls, buf, sizeof (buf)); } free (wire_enc); /* not using GNUNET_free(), as this one is allocated by libjansson */ test_prepare_wire_transfer_cancel (NULL, pth); } /** * Prepare for exeuction of a wire transfer. Note that we should call * @a ptc asynchronously (as that is what the API requires, because * some transfer methods need it). So while we could immediately call * @a ptc, we first bundle up all the data and schedule a task to do * the work. * * @param cls the @e cls of this struct with the plugin-specific state * @param wire valid wire account information * @param amount amount to transfer, already rounded * @param exchange_base_url base URL of this exchange * @param wtid wire transfer identifier to use * @param ptc function to call with the prepared data to persist * @param ptc_cls closure for @a ptc * @return NULL on failure */ static struct TALER_WIRE_PrepareHandle * test_prepare_wire_transfer (void *cls, const json_t *wire, const struct TALER_Amount *amount, const char *exchange_base_url, const struct TALER_WireTransferIdentifierRawP *wtid, TALER_WIRE_PrepareTransactionCallback ptc, void *ptc_cls) { struct TestClosure *tc = cls; struct TALER_WIRE_PrepareHandle *pth; char *emsg; if (TALER_EC_NONE != test_wire_validate (tc, wire, NULL, &emsg)) { GNUNET_break_op (0); GNUNET_free (emsg); return NULL; } pth = GNUNET_new (struct TALER_WIRE_PrepareHandle); pth->tc = tc; pth->wire = json_incref ((json_t *) wire); pth->exchange_base_url = GNUNET_strdup (exchange_base_url); pth->wtid = *wtid; pth->ptc = ptc; pth->ptc_cls = ptc_cls; pth->amount = *amount; pth->task = GNUNET_SCHEDULER_add_now (&do_prepare, pth); return pth; } /** * Called with the result of submitting information about an incoming * transaction to a bank. * * @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) { struct TALER_WIRE_ExecuteHandle *eh = cls; json_t *reason; const char *emsg; char *s; eh->aaih = NULL; emsg = NULL; if (NULL != json) { reason = json_object_get (json, "reason"); if (NULL != reason) emsg = json_string_value (reason); } if (NULL != emsg) GNUNET_asprintf (&s, "%u/%u (%s)", http_status, (unsigned int) ec, emsg); else GNUNET_asprintf (&s, "%u/%u", http_status, (unsigned int) ec); eh->cc (eh->cc_cls, (MHD_HTTP_OK == http_status) ? GNUNET_OK : GNUNET_SYSERR, serial_id, (MHD_HTTP_OK == http_status) ? NULL : s); GNUNET_free (s); GNUNET_free (eh); } /** * Sign wire transfer details in the plugin-specific format. * * @param cls closure * @param in wire transfer details in JSON format * @param key private signing key to use * @param salt salt to add * @param[out] sig where to write the signature * @return #GNUNET_OK on success */ static int test_sign_wire_details (void *cls, const json_t *in, const struct TALER_MasterPrivateKeyP *key, const struct GNUNET_HashCode *salt, struct TALER_MasterSignatureP *sig) { struct TALER_MasterWireDetailsPS wsd; const char *bank_url; const char *type; json_int_t account; json_error_t err; if (0 != json_unpack_ex ((json_t *) in, &err, 0 /* flags */, "{s:s, s:s, s:I}", "type", &type, "bank_url", &bank_url, "account_number", &account)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to unpack JSON: %s (at %u)\n", err.text, err.position); return GNUNET_SYSERR; } if (0 != strcmp (type, "test")) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "`type' must be `test' for test wire details\n"); return GNUNET_SYSERR; } compute_purpose (account, bank_url, &wsd); GNUNET_CRYPTO_eddsa_sign (&key->eddsa_priv, &wsd.purpose, &sig->eddsa_signature); return GNUNET_OK; } /** * 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 */ static struct TALER_WIRE_ExecuteHandle * test_execute_wire_transfer (void *cls, const char *buf, size_t buf_size, TALER_WIRE_ConfirmationCallback cc, void *cc_cls) { struct TestClosure *tc = cls; struct TALER_WIRE_ExecuteHandle *eh; json_t *wire; json_error_t error; struct TALER_Amount amount; json_int_t account_no; struct BufFormatP bf; char *emsg; const char *json_s; const char *exchange_base_url; char *wire_s; if (NULL == tc->ctx) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Bank not initialized, cannot do transfers!\n"); return NULL; /* not initialized with configuration, cannot do transfers */ } if ( (buf_size <= sizeof (struct BufFormatP)) || ('\0' != buf[buf_size - 1]) ) { GNUNET_break (0); return NULL; } json_s = &buf[sizeof (struct BufFormatP)]; exchange_base_url = &json_s[strlen (json_s) + 1]; if (exchange_base_url > &buf[buf_size - 1]) { GNUNET_break (0); return NULL; } memcpy (&bf, buf, sizeof (bf)); TALER_amount_ntoh (&amount, &bf.amount); wire = json_loads (json_s, JSON_REJECT_DUPLICATES, NULL); if (NULL == wire) { GNUNET_break (0); return NULL; } GNUNET_assert (TALER_EC_NONE == test_wire_validate (tc, wire, NULL, &emsg)); if (0 != json_unpack_ex (wire, &error, 0, "{s:I}", "account_number", &account_no)) { GNUNET_break (0); return NULL; } 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_url, &tc->auth, exchange_base_url, 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) { GNUNET_break (0); GNUNET_free (eh); return NULL; } return eh; } /** * Abort execution of a wire transfer. For example, because we are * shutting down. Note that if an execution is aborted, it may or * may not still succeed. The caller MUST run @e * execute_wire_transfer again for the same request as soon as * possilbe, to ensure that the request either ultimately succeeds * or ultimately fails. Until this has been done, the transaction is * in limbo (i.e. may or may not have been committed). * * @param cls the @e cls of this struct with the plugin-specific state * @param eh execution to cancel */ static void test_execute_wire_transfer_cancel (void *cls, struct TALER_WIRE_ExecuteHandle *eh) { TALER_BANK_admin_add_incoming_cancel (eh->aaih); GNUNET_free (eh); } /** * Handle for a #test_get_history() request. */ struct TALER_WIRE_HistoryHandle { /** * Function to call with results. */ TALER_WIRE_HistoryResultCallback hres_cb; /** * Closure for @e hres_cb. */ void *hres_cb_cls; /** * Request to the bank. */ struct TALER_BANK_HistoryHandle *hh; }; /** * Function called with results from the bank about the transaction history. * * @param cls the `struct TALER_WIRE_HistoryHandle` * @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 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 * @param json detailed response from the HTTPD, or NULL if reply was not in JSON */ 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, const json_t *json) { struct TALER_WIRE_HistoryHandle *whh = cls; uint64_t bserial_id = GNUNET_htonll (serial_id); struct TALER_WIRE_TransferDetails wd; 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.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; } 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), &wd)) ) whh->hres_cb = NULL; break; } 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, NULL); GNUNET_free (whh); break; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Bank failed with HTTP status %u (EC: %u)\n", http_status, ec); if (NULL != whh->hres_cb) (void) whh->hres_cb (whh->hres_cb_cls, ec, TALER_BANK_DIRECTION_NONE, NULL, 0, NULL); GNUNET_free (whh); break; } } /** * Query transfer history of an account. We use the variable-size * @a start_off to indicate which transfers we are interested in as * different banking systems may have different ways to identify * transfers. The @a start_off value must thus match the value of * a `row_off` argument previously given to the @a hres_cb. Use * NULL to query transfers from the beginning of time (with * positive @a num_results) or from the latest committed transfers * (with negative @a num_results). * * @param cls the @e cls of this struct with the plugin-specific state * @param direction what kinds of wire transfers should be returned * @param start_off from which row on do we want to get results, use NULL for the latest; exclusive * @param start_off_len number of bytes in @a start_off; must be `sizeof(uint64_t)`. * @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 */ static struct TALER_WIRE_HistoryHandle * test_get_history (void *cls, enum TALER_BANK_Direction direction, const void *start_off, size_t start_off_len, int64_t num_results, TALER_WIRE_HistoryResultCallback hres_cb, void *hres_cb_cls) { struct TestClosure *tc = cls; struct TALER_WIRE_HistoryHandle *whh; const uint64_t *start_off_b64; uint64_t start_row; if (0 == num_results) { GNUNET_break (0); return NULL; } if (TALER_BANK_DIRECTION_NONE == direction) { GNUNET_break (0); return NULL; } if ( (NULL != start_off) && (sizeof (uint64_t) != start_off_len) ) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Wire plugin 'test' got start offset of wrong size (%llu instead of %llu)\n", (unsigned long long) start_off_len, (unsigned long long) sizeof (uint64_t)); GNUNET_break (0); /* Probably something is wrong with the DB, some other component * wrote a wrong value to it. Instead of completely stopping to work, * we just scan from the beginning. */ start_off = NULL; } if (NULL == start_off) { start_row = UINT64_MAX; /* no start row */ } else { start_off_b64 = start_off; start_row = GNUNET_ntohll (*start_off_b64); } whh = GNUNET_new (struct TALER_WIRE_HistoryHandle); whh->hres_cb = hres_cb; whh->hres_cb_cls = hres_cb_cls; whh->hh = TALER_BANK_history (tc->ctx, tc->bank_url, &tc->auth, (uint64_t) tc->exchange_account_no, direction, start_row, num_results, &bhist_cb, whh); if (NULL == whh->hh) { GNUNET_break (0); GNUNET_free (whh); return NULL; } return whh; } /** * Cancel going over the account's history. * * @param cls the @e cls of this struct with the plugin-specific state * @param whh operation to cancel */ static void test_get_history_cancel (void *cls, struct TALER_WIRE_HistoryHandle *whh) { TALER_BANK_history_cancel (whh->hh); GNUNET_free (whh); } /** * 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 for the reject operation. */ struct TALER_BANK_RejectHandle *brh; }; /** * 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 TALER_WIRE_RejectHandle *rh = cls; rh->brh = NULL; rh->rej_cb (rh->rej_cb_cls, ec); 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 TestClosure *tc = cls; const uint64_t *rowid_b64 = start_off; struct TALER_WIRE_RejectHandle *rh; 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->brh = TALER_BANK_reject (tc->ctx, tc->bank_url, &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; } /** * 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; if (NULL != rh->brh) TALER_BANK_reject_cancel (rh->brh); GNUNET_free (rh); return ret; } /** * Initialize test-wire subsystem. * * @param cls a configuration instance * @return NULL on error, otherwise a `struct TALER_WIRE_Plugin` */ void * libtaler_plugin_wire_test_init (void *cls) { struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct TestClosure *tc; struct TALER_WIRE_Plugin *plugin; char *user; char *pass; tc = GNUNET_new (struct TestClosure); if (NULL != cfg) { if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "exchange-wire-test", "BANK_URL", &tc->bank_url)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "exchange-wire-test", "BANK_URL"); GNUNET_free (tc); return NULL; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (cfg, "exchange-wire-test", "EXCHANGE_ACCOUNT_NUMBER", &tc->exchange_account_no)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "exchange-wire-test", "EXCHANGE_ACCOUNT_NUMBER"); GNUNET_free (tc->bank_url); GNUNET_free (tc); return NULL; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "taler", "CURRENCY", &tc->currency)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "taler", "CURRENCY"); GNUNET_free (tc->bank_url); GNUNET_free (tc); return NULL; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "exchange-wire-test", "USERNAME", &user)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "exchange-wire-test", "USERNAME"); GNUNET_free (tc->bank_url); GNUNET_free (tc); return NULL; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "exchange-wire-test", "PASSWORD", &pass)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "exchange-wire-test", "PASSWORD"); GNUNET_free (tc->bank_url); GNUNET_free (tc); GNUNET_free (user); return NULL; } tc->auth.method = TALER_BANK_AUTH_BASIC; tc->auth.details.basic.username = user; tc->auth.details.basic.password = pass; tc->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, &tc->rc); tc->rc = GNUNET_CURL_gnunet_rc_create (tc->ctx); if (NULL == tc->ctx) { GNUNET_break (0); GNUNET_free (tc->currency); GNUNET_free (tc->bank_url); GNUNET_free (tc->auth.details.basic.username); GNUNET_free (tc->auth.details.basic.password); GNUNET_free (tc); return NULL; } } plugin = GNUNET_new (struct TALER_WIRE_Plugin); plugin->cls = tc; plugin->amount_round = &test_amount_round; plugin->get_wire_details = &test_get_wire_details; plugin->sign_wire_details = &test_sign_wire_details; plugin->wire_validate = &test_wire_validate; plugin->prepare_wire_transfer = &test_prepare_wire_transfer; plugin->prepare_wire_transfer_cancel = &test_prepare_wire_transfer_cancel; plugin->execute_wire_transfer = &test_execute_wire_transfer; 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; } /** * Shutdown Test wire subsystem. * * @param cls a `struct TALER_WIRE_Plugin` * @return NULL (always) */ void * libtaler_plugin_wire_test_done (void *cls) { struct TALER_WIRE_Plugin *plugin = cls; struct TestClosure *tc = plugin->cls; if (NULL != tc->ctx) { GNUNET_CURL_fini (tc->ctx); tc->ctx = NULL; } if (NULL != tc->rc) { GNUNET_CURL_gnunet_rc_destroy (tc->rc); tc->rc = NULL; } switch (tc->auth.method) { case TALER_BANK_AUTH_NONE: break; case TALER_BANK_AUTH_BASIC: if (NULL != tc->auth.details.basic.username) { GNUNET_free (tc->auth.details.basic.username); tc->auth.details.basic.username = NULL; } if (NULL != tc->auth.details.basic.password) { GNUNET_free (tc->auth.details.basic.password); tc->auth.details.basic.password = NULL; } break; } GNUNET_free_non_null (tc->currency); GNUNET_free_non_null (tc->bank_url); GNUNET_free (tc); GNUNET_free (plugin); return NULL; } /* end of plugin_wire_test.c */