diff options
Diffstat (limited to 'src/bank-lib/fakebank.c')
-rw-r--r-- | src/bank-lib/fakebank.c | 1588 |
1 files changed, 292 insertions, 1296 deletions
diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c index 9c4aeb6e9..3a004dc80 100644 --- a/src/bank-lib/fakebank.c +++ b/src/bank-lib/fakebank.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2016-2020 Taler Systems SA + (C) 2016-2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -22,527 +22,19 @@ * @author Christian Grothoff <christian@grothoff.org> */ #include "platform.h" +#include <pthread.h> +#include <poll.h> +#ifdef __linux__ +#include <sys/eventfd.h> +#endif #include "taler_fakebank_lib.h" #include "taler_bank_service.h" #include "taler_mhd_lib.h" - -/** - * Maximum POST request size (for /admin/add-incoming) - */ -#define REQUEST_BUFFER_MAX (4 * 1024) - - -/** - * Details about a transcation we (as the simulated bank) received. - */ -struct Transaction -{ - /** - * We store transactions in a DLL. - */ - struct Transaction *next; - - /** - * We store transactions in a DLL. - */ - struct Transaction *prev; - - /** - * Amount to be transferred. - */ - struct TALER_Amount amount; - - /** - * Account to debit (string, not payto!) - */ - char *debit_account; - - /** - * Account to credit (string, not payto!) - */ - char *credit_account; - - /** - * Random unique identifier for the request. - */ - struct GNUNET_HashCode request_uid; - - /** - * What does the @e subject contain? - */ - enum - { - /** - * Transfer TO the exchange. - */ - T_CREDIT, - - /** - * Transfer FROM the exchange. - */ - T_DEBIT - } type; - - /** - * Wire transfer subject. - */ - union - { - - /** - * Used if @e type is T_DEBIT. - */ - struct - { - - /** - * Subject of the transfer. - */ - struct TALER_WireTransferIdentifierRawP wtid; - - /** - * Base URL of the exchange. - */ - char *exchange_base_url; - - } debit; - - /** - * Used if @e type is T_CREDIT. - */ - struct - { - - /** - * Reserve public key of the credit operation. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - } credit; - - } subject; - - /** - * When did the transaction happen? - */ - struct GNUNET_TIME_Absolute date; - - /** - * Number of this transaction. - */ - uint64_t row_id; - - /** - * Has this transaction been subjected to #TALER_FAKEBANK_check_credit() - * or #TALER_FAKEBANK_check_debit() - * and should thus no longer be counted in - * #TALER_FAKEBANK_check_empty()? - */ - int checked; -}; - - -/** - * Handle for the fake bank. - */ -struct TALER_FAKEBANK_Handle -{ - /** - * We store transactions in a DLL. - */ - struct Transaction *transactions_head; - - /** - * We store transactions in a DLL. - */ - struct Transaction *transactions_tail; - - /** - * HTTP server we run to pretend to be the "test" bank. - */ - struct MHD_Daemon *mhd_bank; - - /** - * Task running HTTP server for the "test" bank. - */ - struct GNUNET_SCHEDULER_Task *mhd_task; - - /** - * Number of transactions. - */ - uint64_t serial_counter; - - /** - * Currency used by the fakebank. - */ - char *currency; - - /** - * BaseURL of the fakebank. - */ - char *my_baseurl; - - /** - * Our port number. - */ - uint16_t port; - -#if EPOLL_SUPPORT - /** - * Boxed @e mhd_fd. - */ - struct GNUNET_NETWORK_Handle *mhd_rfd; - - /** - * File descriptor to use to wait for MHD. - */ - int mhd_fd; -#endif -}; - - -/** - * Generate log messages for failed check operation. - * - * @param h handle to output transaction log for - */ -static void -check_log (struct TALER_FAKEBANK_Handle *h) -{ - for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next) - { - if (GNUNET_YES == t->checked) - continue; - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "%s -> %s (%s) %s (%s)\n", - t->debit_account, - t->credit_account, - TALER_amount2s (&t->amount), - (T_DEBIT == t->type) - ? t->subject.debit.exchange_base_url - : TALER_B2S (&t->subject.credit.reserve_pub), - (T_DEBIT == t->type) ? "DEBIT" : "CREDIT"); - } -} - - -/** - * Check that the @a want_amount was transferred from the @a - * want_debit to the @a want_credit account. If so, set the @a subject - * to the transfer identifier and remove the transaction from the - * list. If the transaction was not recorded, return #GNUNET_SYSERR. - * - * @param h bank instance - * @param want_amount transfer amount desired - * @param want_debit account that should have been debited - * @param want_credit account that should have been credited - * @param exchange_base_url expected base URL of the exchange, - * i.e. "https://example.com/"; may include a port - * @param[out] wtid set to the wire transfer identifier - * @return #GNUNET_OK on success - */ -int -TALER_FAKEBANK_check_debit (struct TALER_FAKEBANK_Handle *h, - const struct TALER_Amount *want_amount, - const char *want_debit, - const char *want_credit, - const char *exchange_base_url, - struct TALER_WireTransferIdentifierRawP *wtid) -{ - GNUNET_assert (0 == strcasecmp (want_amount->currency, - h->currency)); - for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next) - { - if ( (0 == strcasecmp (want_debit, - t->debit_account)) && - (0 == strcasecmp (want_credit, - t->credit_account)) && - (0 == TALER_amount_cmp (want_amount, - &t->amount)) && - (GNUNET_NO == t->checked) && - (T_DEBIT == t->type) && - (0 == strcasecmp (exchange_base_url, - t->subject.debit.exchange_base_url)) ) - { - *wtid = t->subject.debit.wtid; - t->checked = GNUNET_YES; - return GNUNET_OK; - } - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Did not find matching transaction! I have:\n"); - check_log (h); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "I wanted: %s->%s (%s) from exchange %s (DEBIT)\n", - want_debit, - want_credit, - TALER_amount2s (want_amount), - exchange_base_url); - return GNUNET_SYSERR; -} - - -/** - * Check that the @a want_amount was transferred from the @a want_debit to the - * @a want_credit account with the @a subject. If so, remove the transaction - * from the list. If the transaction was not recorded, return #GNUNET_SYSERR. - * - * @param h bank instance - * @param want_amount transfer amount desired - * @param want_debit account that should have been debited - * @param want_credit account that should have been credited - * @param reserve_pub reserve public key expected in wire subject - * @return #GNUNET_OK on success - */ -int -TALER_FAKEBANK_check_credit (struct TALER_FAKEBANK_Handle *h, - const struct TALER_Amount *want_amount, - const char *want_debit, - const char *want_credit, - const struct TALER_ReservePublicKeyP *reserve_pub) -{ - GNUNET_assert (0 == strcasecmp (want_amount->currency, - h->currency)); - for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next) - { - if ( (0 == strcasecmp (want_debit, - t->debit_account)) && - (0 == strcasecmp (want_credit, - t->credit_account)) && - (0 == TALER_amount_cmp (want_amount, - &t->amount)) && - (GNUNET_NO == t->checked) && - (T_CREDIT == t->type) && - (0 == GNUNET_memcmp (reserve_pub, - &t->subject.credit.reserve_pub)) ) - { - t->checked = GNUNET_YES; - return GNUNET_OK; - } - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Did not find matching transaction!\nI have:\n"); - check_log (h); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "I wanted:\n%s -> %s (%s) with subject %s (CREDIT)\n", - want_debit, - want_credit, - TALER_amount2s (want_amount), - TALER_B2S (reserve_pub)); - return GNUNET_SYSERR; -} - - -/** - * Tell the fakebank to create another wire transfer *from* an exchange. - * - * @param h fake bank handle - * @param debit_account account to debit - * @param credit_account account to credit - * @param amount amount to transfer - * @param subject wire transfer subject to use - * @param exchange_base_url exchange URL - * @param request_uid unique number to make the request unique, or NULL to create one - * @param[out] ret_row_id pointer to store the row ID of this transaction - * @return #GNUNET_YES if the transfer was successful, - * #GNUNET_SYSERR if the request_uid was reused for a different transfer - */ -int -TALER_FAKEBANK_make_transfer ( - struct TALER_FAKEBANK_Handle *h, - const char *debit_account, - const char *credit_account, - const struct TALER_Amount *amount, - const struct TALER_WireTransferIdentifierRawP *subject, - const char *exchange_base_url, - const struct GNUNET_HashCode *request_uid, - uint64_t *ret_row_id) -{ - struct Transaction *t; - - GNUNET_assert (0 == strcasecmp (amount->currency, - h->currency)); - GNUNET_break (0 != strncasecmp ("payto://", - debit_account, - strlen ("payto://"))); - GNUNET_break (0 != strncasecmp ("payto://", - credit_account, - strlen ("payto://"))); - if (NULL != request_uid) - { - for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next) - { - if (0 != GNUNET_memcmp (request_uid, &t->request_uid)) - continue; - if ( (0 != strcasecmp (debit_account, - t->debit_account)) || - (0 != strcasecmp (credit_account, - t->credit_account)) || - (0 != TALER_amount_cmp (amount, - &t->amount)) || - (T_DEBIT != t->type) || - (0 != GNUNET_memcmp (subject, - &t->subject.debit.wtid)) ) - { - /* Transaction exists, but with different details. */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - *ret_row_id = t->row_id; - return GNUNET_OK; - } - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Making transfer from %s to %s over %s and subject %s; for exchange: %s\n", - debit_account, - credit_account, - TALER_amount2s (amount), - TALER_B2S (subject), - exchange_base_url); - t = GNUNET_new (struct Transaction); - t->debit_account = GNUNET_strdup (debit_account); - t->credit_account = GNUNET_strdup (credit_account); - t->amount = *amount; - t->row_id = ++h->serial_counter; - t->date = GNUNET_TIME_absolute_get (); - t->type = T_DEBIT; - t->subject.debit.exchange_base_url = GNUNET_strdup (exchange_base_url); - t->subject.debit.wtid = *subject; - if (NULL == request_uid) - GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE, - &t->request_uid); - else - t->request_uid = *request_uid; - GNUNET_TIME_round_abs (&t->date); - GNUNET_CONTAINER_DLL_insert_tail (h->transactions_head, - h->transactions_tail, - t); - *ret_row_id = t->row_id; - return GNUNET_OK; -} - - -/** - * Tell the fakebank to create another wire transfer *to* an exchange. - * - * @param h fake bank handle - * @param debit_account account to debit - * @param credit_account account to credit - * @param amount amount to transfer - * @param reserve_pub reserve public key to use in subject - * @return serial_id of the transfer - */ -uint64_t -TALER_FAKEBANK_make_admin_transfer ( - struct TALER_FAKEBANK_Handle *h, - const char *debit_account, - const char *credit_account, - const struct TALER_Amount *amount, - const struct TALER_ReservePublicKeyP *reserve_pub) -{ - struct Transaction *t; - - GNUNET_assert (0 == strcasecmp (amount->currency, - h->currency)); - GNUNET_assert (NULL != debit_account); - GNUNET_assert (NULL != credit_account); - GNUNET_break (0 != strncasecmp ("payto://", - debit_account, - strlen ("payto://"))); - GNUNET_break (0 != strncasecmp ("payto://", - credit_account, - strlen ("payto://"))); - t = GNUNET_new (struct Transaction); - t->debit_account = GNUNET_strdup (debit_account); - t->credit_account = GNUNET_strdup (credit_account); - t->amount = *amount; - t->row_id = ++h->serial_counter; - t->date = GNUNET_TIME_absolute_get (); - t->type = T_CREDIT; - t->subject.credit.reserve_pub = *reserve_pub; - GNUNET_TIME_round_abs (&t->date); - GNUNET_CONTAINER_DLL_insert_tail (h->transactions_head, - h->transactions_tail, - t); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Making transfer from %s to %s over %s and subject %s at row %llu\n", - debit_account, - credit_account, - TALER_amount2s (amount), - TALER_B2S (reserve_pub), - (unsigned long long) t->row_id); - return t->row_id; -} - - -/** - * Check that no wire transfers were ordered (or at least none - * that have not been taken care of via #TALER_FAKEBANK_check_credit() - * or #TALER_FAKEBANK_check_debit()). - * If any transactions are onrecord, return #GNUNET_SYSERR. - * - * @param h bank instance - * @return #GNUNET_OK on success - */ -int -TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h) -{ - struct Transaction *t; - - t = h->transactions_head; - while (NULL != t) - { - if (GNUNET_YES != t->checked) - break; - t = t->next; - } - if (NULL == t) - return GNUNET_OK; - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Expected empty transaction set, but I have:\n"); - check_log (h); - return GNUNET_SYSERR; -} - - -/** - * Stop running the fake bank. - * - * @param h bank to stop - */ -void -TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h) -{ - struct Transaction *t; - - while (NULL != (t = h->transactions_head)) - { - GNUNET_CONTAINER_DLL_remove (h->transactions_head, - h->transactions_tail, - t); - GNUNET_free (t->debit_account); - GNUNET_free (t->credit_account); - if (T_DEBIT == t->type) - GNUNET_free (t->subject.debit.exchange_base_url); - GNUNET_free (t); - } - if (NULL != h->mhd_task) - { - GNUNET_SCHEDULER_cancel (h->mhd_task); - h->mhd_task = NULL; - } -#if EPOLL_SUPPORT - GNUNET_NETWORK_socket_free_memory_only_ (h->mhd_rfd); -#endif - if (NULL != h->mhd_bank) - { - MHD_stop_daemon (h->mhd_bank); - h->mhd_bank = NULL; - } - GNUNET_free (h->my_baseurl); - GNUNET_free (h->currency); - GNUNET_free (h); -} +#include <gnunet/gnunet_mhd_compat.h> +#include "fakebank.h" +#include "fakebank_bank.h" +#include "fakebank_common_lp.h" +#include "fakebank_tbi.h" /** @@ -551,9 +43,9 @@ TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h) * @a con_cls that might still need to be cleaned up. Call the * respective function to free the memory. * - * @param cls client-defined closure + * @param cls a `struct TALER_FAKEBANK_Handle *` * @param connection connection handle - * @param con_cls value as set by the last call to + * @param con_cls a `struct ConnectionContext *` * the #MHD_AccessHandlerCallback * @param toe reason for request termination * @see #MHD_OPTION_NOTIFY_COMPLETED @@ -565,704 +57,16 @@ handle_mhd_completion_callback (void *cls, void **con_cls, enum MHD_RequestTerminationCode toe) { - /* struct TALER_FAKEBANK_Handle *h = cls; */ - (void) cls; - (void) connection; - (void) toe; - GNUNET_JSON_post_parser_cleanup (*con_cls); - *con_cls = NULL; -} - - -/** - * Handle incoming HTTP request for /admin/add/incoming. - * - * @param h the fakebank handle - * @param connection the connection - * @param account account into which to deposit the funds (credit) - * @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_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) -{ - enum GNUNET_JSON_PostResult pr; - json_t *json; - uint64_t row_id; - - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - 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; - } - { - const char *debit_account; - struct TALER_Amount amount; - struct TALER_ReservePublicKeyP reserve_pub; - char *debit; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_pub", &reserve_pub), - GNUNET_JSON_spec_string ("debit_account", &debit_account), - TALER_JSON_spec_amount ("amount", &amount), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break (0); - json_decref (json); - /* We're fakebank, no need for nice error handling */ - return MHD_NO; - } - debit = TALER_xtalerbank_account_from_payto (debit_account); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Receiving incoming wire transfer: %s->%s, subject: %s, amount: %s\n", - debit, - account, - TALER_B2S (&reserve_pub), - TALER_amount2s (&amount)); - row_id = TALER_FAKEBANK_make_admin_transfer (h, - debit, - account, - &amount, - &reserve_pub); - GNUNET_free (debit); - } - json_decref (json); - - /* Finally build response object */ - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_OK, - "{s:I, s:o}", - "row_id", - (json_int_t) row_id, - "timestamp", - GNUNET_JSON_from_time_abs ( - h->transactions_tail->date)); -} - - -/** - * Handle incoming HTTP request for /transfer. - * - * @param h the fakebank handle - * @param connection the connection - * @param account account making the transfer - * @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_transfer (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) -{ - enum GNUNET_JSON_PostResult pr; - json_t *json; - uint64_t row_id; - - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - 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; - } - { - struct GNUNET_HashCode uuid; - struct TALER_WireTransferIdentifierRawP wtid; - const char *credit_account; - char *credit; - const char *base_url; - struct TALER_Amount amount; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("request_uid", - &uuid), - TALER_JSON_spec_amount ("amount", - &amount), - GNUNET_JSON_spec_string ("exchange_base_url", - &base_url), - GNUNET_JSON_spec_fixed_auto ("wtid", - &wtid), - GNUNET_JSON_spec_string ("credit_account", - &credit_account), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break (0); - json_decref (json); - /* We are fakebank, no need for nice error handling */ - return MHD_NO; - } - { - int ret; - - credit = TALER_xtalerbank_account_from_payto (credit_account); - ret = TALER_FAKEBANK_make_transfer (h, - account, - credit, - &amount, - &wtid, - base_url, - &uuid, - &row_id); - if (GNUNET_OK != ret) - { - GNUNET_break (0); - json_decref (json); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED, - "transfer request UID was reused"); - - - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Receiving incoming wire transfer: %s->%s, subject: %s, amount: %s, from %s\n", - account, - credit, - TALER_B2S (&wtid), - TALER_amount2s (&amount), - base_url); - GNUNET_free (credit); - } - } - json_decref (json); - - /* Finally build response object */ - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_OK, - "{s:I, s:o}", - "row_id", - (json_int_t) row_id, - /* dummy timestamp */ - "timestamp", GNUNET_JSON_from_time_abs ( - GNUNET_TIME_UNIT_ZERO_ABS)); -} - - -/** - * Handle incoming HTTP request for / (home page). - * - * @param h the fakebank handle - * @param connection the connection - * @param con_cls place to store state, not used - * @return MHD result code - */ -static int -handle_home_page (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - void **con_cls) -{ - int ret; - struct MHD_Response *resp; -#define HELLOMSG "Hello, Fakebank!" + struct TALER_FAKEBANK_Handle *h = cls; + struct ConnectionContext *cc = *con_cls; (void) h; - (void) con_cls; - resp = MHD_create_response_from_buffer - (strlen (HELLOMSG), - HELLOMSG, - MHD_RESPMEM_MUST_COPY); - - ret = MHD_queue_response (connection, - MHD_HTTP_OK, - resp); - - MHD_destroy_response (resp); - return ret; -} - - -/** - * This is the "base" structure for both the /history and the - * /history-range API calls. - */ -struct HistoryArgs -{ - - /** - * Bank account number of the requesting client. - */ - uint64_t account_number; - - /** - * Index of the starting transaction. - */ - uint64_t start_idx; - - /** - * Requested number of results and order - * (positive: ascending, negative: descending) - */ - int64_t delta; - - /** - * Timeout for long polling. - */ - struct GNUNET_TIME_Relative lp_timeout; - - /** - * #GNUNET_YES if starting point was given. - */ - int have_start; - -}; - - -/** - * Parse URL history arguments, of _both_ APIs: - * /history/incoming and /history/outgoing. - * - * @param connection MHD connection. - * @param[out] ha will contain the parsed values. - * @return #GNUNET_OK only if the parsing succeeds. - */ -static int -parse_history_common_args (struct MHD_Connection *connection, - struct HistoryArgs *ha) -{ - const char *start; - const char *delta; - const char *long_poll_ms; - unsigned long long lp_timeout; - unsigned long long sval; - long long d; - - start = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "start"); - ha->have_start = (NULL != start); - delta = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "delta"); - long_poll_ms = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "long_poll_ms"); - lp_timeout = 0; - if ( (NULL == delta) || - (1 != sscanf (delta, - "%lld", - &d)) || - ( (NULL != long_poll_ms) && - (1 != sscanf (long_poll_ms, - "%llu", - &lp_timeout)) ) || - ( (NULL != start) && - (1 != sscanf (start, - "%llu", - &sval)) ) ) - { - /* Fail if one of the above failed. */ - /* Invalid request, given that this is fakebank we impolitely - * just kill the connection instead of returning a nice error. - */ - GNUNET_break (0); - return GNUNET_NO; - } - if (NULL == start) - ha->start_idx = (d > 0) ? 0 : UINT64_MAX; - else - ha->start_idx = (uint64_t) sval; - ha->delta = (int64_t) d; - if (0 == ha->delta) - { - GNUNET_break (0); - return GNUNET_NO; - } - ha->lp_timeout - = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, - lp_timeout); - return GNUNET_OK; -} - - -/** - * Handle incoming HTTP request for /history/outgoing - * - * @param h the fakebank handle - * @param connection the connection - * @param account which account the request is about - * @return MHD result code - */ -static int -handle_debit_history (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account) -{ - struct HistoryArgs ha; - const struct Transaction *pos; - json_t *history; - - if (GNUNET_OK != - parse_history_common_args (connection, - &ha)) - { - GNUNET_break (0); - return MHD_NO; - } - - if (! ha.have_start) - { - pos = (0 > ha.delta) - ? h->transactions_tail - : h->transactions_head; - } - else if (NULL != h->transactions_head) - { - for (pos = h->transactions_head; - NULL != pos; - pos = pos->next) - if (pos->row_id == ha.start_idx) - break; - if (NULL == pos) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid start specified, transaction %llu not known!\n", - (unsigned long long) ha.start_idx); - return MHD_NO; - } - /* range is exclusive, skip the matching entry */ - if (0 > ha.delta) - pos = pos->prev; - else - pos = pos->next; - } - else - { - /* list is empty */ - pos = NULL; - } - history = json_array (); - while ( (0 != ha.delta) && - (NULL != pos) ) - { - if ( (0 == strcasecmp (pos->debit_account, - account)) && - (T_DEBIT == pos->type) ) - { - json_t *trans; - char *credit_payto; - char *debit_payto; - - GNUNET_asprintf (&credit_payto, - "payto://x-taler-bank/localhost/%s", - pos->credit_account); - - GNUNET_asprintf (&debit_payto, - "payto://x-taler-bank/localhost/%s", - pos->debit_account); - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "made credit_payto (%s) from credit_account (%s) within fakebank\n", - credit_payto, - pos->credit_account); - - trans = json_pack - ("{s:I, s:o, s:o, s:s, s:s, s:s, s:o}", - "row_id", (json_int_t) pos->row_id, - "date", GNUNET_JSON_from_time_abs (pos->date), - "amount", TALER_JSON_from_amount (&pos->amount), - "credit_account", credit_payto, - "debit_account", debit_payto, - "exchange_base_url", - pos->subject.debit.exchange_base_url, - "wtid", GNUNET_JSON_from_data_auto ( - &pos->subject.debit.wtid)); - GNUNET_free (credit_payto); - GNUNET_free (debit_payto); - GNUNET_assert (0 == - json_array_append_new (history, - trans)); - if (ha.delta > 0) - ha.delta--; - else - ha.delta++; - } - if (0 > ha.delta) - pos = pos->prev; - if (0 < ha.delta) - pos = pos->next; - } - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_OK, - "{s:o}", - "outgoing_transactions", - history); -} - - -/** - * Handle incoming HTTP request for /history/incoming - * - * @param h the fakebank handle - * @param connection the connection - * @param account which account the request is about - * @return MHD result code - */ -static int -handle_credit_history (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account) -{ - struct HistoryArgs ha; - const struct Transaction *pos; - json_t *history; - - if (GNUNET_OK != - parse_history_common_args (connection, - &ha)) - { - GNUNET_break (0); - return MHD_NO; - } - if (! ha.have_start) - { - pos = (0 > ha.delta) - ? h->transactions_tail - : h->transactions_head; - } - else if (NULL != h->transactions_head) - { - for (pos = h->transactions_head; - NULL != pos; - pos = pos->next) - { - if (pos->row_id == ha.start_idx) - break; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Skipping transaction %s->%s (%s) at %llu (looking for start index %llu)\n", - pos->debit_account, - pos->credit_account, - TALER_B2S (&pos->subject.credit.reserve_pub), - (unsigned long long) pos->row_id, - (unsigned long long) ha.start_idx); - } - if (NULL == pos) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid start specified, transaction %llu not known!\n", - (unsigned long long) ha.start_idx); - return MHD_NO; - } - /* range is exclusive, skip the matching entry */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Skipping transaction %s->%s (%s) (start index %llu is exclusive)\n", - pos->debit_account, - pos->credit_account, - TALER_B2S (&pos->subject.credit.reserve_pub), - (unsigned long long) ha.start_idx); - if (0 > ha.delta) - pos = pos->prev; - else - pos = pos->next; - } - else - { - /* list is empty */ - pos = NULL; - } - history = json_array (); - while ( (0 != ha.delta) && - (NULL != pos) ) - { - if ( (0 == strcasecmp (pos->credit_account, - account)) && - (T_CREDIT == pos->type) ) - { - json_t *trans; - char *credit_payto; - char *debit_payto; - - GNUNET_asprintf (&credit_payto, - "payto://x-taler-bank/localhost/%s", - pos->credit_account); - - GNUNET_asprintf (&debit_payto, - "payto://x-taler-bank/localhost/%s", - pos->debit_account); - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "made credit_payto (%s) from credit_account (%s) within fakebank\n", - credit_payto, - pos->credit_account); - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning transaction %s->%s (%s) at %llu\n", - pos->debit_account, - pos->credit_account, - TALER_B2S (&pos->subject.credit.reserve_pub), - (unsigned long long) pos->row_id); - trans = json_pack - ("{s:I, s:o, s:o, s:s, s:s, s:o}", - "row_id", (json_int_t) pos->row_id, - "date", GNUNET_JSON_from_time_abs (pos->date), - "amount", TALER_JSON_from_amount (&pos->amount), - "credit_account", credit_payto, - "debit_account", debit_payto, - "reserve_pub", GNUNET_JSON_from_data_auto ( - &pos->subject.credit.reserve_pub)); - GNUNET_free (credit_payto); - GNUNET_free (debit_payto); - GNUNET_assert (0 == - json_array_append_new (history, - trans)); - if (ha.delta > 0) - ha.delta--; - else - ha.delta++; - } - else if (T_CREDIT == pos->type) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Skipping transaction %s->%s (%s) at row %llu\n", - pos->debit_account, - pos->credit_account, - TALER_B2S (&pos->subject.credit.reserve_pub), - (unsigned long long) pos->row_id); - } - if (0 > ha.delta) - pos = pos->prev; - if (0 < ha.delta) - pos = pos->next; - } - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_OK, - "{s:o}", - "incoming_transactions", - history); -} - - -/** - * Handle incoming HTTP request. - * - * @param h our handle - * @param connection the connection - * @param url the requested url - * @param method the method (POST, GET, ...) - * @param account which account should process the request - * @param upload_data request data - * @param upload_data_size size of @a upload_data in bytes - * @param con_cls closure for request (a `struct Buffer *`) - * @return MHD result code - */ -static int -serve (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account, - const char *url, - const char *method, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) -{ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Fakebank, serving URL `%s' for account `%s'\n", - url, - account); - if ( (0 == strcmp (url, - "/")) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) ) - return handle_home_page (h, - connection, - con_cls); - if ( (0 == strcmp (url, - "/admin/add-incoming")) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_POST)) ) - return handle_admin_add_incoming (h, - connection, - account, - upload_data, - upload_data_size, - con_cls); - if ( (0 == strcmp (url, - "/transfer")) && - (NULL != account) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_POST)) ) - return handle_transfer (h, - connection, - account, - upload_data, - upload_data_size, - con_cls); - if ( (0 == strcmp (url, - "/history/incoming")) && - (NULL != account) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) ) - return handle_credit_history (h, - connection, - account); - if ( (0 == strcmp (url, - "/history/outgoing")) && - (NULL != account) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) ) - return handle_debit_history (h, - connection, - account); - - /* Unexpected URL path, just close the connection. */ - /* we're rather impolite here, but it's a testcase. */ - TALER_LOG_ERROR ("Breaking URL: %s\n", - url); - GNUNET_break_op (0); - return MHD_NO; + (void) connection; + (void) toe; + if (NULL == cc) + return; + cc->ctx_cleaner (cc->ctx); + GNUNET_free (cc); } @@ -1276,10 +80,10 @@ serve (struct TALER_FAKEBANK_Handle *h, * @param version HTTP version (ignored) * @param upload_data request data * @param upload_data_size size of @a upload_data in bytes - * @param con_cls closure for request (a `struct Buffer *`) + * @param con_cls closure for request * @return MHD result code */ -static int +static MHD_RESULT handle_mhd_request (void *cls, struct MHD_Connection *connection, const char *url, @@ -1290,43 +94,31 @@ handle_mhd_request (void *cls, void **con_cls) { struct TALER_FAKEBANK_Handle *h = cls; - char *account = NULL; - char *end; - int ret; (void) version; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Handling request for `%s'\n", - url); - if ( (strlen (url) > 1) && - (NULL != (end = strchr (url + 1, '/'))) ) + if (0 == strncmp (url, + "/taler-integration/", + strlen ("/taler-integration/"))) { - account = GNUNET_strndup (url + 1, - end - url - 1); - url = end; + url += strlen ("/taler-integration"); + return TALER_FAKEBANK_tbi_main_ (h, + connection, + url, + method, + upload_data, + upload_data_size, + con_cls); } - ret = serve (h, - connection, - account, - url, - method, - upload_data, - upload_data_size, - con_cls); - GNUNET_free_non_null (account); - return ret; + return TALER_FAKEBANK_bank_main_ (h, + connection, + url, + method, + upload_data, + upload_data_size, + con_cls); } -/** - * Task run whenever HTTP server operations are pending. - * - * @param cls the `struct TALER_FAKEBANK_Handle` - */ -static void -run_mhd (void *cls); - - #if EPOLL_SUPPORT /** * Schedule MHD. This function should be called initially when an @@ -1342,6 +134,7 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h) MHD_UNSIGNED_LONG_LONG timeout; struct GNUNET_TIME_Relative tv; + GNUNET_assert (-1 != h->mhd_fd); haveto = MHD_get_timeout (h->mhd_bank, &timeout); if (MHD_YES == haveto) @@ -1353,7 +146,7 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h) h->mhd_task = GNUNET_SCHEDULER_add_read_net (tv, h->mhd_rfd, - &run_mhd, + &TALER_FAKEBANK_run_mhd_, h); } @@ -1379,16 +172,27 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h) MHD_UNSIGNED_LONG_LONG timeout; struct GNUNET_TIME_Relative tv; +#ifdef __linux__ + GNUNET_assert (-1 == h->lp_event); +#else + GNUNET_assert (-1 == h->lp_event_in); + GNUNET_assert (-1 == h->lp_event_out); +#endif FD_ZERO (&rs); FD_ZERO (&ws); FD_ZERO (&es); max = -1; - if (MHD_YES != MHD_get_fdset (h->mhd_bank, &rs, &ws, &es, &max)) + if (MHD_YES != MHD_get_fdset (h->mhd_bank, + &rs, + &ws, + &es, + &max)) { GNUNET_assert (0); return; } - haveto = MHD_get_timeout (h->mhd_bank, &timeout); + haveto = MHD_get_timeout (h->mhd_bank, + &timeout); if (MHD_YES == haveto) tv.rel_value_us = (uint64_t) timeout * 1000LL; else @@ -1397,8 +201,12 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h) { wrs = GNUNET_NETWORK_fdset_create (); wws = GNUNET_NETWORK_fdset_create (); - GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1); - GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1); + GNUNET_NETWORK_fdset_copy_native (wrs, + &rs, + max + 1); + GNUNET_NETWORK_fdset_copy_native (wws, + &ws, + max + 1); } else { @@ -1412,7 +220,8 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h) tv, wrs, wws, - &run_mhd, h); + &TALER_FAKEBANK_run_mhd_, + h); if (NULL != wrs) GNUNET_NETWORK_fdset_destroy (wrs); if (NULL != wws) @@ -1428,71 +237,258 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h) * * @param cls the `struct TALER_FAKEBANK_Handle` */ -static void -run_mhd (void *cls) +void +TALER_FAKEBANK_run_mhd_ (void *cls) { struct TALER_FAKEBANK_Handle *h = cls; h->mhd_task = NULL; - MHD_run (h->mhd_bank); + h->mhd_again = true; + while (h->mhd_again) + { + h->mhd_again = false; + MHD_run (h->mhd_bank); + } +#ifdef __linux__ + GNUNET_assert (-1 == h->lp_event); +#else + GNUNET_assert (-1 == h->lp_event_in); + GNUNET_assert (-1 == h->lp_event_out); +#endif schedule_httpd (h); } -/** - * Start the fake bank. The fake bank will, like the normal bank, listen for - * requests for /admin/add/incoming and /transfer. However, instead of - * executing or storing those requests, it will simply allow querying whether - * such a request has been made via #TALER_FAKEBANK_check_debit() and - * #TALER_FAKEBANK_check_credit() as well as the history API. - * - * This is useful for writing testcases to check whether the exchange - * would have issued the correct wire transfer orders. - * - * @param port port to listen to - * @param currency currency the bank uses - * @return NULL on error - */ struct TALER_FAKEBANK_Handle * TALER_FAKEBANK_start (uint16_t port, const char *currency) { + return TALER_FAKEBANK_start2 (port, + currency, + 65536, /* RAM limit */ + 1); +} + + +struct TALER_FAKEBANK_Handle * +TALER_FAKEBANK_start2 (uint16_t port, + const char *currency, + uint64_t ram_limit, + unsigned int num_threads) +{ + struct TALER_Amount zero; + + if (GNUNET_OK != + TALER_amount_set_zero (currency, + &zero)) + { + GNUNET_break (0); + return NULL; + } + return TALER_FAKEBANK_start3 ("localhost", + port, + NULL, + currency, + ram_limit, + num_threads, + &zero); +} + + +struct TALER_FAKEBANK_Handle * +TALER_FAKEBANK_start3 (const char *hostname, + uint16_t port, + const char *exchange_url, + const char *currency, + uint64_t ram_limit, + unsigned int num_threads, + const struct TALER_Amount *signup_bonus) +{ struct TALER_FAKEBANK_Handle *h; + if (SIZE_MAX / sizeof (struct Transaction *) < ram_limit) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "This CPU architecture does not support keeping %llu transactions in RAM\n", + (unsigned long long) ram_limit); + return NULL; + } GNUNET_assert (strlen (currency) < TALER_CURRENCY_LEN); + if (0 != strcmp (signup_bonus->currency, + currency)) + { + GNUNET_break (0); + return NULL; + } h = GNUNET_new (struct TALER_FAKEBANK_Handle); + h->signup_bonus = *signup_bonus; + if (NULL != exchange_url) + h->exchange_url = GNUNET_strdup (exchange_url); +#ifdef __linux__ + h->lp_event = -1; +#else + h->lp_event_in = -1; + h->lp_event_out = -1; +#endif +#if EPOLL_SUPPORT + h->mhd_fd = -1; +#endif h->port = port; + h->ram_limit = ram_limit; + h->serial_counter = 0; + GNUNET_assert (0 == + pthread_mutex_init (&h->accounts_lock, + NULL)); + GNUNET_assert (0 == + pthread_mutex_init (&h->rpubs_lock, + NULL)); + GNUNET_assert (0 == + pthread_mutex_init (&h->uuid_map_lock, + NULL)); + GNUNET_assert (0 == + pthread_mutex_init (&h->big_lock, + NULL)); + h->transactions + = GNUNET_malloc_large (sizeof (struct Transaction *) + * ram_limit); + if (NULL == h->transactions) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "malloc"); + TALER_FAKEBANK_stop (h); + return NULL; + } + h->accounts = GNUNET_CONTAINER_multihashmap_create (128, + GNUNET_NO); + h->uuid_map = GNUNET_CONTAINER_multihashmap_create (ram_limit * 4 / 3, + GNUNET_YES); + if (NULL == h->uuid_map) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "malloc"); + TALER_FAKEBANK_stop (h); + return NULL; + } + h->rpubs = GNUNET_CONTAINER_multipeermap_create (ram_limit * 4 / 3, + GNUNET_NO); + if (NULL == h->rpubs) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "malloc"); + TALER_FAKEBANK_stop (h); + return NULL; + } + h->lp_heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN); h->currency = GNUNET_strdup (currency); + h->hostname = GNUNET_strdup (hostname); GNUNET_asprintf (&h->my_baseurl, - "http://localhost:%u/", + "http://%s:%u/", + h->hostname, (unsigned int) port); - h->mhd_bank = MHD_start_daemon (MHD_USE_DEBUG + if (0 == num_threads) + { + h->mhd_bank = MHD_start_daemon ( + MHD_USE_DEBUG #if EPOLL_SUPPORT - | MHD_USE_EPOLL_INTERNAL_THREAD -#else - | MHD_USE_INTERNAL_POLLING_THREAD + | MHD_USE_EPOLL #endif - | MHD_USE_DUAL_STACK, - port, - NULL, NULL, - &handle_mhd_request, h, - MHD_OPTION_NOTIFY_COMPLETED, - &handle_mhd_completion_callback, h, - MHD_OPTION_LISTEN_BACKLOG_SIZE, - (unsigned int) 1024, - MHD_OPTION_END); - if (NULL == h->mhd_bank) - { - GNUNET_free (h->currency); - GNUNET_free (h); - return NULL; - } + | MHD_USE_DUAL_STACK + | MHD_ALLOW_SUSPEND_RESUME, + port, + NULL, NULL, + &handle_mhd_request, h, + MHD_OPTION_NOTIFY_COMPLETED, + &handle_mhd_completion_callback, h, + MHD_OPTION_LISTEN_BACKLOG_SIZE, + (unsigned int) 1024, + MHD_OPTION_CONNECTION_LIMIT, + (unsigned int) 65536, + MHD_OPTION_END); + if (NULL == h->mhd_bank) + { + TALER_FAKEBANK_stop (h); + return NULL; + } #if EPOLL_SUPPORT - h->mhd_fd = MHD_get_daemon_info (h->mhd_bank, - MHD_DAEMON_INFO_EPOLL_FD)->epoll_fd; - h->mhd_rfd = GNUNET_NETWORK_socket_box_native (h->mhd_fd); + h->mhd_fd = MHD_get_daemon_info (h->mhd_bank, + MHD_DAEMON_INFO_EPOLL_FD)->epoll_fd; + h->mhd_rfd = GNUNET_NETWORK_socket_box_native (h->mhd_fd); #endif - schedule_httpd (h); + schedule_httpd (h); + } + else + { +#ifdef __linux__ + h->lp_event = eventfd (0, + EFD_CLOEXEC); + if (-1 == h->lp_event) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "eventfd"); + TALER_FAKEBANK_stop (h); + return NULL; + } +#else + { + int pipefd[2]; + + if (0 != pipe (pipefd)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "pipe"); + TALER_FAKEBANK_stop (h); + return NULL; + } + h->lp_event_out = pipefd[0]; + h->lp_event_in = pipefd[1]; + } +#endif + if (0 != + pthread_create (&h->lp_thread, + NULL, + &TALER_FAKEBANK_lp_expiration_thread_, + h)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "pthread_create"); +#ifdef __linux__ + GNUNET_break (0 == close (h->lp_event)); + h->lp_event = -1; +#else + GNUNET_break (0 == close (h->lp_event_in)); + GNUNET_break (0 == close (h->lp_event_out)); + h->lp_event_in = -1; + h->lp_event_out = -1; +#endif + TALER_FAKEBANK_stop (h); + return NULL; + } + h->mhd_bank = MHD_start_daemon ( + MHD_USE_DEBUG + | MHD_USE_AUTO_INTERNAL_THREAD + | MHD_ALLOW_SUSPEND_RESUME + | MHD_USE_TURBO + | MHD_USE_TCP_FASTOPEN + | MHD_USE_DUAL_STACK, + port, + NULL, NULL, + &handle_mhd_request, h, + MHD_OPTION_NOTIFY_COMPLETED, + &handle_mhd_completion_callback, h, + MHD_OPTION_LISTEN_BACKLOG_SIZE, + (unsigned int) 1024, + MHD_OPTION_CONNECTION_LIMIT, + (unsigned int) 65536, + MHD_OPTION_THREAD_POOL_SIZE, + num_threads, + MHD_OPTION_END); + if (NULL == h->mhd_bank) + { + GNUNET_break (0); + TALER_FAKEBANK_stop (h); + return NULL; + } + } return h; } |