From 48e58124fb61ded372f147d00d112f108c997f81 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 21 Sep 2023 00:01:45 +0200 Subject: major refactor of fakebank implementation --- src/bank-lib/Makefile.am | 29 +- src/bank-lib/bank_api_admin.c | 4 +- src/bank-lib/fakebank.c | 4540 +------------------- src/bank-lib/fakebank.h | 696 +++ src/bank-lib/fakebank_api_check.c | 238 + src/bank-lib/fakebank_bank.c | 224 + src/bank-lib/fakebank_bank.h | 54 + src/bank-lib/fakebank_bank_accounts_withdrawals.c | 101 + src/bank-lib/fakebank_bank_accounts_withdrawals.h | 50 + src/bank-lib/fakebank_bank_get_accounts.c | 80 + src/bank-lib/fakebank_bank_get_accounts.h | 48 + .../fakebank_bank_get_accounts_withdrawals.c | 111 + .../fakebank_bank_get_accounts_withdrawals.h | 53 + src/bank-lib/fakebank_bank_get_root.c | 58 + src/bank-lib/fakebank_bank_get_root.h | 44 + .../fakebank_bank_post_accounts_withdrawals.c | 196 + .../fakebank_bank_post_accounts_withdrawals.h | 54 + ...fakebank_bank_post_accounts_withdrawals_abort.c | 97 + ...fakebank_bank_post_accounts_withdrawals_abort.h | 50 + ...kebank_bank_post_accounts_withdrawals_confirm.c | 139 + ...kebank_bank_post_accounts_withdrawals_confirm.h | 50 + src/bank-lib/fakebank_bank_testing_register.c | 128 + src/bank-lib/fakebank_bank_testing_register.h | 53 + src/bank-lib/fakebank_common_lookup.c | 103 + src/bank-lib/fakebank_common_lookup.h | 62 + src/bank-lib/fakebank_common_lp.c | 344 ++ src/bank-lib/fakebank_common_lp.h | 100 + src/bank-lib/fakebank_common_make_admin_transfer.c | 117 + src/bank-lib/fakebank_common_make_admin_transfer.h | 56 + src/bank-lib/fakebank_common_parser.c | 138 + src/bank-lib/fakebank_common_parser.h | 50 + src/bank-lib/fakebank_common_transact.c | 261 ++ src/bank-lib/fakebank_common_transact.h | 76 + src/bank-lib/fakebank_stop.c | 192 + src/bank-lib/fakebank_tbi.c | 126 + src/bank-lib/fakebank_tbi.h | 54 + .../fakebank_tbi_get_withdrawal_operation.c | 127 + .../fakebank_tbi_get_withdrawal_operation.h | 51 + .../fakebank_tbi_post_withdrawal_operation.c | 221 + .../fakebank_tbi_post_withdrawal_operation.h | 53 + src/bank-lib/fakebank_tbr.c | 74 + src/bank-lib/fakebank_tbr.h | 58 + src/bank-lib/fakebank_tbr_get_history.c | 305 ++ src/bank-lib/fakebank_tbr_get_history.h | 52 + src/bank-lib/fakebank_tbr_get_root.c | 50 + src/bank-lib/fakebank_tbr_get_root.h | 45 + src/bank-lib/fakebank_twg.c | 106 + src/bank-lib/fakebank_twg.h | 56 + src/bank-lib/fakebank_twg_admin_add_incoming.c | 160 + src/bank-lib/fakebank_twg_admin_add_incoming.h | 52 + src/bank-lib/fakebank_twg_get_root.c | 58 + src/bank-lib/fakebank_twg_get_root.h | 46 + src/bank-lib/fakebank_twg_history.c | 537 +++ src/bank-lib/fakebank_twg_history.h | 67 + src/bank-lib/fakebank_twg_transfer.c | 178 + src/bank-lib/fakebank_twg_transfer.h | 55 + 56 files changed, 6699 insertions(+), 4428 deletions(-) create mode 100644 src/bank-lib/fakebank.h create mode 100644 src/bank-lib/fakebank_api_check.c create mode 100644 src/bank-lib/fakebank_bank.c create mode 100644 src/bank-lib/fakebank_bank.h create mode 100644 src/bank-lib/fakebank_bank_accounts_withdrawals.c create mode 100644 src/bank-lib/fakebank_bank_accounts_withdrawals.h create mode 100644 src/bank-lib/fakebank_bank_get_accounts.c create mode 100644 src/bank-lib/fakebank_bank_get_accounts.h create mode 100644 src/bank-lib/fakebank_bank_get_accounts_withdrawals.c create mode 100644 src/bank-lib/fakebank_bank_get_accounts_withdrawals.h create mode 100644 src/bank-lib/fakebank_bank_get_root.c create mode 100644 src/bank-lib/fakebank_bank_get_root.h create mode 100644 src/bank-lib/fakebank_bank_post_accounts_withdrawals.c create mode 100644 src/bank-lib/fakebank_bank_post_accounts_withdrawals.h create mode 100644 src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.c create mode 100644 src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.h create mode 100644 src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.c create mode 100644 src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.h create mode 100644 src/bank-lib/fakebank_bank_testing_register.c create mode 100644 src/bank-lib/fakebank_bank_testing_register.h create mode 100644 src/bank-lib/fakebank_common_lookup.c create mode 100644 src/bank-lib/fakebank_common_lookup.h create mode 100644 src/bank-lib/fakebank_common_lp.c create mode 100644 src/bank-lib/fakebank_common_lp.h create mode 100644 src/bank-lib/fakebank_common_make_admin_transfer.c create mode 100644 src/bank-lib/fakebank_common_make_admin_transfer.h create mode 100644 src/bank-lib/fakebank_common_parser.c create mode 100644 src/bank-lib/fakebank_common_parser.h create mode 100644 src/bank-lib/fakebank_common_transact.c create mode 100644 src/bank-lib/fakebank_common_transact.h create mode 100644 src/bank-lib/fakebank_stop.c create mode 100644 src/bank-lib/fakebank_tbi.c create mode 100644 src/bank-lib/fakebank_tbi.h create mode 100644 src/bank-lib/fakebank_tbi_get_withdrawal_operation.c create mode 100644 src/bank-lib/fakebank_tbi_get_withdrawal_operation.h create mode 100644 src/bank-lib/fakebank_tbi_post_withdrawal_operation.c create mode 100644 src/bank-lib/fakebank_tbi_post_withdrawal_operation.h create mode 100644 src/bank-lib/fakebank_tbr.c create mode 100644 src/bank-lib/fakebank_tbr.h create mode 100644 src/bank-lib/fakebank_tbr_get_history.c create mode 100644 src/bank-lib/fakebank_tbr_get_history.h create mode 100644 src/bank-lib/fakebank_tbr_get_root.c create mode 100644 src/bank-lib/fakebank_tbr_get_root.h create mode 100644 src/bank-lib/fakebank_twg.c create mode 100644 src/bank-lib/fakebank_twg.h create mode 100644 src/bank-lib/fakebank_twg_admin_add_incoming.c create mode 100644 src/bank-lib/fakebank_twg_admin_add_incoming.h create mode 100644 src/bank-lib/fakebank_twg_get_root.c create mode 100644 src/bank-lib/fakebank_twg_get_root.h create mode 100644 src/bank-lib/fakebank_twg_history.c create mode 100644 src/bank-lib/fakebank_twg_history.h create mode 100644 src/bank-lib/fakebank_twg_transfer.c create mode 100644 src/bank-lib/fakebank_twg_transfer.h diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am index ffd428a6f..783ed825c 100644 --- a/src/bank-lib/Makefile.am +++ b/src/bank-lib/Makefile.am @@ -58,7 +58,34 @@ libtalerfakebank_la_LDFLAGS = \ -version-info 0:0:0 \ -no-undefined libtalerfakebank_la_SOURCES = \ - fakebank.c + fakebank.c fakebank.h \ + fakebank_api_check.c \ + fakebank_common_lookup.c fakebank_common_lookup.h \ + fakebank_common_lp.c fakebank_common_lp.h \ + fakebank_common_make_admin_transfer.c fakebank_common_make_admin_transfer.h \ + fakebank_common_parser.c fakebank_common_parser.h \ + fakebank_common_transact.c fakebank_common_transact.h \ + fakebank_stop.c \ + fakebank_bank.c fakebank_bank.h \ + fakebank_bank_accounts_withdrawals.c fakebank_bank_accounts_withdrawals.h \ + fakebank_bank_get_accounts.c fakebank_bank_get_accounts.h \ + fakebank_bank_get_accounts_withdrawals.c fakebank_bank_get_accounts_withdrawals.h \ + fakebank_bank_get_root.c fakebank_bank_get_root.h \ + fakebank_bank_post_accounts_withdrawals.c fakebank_bank_post_accounts_withdrawals.h \ + fakebank_bank_post_accounts_withdrawals_abort.c fakebank_bank_post_accounts_withdrawals_abort.h \ + fakebank_bank_post_accounts_withdrawals_confirm.c fakebank_bank_post_accounts_withdrawals_confirm.h \ + fakebank_bank_testing_register.c fakebank_bank_testing_register.h \ + fakebank_tbr.c fakebank_tbr.h \ + fakebank_tbr_get_history.c fakebank_tbr_get_history.h \ + fakebank_tbr_get_root.c fakebank_tbr_get_root.h \ + fakebank_tbi.c fakebank_tbi.h \ + fakebank_tbi_get_withdrawal_operation.c fakebank_tbi_get_withdrawal_operation.h \ + fakebank_tbi_post_withdrawal_operation.c fakebank_tbi_post_withdrawal_operation.h \ + fakebank_twg.c fakebank_twg.h \ + fakebank_twg_admin_add_incoming.c fakebank_twg_admin_add_incoming.h \ + fakebank_twg_get_root.c fakebank_twg_get_root.h \ + fakebank_twg_history.c fakebank_twg_history.h \ + fakebank_twg_transfer.c fakebank_twg_transfer.h libtalerfakebank_la_LIBADD = \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/mhd/libtalermhd.la \ diff --git a/src/bank-lib/bank_api_admin.c b/src/bank-lib/bank_api_admin.c index 0b8e80e98..47d8a5c6d 100644 --- a/src/bank-lib/bank_api_admin.c +++ b/src/bank-lib/bank_api_admin.c @@ -16,7 +16,7 @@ */ /** * @file bank-lib/bank_api_admin.c - * @brief Implementation of the /admin/ requests of the bank's HTTP API + * @brief Implementation of the /admin/add-incoming requests of the bank's HTTP API * @author Christian Grothoff */ #include "platform.h" @@ -27,7 +27,7 @@ /** - * @brief An admin/add-incoming Handle + * @brief An /admin/add-incoming Handle */ struct TALER_BANK_AdminAddIncomingHandle { diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c index d0fbf7691..846695571 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-2022 Taler Systems SA + (C) 2016-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -21,4366 +21,54 @@ * @brief library that fakes being a Taler bank for testcases * @author Christian Grothoff */ -// TODO: support adding WAD transfers - -#include "platform.h" -#include -#include -#ifdef __linux__ -#include -#endif -#include "taler_fakebank_lib.h" -#include "taler_bank_service.h" -#include "taler_mhd_lib.h" -#include - -/** - * Maximum POST request size (for /admin/add-incoming) - */ -#define REQUEST_BUFFER_MAX (4 * 1024) - -/** - * How long are exchange base URLs allowed to be at most? - * Set to a relatively low number as this does contribute - * significantly to our RAM consumption. - */ -#define MAX_URL_LEN 64 - -/** - * Per account information. - */ -struct Account; - - -/** - * Types of long polling activities. - */ -enum LongPollType -{ - /** - * Transfer TO the exchange. - */ - LP_CREDIT, - - /** - * Transfer FROM the exchange. - */ - LP_DEBIT, - - /** - * Withdraw operation completion/abort. - */ - LP_WITHDRAW - -}; - -/** - * Client waiting for activity on this account. - */ -struct LongPoller -{ - - /** - * Kept in a DLL. - */ - struct LongPoller *next; - - /** - * Kept in a DLL. - */ - struct LongPoller *prev; - - /** - * Account this long poller is waiting on. - */ - struct Account *account; - - /** - * Withdraw operation we are waiting on, - * only if @e type is #LP_WITHDRAW, otherwise NULL. - */ - const struct WithdrawalOperation *wo; - - /** - * Entry in the heap for this long poller. - */ - struct GNUNET_CONTAINER_HeapNode *hn; - - /** - * Client that is waiting for transactions. - */ - struct MHD_Connection *conn; - - /** - * When will this long poller time out? - */ - struct GNUNET_TIME_Absolute timeout; - - /** - * What does the @e connection wait for? - */ - enum LongPollType type; - -}; - - -/** - * Details about a transcation we (as the simulated bank) received. - */ -struct Transaction; - - -/** - * Information we keep per withdraw operation. - */ -struct WithdrawalOperation -{ - /** - * Unique (random) operation ID. - */ - struct GNUNET_ShortHashCode wopid; - - /** - * Debited account. - */ - struct Account *debit_account; - - /** - * Target exchange account, or NULL if unknown. - */ - const struct Account *exchange_account; - - /** - * RowID of the resulting transaction, if any. Otherwise 0. - */ - uint64_t row_id; - - /** - * Amount transferred. - */ - struct TALER_Amount amount; - - /** - * Public key of the reserve, wire transfer subject. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * When was the transaction made? 0 if not yet. - */ - struct GNUNET_TIME_Timestamp timestamp; - - /** - * Was the withdrawal aborted? - */ - bool aborted; - - /** - * Did the bank confirm the withdrawal? - */ - bool confirmation_done; - - /** - * Is @e reserve_pub initialized? - */ - bool selection_done; - -}; - - -/** - * Per account information. - */ -struct Account -{ - - /** - * Inbound transactions for this account in a MDLL. - */ - struct Transaction *in_head; - - /** - * Inbound transactions for this account in a MDLL. - */ - struct Transaction *in_tail; - - /** - * Outbound transactions for this account in a MDLL. - */ - struct Transaction *out_head; - - /** - * Outbound transactions for this account in a MDLL. - */ - struct Transaction *out_tail; - - /** - * Kept in a DLL. - */ - struct LongPoller *lp_head; - - /** - * Kept in a DLL. - */ - struct LongPoller *lp_tail; - - /** - * Account name (string, not payto!) - */ - char *account_name; - - /** - * Receiver name for payto:// URIs. - */ - char *receiver_name; - - /** - * Payto URI for this account. - */ - char *payto_uri; - - /** - * Password set for the account (if any). - */ - char *password; - - /** - * Current account balance. - */ - struct TALER_Amount balance; - - /** - * true if the balance is negative. - */ - bool is_negative; - -}; - - -/** - * Details about a transcation we (as the simulated bank) received. - */ -struct Transaction -{ - /** - * We store inbound transactions in a MDLL. - */ - struct Transaction *next_in; - - /** - * We store inbound transactions in a MDLL. - */ - struct Transaction *prev_in; - - /** - * We store outbound transactions in a MDLL. - */ - struct Transaction *next_out; - - /** - * We store outbound transactions in a MDLL. - */ - struct Transaction *prev_out; - - /** - * Amount to be transferred. - */ - struct TALER_Amount amount; - - /** - * Account to debit. - */ - struct Account *debit_account; - - /** - * Account to credit. - */ - struct Account *credit_account; - - /** - * Random unique identifier for the request. - * Used to detect idempotent requests. - */ - struct GNUNET_HashCode request_uid; - - /** - * When did the transaction happen? - */ - struct GNUNET_TIME_Timestamp date; - - /** - * Number of this transaction. - */ - uint64_t row_id; - - /** - * What does the @e subject contain? - */ - enum - { - /** - * Transfer TO the exchange. - */ - T_CREDIT, - - /** - * Transfer FROM the exchange. - */ - T_DEBIT, - - /** - * Exchange-to-exchange WAD transfer. - */ - T_WAD, - } 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[MAX_URL_LEN]; - - } debit; - - /** - * Used if @e type is T_CREDIT. - */ - struct - { - - /** - * Reserve public key of the credit operation. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - } credit; - - /** - * Used if @e type is T_WAD. - */ - struct - { - - /** - * Subject of the transfer. - */ - struct TALER_WadIdentifierP wad; - - /** - * Base URL of the originating exchange. - */ - char origin_base_url[MAX_URL_LEN]; - - } wad; - - } subject; - - /** - * Has this transaction not yet been subjected to - * #TALER_FAKEBANK_check_credit() or #TALER_FAKEBANK_check_debit() and - * should thus be counted in #TALER_FAKEBANK_check_empty()? - */ - bool unchecked; -}; - - -/** - * Function called to clean up context of a connection. - * - * @param ctx context to clean up - */ -typedef void -(*ConnectionCleaner)(void *ctx); - -/** - * Universal context we keep per connection. - */ -struct ConnectionContext -{ - /** - * Function we call upon completion to clean up. - */ - ConnectionCleaner ctx_cleaner; - - /** - * Request-handler specific context. - */ - void *ctx; -}; - - -/** - * 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, exclusive (!). - */ - 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; - - /** - * true if starting point was given. - */ - bool have_start; - -}; - - -/** - * Context we keep per history request. - */ -struct HistoryContext -{ - /** - * When does this request time out. - */ - struct GNUNET_TIME_Absolute timeout; - - /** - * Client arguments for this request. - */ - struct HistoryArgs ha; - - /** - * Account the request is about. - */ - struct Account *acc; - - /** - * Payto URI of the account. - */ - char *payto_uri; - - /** - * JSON object we are building to return. - */ - json_t *history; - -}; - - -/** - * Function called to clean up a history context. - * - * @param cls a `struct HistoryContext *` - */ -static void -history_cleanup (void *cls) -{ - struct HistoryContext *hc = cls; - - GNUNET_free (hc->payto_uri); - json_decref (hc->history); - GNUNET_free (hc); -} - - -/** - * Context we keep per get withdrawal operation request. - */ -struct WithdrawContext -{ - /** - * When does this request time out. - */ - struct GNUNET_TIME_Absolute timeout; - - /** - * The withdrawal operation this is about. - */ - struct WithdrawalOperation *wo; - -}; - - -/** - * Function called to clean up a withdraw context. - * - * @param cls a `struct WithdrawContext *` - */ -static void -withdraw_cleanup (void *cls) -{ - struct WithdrawContext *wc = cls; - - GNUNET_free (wc); -} - - -/** - * Handle for the fake bank. - */ -struct TALER_FAKEBANK_Handle -{ - /** - * We store transactions in a revolving array. - */ - struct Transaction **transactions; - - /** - * HTTP server we run to pretend to be the "test" bank. - */ - struct MHD_Daemon *mhd_bank; - - /** - * Task running HTTP server for the "test" bank, - * unless we are using a thread pool (then NULL). - */ - struct GNUNET_SCHEDULER_Task *mhd_task; - - /** - * Task for expiring long-polling connections, - * unless we are using a thread pool (then NULL). - */ - struct GNUNET_SCHEDULER_Task *lp_task; - - /** - * Task for expiring long-polling connections, unless we are using the - * GNUnet scheduler (then NULL). - */ - pthread_t lp_thread; - - /** - * MIN-heap of long pollers, sorted by timeout. - */ - struct GNUNET_CONTAINER_Heap *lp_heap; - - /** - * Hashmap of reserve public keys to - * `struct Transaction` with that reserve public - * key. Used to prevent public-key re-use. - */ - struct GNUNET_CONTAINER_MultiPeerMap *rpubs; - - /** - * Hashmap of short hashes (wopids) to - * `struct WithdrawalOperation`. - * Used to lookup withdrawal operations. - */ - struct GNUNET_CONTAINER_MultiShortmap *wops; - - /** - * (Base) URL to suggest for the exchange. Can - * be NULL if there is no suggestion to be made. - */ - char *exchange_url; - - /** - * Lock for accessing @a rpubs map. - */ - pthread_mutex_t rpubs_lock; - - /** - * Hashmap of hashes of account names to `struct Account`. - */ - struct GNUNET_CONTAINER_MultiHashMap *accounts; - - /** - * Lock for accessing @a accounts hash map. - */ - pthread_mutex_t accounts_lock; - - /** - * Hashmap of hashes of transaction request_uids to `struct Transaction`. - */ - struct GNUNET_CONTAINER_MultiHashMap *uuid_map; - - /** - * Lock for accessing @a uuid_map. - */ - pthread_mutex_t uuid_map_lock; - - /** - * Lock for accessing the internals of - * accounts and transaction array entries. - */ - pthread_mutex_t big_lock; - - /** - * How much money should be put into new accounts - * on /register. - */ - struct TALER_Amount signup_bonus; - - /** - * Current transaction counter. - */ - uint64_t serial_counter; - - /** - * Number of transactions we keep in memory (at most). - */ - uint64_t ram_limit; - - /** - * Currency used by the fakebank. - */ - char *currency; - - /** - * Hostname of the fakebank. - */ - char *hostname; - - /** - * BaseURL of the fakebank. - */ - char *my_baseurl; - - /** - * Our port number. - */ - uint16_t port; - -#ifdef __linux__ - /** - * Event FD to signal @a lp_thread a change in - * @a lp_heap. - */ - int lp_event; -#else - /** - * Pipe input to signal @a lp_thread a change in - * @a lp_heap. - */ - int lp_event_in; - - /** - * Pipe output to signal @a lp_thread a change in - * @a lp_heap. - */ - int lp_event_out; -#endif - - /** - * Set to true once we are shutting down. - */ - bool in_shutdown; - - /** - * Should we run MHD immediately again? - */ - bool mhd_again; - -#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 -}; - - -/** - * Task run whenever HTTP server operations are pending. - * - * @param cls the `struct TALER_FAKEBANK_Handle` - */ -static void -run_mhd (void *cls); - - -/** - * Find withdrawal operation @a wopid in @a h. - * - * @param h fakebank handle - * @param wopid withdrawal operation ID as a string - * @return NULL if operation was not found - */ -static struct WithdrawalOperation * -lookup_withdrawal_operation (struct TALER_FAKEBANK_Handle *h, - const char *wopid) -{ - struct GNUNET_ShortHashCode sh; - - if (NULL == h->wops) - return NULL; - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (wopid, - strlen (wopid), - &sh, - sizeof (sh))) - { - GNUNET_break_op (0); - return NULL; - } - return GNUNET_CONTAINER_multishortmap_get (h->wops, - &sh); -} - - -/** - * Trigger the @a lp. Frees associated resources, - * except the entry of @a lp in the timeout heap. - * Must be called while the ``big lock`` is held. - * - * @param[in] lp long poller to trigger - * @param[in,out] h fakebank handle - */ -static void -lp_trigger (struct LongPoller *lp, - struct TALER_FAKEBANK_Handle *h) -{ - struct Account *acc = lp->account; - - GNUNET_CONTAINER_DLL_remove (acc->lp_head, - acc->lp_tail, - lp); - MHD_resume_connection (lp->conn); - GNUNET_free (lp); - h->mhd_again = true; -#ifdef __linux__ - if (-1 == h->lp_event) -#else - if ( (-1 == h->lp_event_in) && - (-1 == h->lp_event_out) ) -#endif - { - if (NULL != h->mhd_task) - GNUNET_SCHEDULER_cancel (h->mhd_task); - h->mhd_task = - GNUNET_SCHEDULER_add_now (&run_mhd, - h); - } -} - - -/** - * Thread that is run to wake up connections that have hit their timeout. Runs - * until in_shutdown is set to true. Must be send signals via lp_event on - * shutdown and/or whenever the heap changes to an earlier timeout. - * - * @param cls a `struct TALER_FAKEBANK_Handle *` - * @return NULL - */ -static void * -lp_expiration_thread (void *cls) -{ - struct TALER_FAKEBANK_Handle *h = cls; - - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - while (! h->in_shutdown) - { - struct LongPoller *lp; - int timeout_ms; - - lp = GNUNET_CONTAINER_heap_peek (h->lp_heap); - while ( (NULL != lp) && - GNUNET_TIME_absolute_is_past (lp->timeout)) - { - GNUNET_assert (lp == - GNUNET_CONTAINER_heap_remove_root (h->lp_heap)); - lp_trigger (lp, - h); - lp = GNUNET_CONTAINER_heap_peek (h->lp_heap); - } - if (NULL != lp) - { - struct GNUNET_TIME_Relative rem; - unsigned long long left_ms; - - rem = GNUNET_TIME_absolute_get_remaining (lp->timeout); - left_ms = rem.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; - if (left_ms > INT_MAX) - timeout_ms = INT_MAX; - else - timeout_ms = (int) left_ms; - } - else - { - timeout_ms = -1; /* infinity */ - } - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - { - struct pollfd p = { -#ifdef __linux__ - .fd = h->lp_event, -#else - .fd = h->lp_event_out, -#endif - .events = POLLIN - }; - int ret; - - ret = poll (&p, - 1, - timeout_ms); - if (-1 == ret) - { - if (EINTR != errno) - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, - "poll"); - } - else if (1 == ret) - { - /* clear event */ - uint64_t ev; - ssize_t iret; - -#ifdef __linux__ - iret = read (h->lp_event, -#else - iret = read (h->lp_event_out, -#endif - &ev, - sizeof (ev)); - if (-1 == iret) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, - "read"); - } - else - { - GNUNET_break (sizeof (uint64_t) == iret); - } - } - } - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - } - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return NULL; -} - - -/** - * Lookup account with @a name, and if it does not exist, create it. - * - * @param[in,out] h bank to lookup account at - * @param name account name to resolve - * @param receiver_name receiver name in payto:// URI, - * NULL if the account must already exist - * @return account handle, NULL if account does not yet exist - */ -static struct Account * -lookup_account (struct TALER_FAKEBANK_Handle *h, - const char *name, - const char *receiver_name) -{ - struct GNUNET_HashCode hc; - size_t slen; - struct Account *account; - - memset (&hc, - 0, - sizeof (hc)); - slen = strlen (name); - GNUNET_CRYPTO_hash (name, - slen, - &hc); - GNUNET_assert (0 == - pthread_mutex_lock (&h->accounts_lock)); - account = GNUNET_CONTAINER_multihashmap_get (h->accounts, - &hc); - if (NULL == account) - { - if (NULL == receiver_name) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->accounts_lock)); - return NULL; - } - account = GNUNET_new (struct Account); - account->account_name = GNUNET_strdup (name); - account->receiver_name = GNUNET_strdup (receiver_name); - GNUNET_asprintf (&account->payto_uri, - "payto://x-taler-bank/%s/%s?receiver-name=%s", - h->hostname, - account->account_name, - account->receiver_name); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (h->currency, - &account->balance)); - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_put (h->accounts, - &hc, - account, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - } - GNUNET_assert (0 == - pthread_mutex_unlock (&h->accounts_lock)); - return account; -} - - -/** - * 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 (uint64_t i = 0; iram_limit; i++) - { - struct Transaction *t = h->transactions[i]; - - if (NULL == t) - continue; - if (! t->unchecked) - continue; - switch (t->type) - { - case T_DEBIT: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "%s -> %s (%s) %s (%s)\n", - t->debit_account->account_name, - t->credit_account->account_name, - TALER_amount2s (&t->amount), - t->subject.debit.exchange_base_url, - "DEBIT"); - break; - case T_CREDIT: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "%s -> %s (%s) %s (%s)\n", - t->debit_account->account_name, - t->credit_account->account_name, - TALER_amount2s (&t->amount), - TALER_B2S (&t->subject.credit.reserve_pub), - "CREDIT"); - break; - case T_WAD: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "%s -> %s (%s) %s[%s] (%s)\n", - t->debit_account->account_name, - t->credit_account->account_name, - TALER_amount2s (&t->amount), - t->subject.wad.origin_base_url, - TALER_B2S (&t->subject.wad), - "WAD"); - break; - } - } -} - - -enum GNUNET_GenericReturnValue -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) -{ - struct Account *debit_account; - struct Account *credit_account; - - GNUNET_assert (0 == - strcasecmp (want_amount->currency, - h->currency)); - debit_account = lookup_account (h, - want_debit, - NULL); - credit_account = lookup_account (h, - want_credit, - NULL); - if (NULL == debit_account) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "I wanted: %s->%s (%s) from exchange %s (DEBIT), but debit account does not even exist!\n", - want_debit, - want_credit, - TALER_amount2s (want_amount), - exchange_base_url); - return GNUNET_SYSERR; - } - if (NULL == credit_account) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "I wanted: %s->%s (%s) from exchange %s (DEBIT), but credit account does not even exist!\n", - want_debit, - want_credit, - TALER_amount2s (want_amount), - exchange_base_url); - return GNUNET_SYSERR; - } - for (struct Transaction *t = debit_account->out_tail; - NULL != t; - t = t->prev_out) - { - if ( (t->unchecked) && - (credit_account == t->credit_account) && - (T_DEBIT == t->type) && - (0 == TALER_amount_cmp (want_amount, - &t->amount)) && - (0 == strcasecmp (exchange_base_url, - t->subject.debit.exchange_base_url)) ) - { - *wtid = t->subject.debit.wtid; - t->unchecked = false; - 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; -} - - -enum GNUNET_GenericReturnValue -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) -{ - struct Account *debit_account; - struct Account *credit_account; - - GNUNET_assert (0 == strcasecmp (want_amount->currency, - h->currency)); - debit_account = lookup_account (h, - want_debit, - NULL); - credit_account = lookup_account (h, - want_credit, - NULL); - if (NULL == debit_account) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "I wanted:\n%s -> %s (%s) with subject %s (CREDIT) but debit account is unknown.\n", - want_debit, - want_credit, - TALER_amount2s (want_amount), - TALER_B2S (reserve_pub)); - return GNUNET_SYSERR; - } - if (NULL == credit_account) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "I wanted:\n%s -> %s (%s) with subject %s (CREDIT) but credit account is unknown.\n", - want_debit, - want_credit, - TALER_amount2s (want_amount), - TALER_B2S (reserve_pub)); - return GNUNET_SYSERR; - } - for (struct Transaction *t = credit_account->in_tail; - NULL != t; - t = t->prev_in) - { - if ( (t->unchecked) && - (debit_account == t->debit_account) && - (T_CREDIT == t->type) && - (0 == TALER_amount_cmp (want_amount, - &t->amount)) && - (0 == GNUNET_memcmp (reserve_pub, - &t->subject.credit.reserve_pub)) ) - { - t->unchecked = false; - 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; -} - - -/** - * Update @a account balance by @a amount. - * - * The @a big_lock must already be locked when calling - * this function. - * - * @param[in,out] account account to update - * @param amount balance change - * @param debit true to subtract, false to add @a amount - */ -static void -update_balance (struct Account *account, - const struct TALER_Amount *amount, - bool debit) -{ - if (debit == account->is_negative) - { - GNUNET_assert (0 <= - TALER_amount_add (&account->balance, - &account->balance, - amount)); - return; - } - if (0 <= TALER_amount_cmp (&account->balance, - amount)) - { - GNUNET_assert (0 <= - TALER_amount_subtract (&account->balance, - &account->balance, - amount)); - } - else - { - GNUNET_assert (0 <= - TALER_amount_subtract (&account->balance, - amount, - &account->balance)); - account->is_negative = ! account->is_negative; - } -} - - -/** - * Add transaction to the debit and credit accounts, - * updating the balances as needed. - * - * The transaction @a t must already be locked - * when calling this function! - * - * @param[in,out] h bank handle - * @param[in,out] t transaction to add to account lists - */ -static void -post_transaction (struct TALER_FAKEBANK_Handle *h, - struct Transaction *t) -{ - struct Account *debit_acc = t->debit_account; - struct Account *credit_acc = t->credit_account; - uint64_t row_id; - struct Transaction *old; - - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - row_id = ++h->serial_counter; - old = h->transactions[row_id % h->ram_limit]; - h->transactions[row_id % h->ram_limit] = t; - t->row_id = row_id; - GNUNET_CONTAINER_MDLL_insert_tail (out, - debit_acc->out_head, - debit_acc->out_tail, - t); - update_balance (debit_acc, - &t->amount, - true); - GNUNET_CONTAINER_MDLL_insert_tail (in, - credit_acc->in_head, - credit_acc->in_tail, - t); - update_balance (credit_acc, - &t->amount, - false); - if (NULL != old) - { - struct Account *da; - struct Account *ca; - - da = old->debit_account; - ca = old->credit_account; - /* slot was already in use, must clean out old - entry first! */ - GNUNET_CONTAINER_MDLL_remove (out, - da->out_head, - da->out_tail, - old); - GNUNET_CONTAINER_MDLL_remove (in, - ca->in_head, - ca->in_tail, - old); - } - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - if ( (NULL != old) && - (T_DEBIT == old->type) ) - { - GNUNET_assert (0 == - pthread_mutex_lock (&h->uuid_map_lock)); - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_remove (h->uuid_map, - &old->request_uid, - old)); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->uuid_map_lock)); - } - GNUNET_free (old); -} - - -/** - * Trigger long pollers that might have been waiting - * for @a t. - * - * @param h fakebank handle - * @param t transaction to notify on - */ -static void -notify_transaction (struct TALER_FAKEBANK_Handle *h, - struct Transaction *t) -{ - struct Account *debit_acc = t->debit_account; - struct Account *credit_acc = t->credit_account; - struct LongPoller *nxt; - - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - for (struct LongPoller *lp = debit_acc->lp_head; - NULL != lp; - lp = nxt) - { - nxt = lp->next; - if (LP_DEBIT == lp->type) - { - GNUNET_assert (lp == - GNUNET_CONTAINER_heap_remove_node (lp->hn)); - lp_trigger (lp, - h); - } - } - for (struct LongPoller *lp = credit_acc->lp_head; - NULL != lp; - lp = nxt) - { - nxt = lp->next; - if (LP_CREDIT == lp->type) - { - GNUNET_assert (lp == - GNUNET_CONTAINER_heap_remove_node (lp->hn)); - lp_trigger (lp, - h); - } - } - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); -} - - -/** - * 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 - * @param[out] timestamp set to the time of the transfer - * @return #GNUNET_YES if the transfer was successful, - * #GNUNET_SYSERR if the request_uid was reused for a different transfer - */ -static enum GNUNET_GenericReturnValue -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 GNUNET_TIME_Timestamp *timestamp) -{ - struct Transaction *t; - struct Account *debit_acc; - struct Account *credit_acc; - size_t url_len; - - 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://"))); - url_len = strlen (exchange_base_url); - GNUNET_assert (url_len < MAX_URL_LEN); - debit_acc = lookup_account (h, - debit_account, - debit_account); - credit_acc = lookup_account (h, - credit_account, - credit_account); - if (NULL != request_uid) - { - GNUNET_assert (0 == - pthread_mutex_lock (&h->uuid_map_lock)); - t = GNUNET_CONTAINER_multihashmap_get (h->uuid_map, - request_uid); - if (NULL != t) - { - if ( (debit_acc != t->debit_account) || - (credit_acc != 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); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->uuid_map_lock)); - return GNUNET_SYSERR; - } - *ret_row_id = t->row_id; - *timestamp = t->date; - GNUNET_assert (0 == - pthread_mutex_unlock (&h->uuid_map_lock)); - return GNUNET_OK; - } - GNUNET_assert (0 == - pthread_mutex_unlock (&h->uuid_map_lock)); - } - t = GNUNET_new (struct Transaction); - t->unchecked = true; - t->debit_account = debit_acc; - t->credit_account = credit_acc; - t->amount = *amount; - t->date = GNUNET_TIME_timestamp_get (); - if (NULL != timestamp) - *timestamp = t->date; - t->type = T_DEBIT; - GNUNET_memcpy (t->subject.debit.exchange_base_url, - exchange_base_url, - url_len); - 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; - post_transaction (h, - t); - GNUNET_assert (0 == - pthread_mutex_lock (&h->uuid_map_lock)); - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_put ( - h->uuid_map, - &t->request_uid, - t, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->uuid_map_lock)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Making transfer %llu from %s to %s over %s and subject %s; for exchange: %s\n", - (unsigned long long) t->row_id, - debit_account, - credit_account, - TALER_amount2s (amount), - TALER_B2S (subject), - exchange_base_url); - *ret_row_id = t->row_id; - notify_transaction (h, - t); - 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 - * @param[out] row_id serial_id of the transfer - * @param[out] timestamp when was the transfer made - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -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, - uint64_t *row_id, - struct GNUNET_TIME_Timestamp *timestamp) -{ - struct Transaction *t; - const struct GNUNET_PeerIdentity *pid; - struct Account *debit_acc; - struct Account *credit_acc; - - GNUNET_static_assert (sizeof (*pid) == - sizeof (*reserve_pub)); - pid = (const struct GNUNET_PeerIdentity *) reserve_pub; - GNUNET_assert (NULL != debit_account); - GNUNET_assert (NULL != credit_account); - 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://"))); - debit_acc = lookup_account (h, - debit_account, - debit_account); - credit_acc = lookup_account (h, - credit_account, - credit_account); - GNUNET_assert (0 == - pthread_mutex_lock (&h->rpubs_lock)); - t = GNUNET_CONTAINER_multipeermap_get (h->rpubs, - pid); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->rpubs_lock)); - if (NULL != t) - { - /* duplicate reserve public key not allowed */ - GNUNET_break_op (0); - return GNUNET_NO; - } - - t = GNUNET_new (struct Transaction); - t->unchecked = true; - t->debit_account = debit_acc; - t->credit_account = credit_acc; - t->amount = *amount; - t->date = GNUNET_TIME_timestamp_get (); - if (NULL != timestamp) - *timestamp = t->date; - t->type = T_CREDIT; - t->subject.credit.reserve_pub = *reserve_pub; - post_transaction (h, - t); - if (NULL != row_id) - *row_id = t->row_id; - GNUNET_assert (0 == - pthread_mutex_lock (&h->rpubs_lock)); - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multipeermap_put ( - h->rpubs, - pid, - t, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->rpubs_lock)); - 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); - notify_transaction (h, - t); - return GNUNET_OK; -} - - -enum GNUNET_GenericReturnValue -TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h) -{ - for (uint64_t i = 0; iram_limit; i++) - { - struct Transaction *t = h->transactions[i]; - - if ( (NULL != t) && - (t->unchecked) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Expected empty transaction set, but I have:\n"); - check_log (h); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -/** - * Helper function to free memory when finished. - * - * @param cls NULL - * @param key key of the account to free (ignored) - * @param val a `struct Account` to free. - */ -static enum GNUNET_GenericReturnValue -free_account (void *cls, - const struct GNUNET_HashCode *key, - void *val) -{ - struct Account *account = val; - - (void) cls; - (void) key; - GNUNET_assert (NULL == account->lp_head); - GNUNET_free (account->account_name); - GNUNET_free (account->receiver_name); - GNUNET_free (account->payto_uri); - GNUNET_free (account->password); - GNUNET_free (account); - return GNUNET_OK; -} - - -/** - * Helper function to free memory when finished. - * - * @param cls NULL - * @param key key of the operation to free (ignored) - * @param val a `struct WithdrawalOperation *` to free. - */ -static enum GNUNET_GenericReturnValue -free_withdraw_op (void *cls, - const struct GNUNET_ShortHashCode *key, - void *val) -{ - struct WithdrawalOperation *wo = val; - - (void) cls; - (void) key; - GNUNET_free (wo); - return GNUNET_OK; -} - - -void -TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h) -{ - if (NULL != h->lp_task) - { - GNUNET_SCHEDULER_cancel (h->lp_task); - h->lp_task = NULL; - } -#if EPOLL_SUPPORT - if (NULL != h->mhd_rfd) - { - GNUNET_NETWORK_socket_free_memory_only_ (h->mhd_rfd); - h->mhd_rfd = NULL; - } -#endif -#ifdef __linux__ - if (-1 != h->lp_event) -#else - if (-1 != h->lp_event_in && -1 != h->lp_event_out) -#endif - { - uint64_t val = 1; - void *ret; - struct LongPoller *lp; - - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - h->in_shutdown = true; - while (NULL != (lp = GNUNET_CONTAINER_heap_remove_root (h->lp_heap))) - lp_trigger (lp, - h); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - GNUNET_break (sizeof (val) == -#ifdef __linux__ - write (h->lp_event, -#else - write (h->lp_event_in, -#endif - &val, - sizeof (val))); - GNUNET_break (0 == - pthread_join (h->lp_thread, - &ret)); - GNUNET_break (NULL == ret); -#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 - } - else - { - struct LongPoller *lp; - - while (NULL != (lp = GNUNET_CONTAINER_heap_remove_root (h->lp_heap))) - lp_trigger (lp, - h); - } - if (NULL != h->mhd_bank) - { - MHD_stop_daemon (h->mhd_bank); - h->mhd_bank = NULL; - } - if (NULL != h->mhd_task) - { - GNUNET_SCHEDULER_cancel (h->mhd_task); - h->mhd_task = NULL; - } - if (NULL != h->accounts) - { - GNUNET_CONTAINER_multihashmap_iterate (h->accounts, - &free_account, - NULL); - GNUNET_CONTAINER_multihashmap_destroy (h->accounts); - } - if (NULL != h->wops) - { - GNUNET_CONTAINER_multishortmap_iterate (h->wops, - &free_withdraw_op, - NULL); - GNUNET_CONTAINER_multishortmap_destroy (h->wops); - } - GNUNET_CONTAINER_multihashmap_destroy (h->uuid_map); - GNUNET_CONTAINER_multipeermap_destroy (h->rpubs); - GNUNET_CONTAINER_heap_destroy (h->lp_heap); - GNUNET_assert (0 == - pthread_mutex_destroy (&h->big_lock)); - GNUNET_assert (0 == - pthread_mutex_destroy (&h->uuid_map_lock)); - GNUNET_assert (0 == - pthread_mutex_destroy (&h->accounts_lock)); - GNUNET_assert (0 == - pthread_mutex_destroy (&h->rpubs_lock)); - for (uint64_t i = 0; iram_limit; i++) - GNUNET_free (h->transactions[i]); - GNUNET_free (h->transactions); - GNUNET_free (h->my_baseurl); - GNUNET_free (h->currency); - GNUNET_free (h->exchange_url); - GNUNET_free (h->hostname); - GNUNET_free (h); -} - - -/** - * Function called whenever MHD is done with a request. If the - * request was a POST, we may have stored a `struct Buffer *` in the - * @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 connection connection handle - * @param con_cls value as set by the last call to - * the #MHD_AccessHandlerCallback - * @param toe reason for request termination - * @see #MHD_OPTION_NOTIFY_COMPLETED - * @ingroup request - */ -static void -handle_mhd_completion_callback (void *cls, - struct MHD_Connection *connection, - void **con_cls, - enum MHD_RequestTerminationCode toe) -{ - /* struct TALER_FAKEBANK_Handle *h = cls; */ - struct ConnectionContext *cc = *con_cls; - (void) cls; - (void) connection; - (void) toe; - if (NULL == cc) - return; - cc->ctx_cleaner (cc->ctx); - GNUNET_free (cc); -} - - -/** - * 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 ConnectionContext *`) - * @return MHD result code - */ -static MHD_RESULT -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) -{ - struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; - json_t *json; - uint64_t row_id; - struct GNUNET_TIME_Timestamp timestamp; - - if (NULL == cc) - { - cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; - *con_cls = cc; - } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - 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; - enum GNUNET_GenericReturnValue ret; - 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", - h->currency, - &amount), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - (ret = TALER_MHD_parse_json_data (connection, - json, - spec))) - { - GNUNET_break_op (0); - json_decref (json); - return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; - } - if (0 != strcasecmp (amount.currency, - h->currency)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Currency `%s' does not match our configuration\n", - amount.currency); - json_decref (json); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_GENERIC_CURRENCY_MISMATCH, - NULL); - } - debit = TALER_xtalerbank_account_from_payto (debit_account); - if (NULL == debit) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PAYTO_URI_MALFORMED, - 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)); - ret = make_admin_transfer (h, - debit, - account, - &amount, - &reserve_pub, - &row_id, - ×tamp); - GNUNET_free (debit); - if (GNUNET_OK != ret) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Reserve public key not unique\n"); - json_decref (json); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT, - NULL); - } - } - json_decref (json); - - /* Finally build response object */ - return TALER_MHD_REPLY_JSON_PACK (connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_uint64 ("row_id", - row_id), - GNUNET_JSON_pack_timestamp ("timestamp", - timestamp)); -} - - -/** - * 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 ConnectionContext *`) - * @return MHD result code - */ -static MHD_RESULT -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) -{ - struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; - json_t *json; - uint64_t row_id; - struct GNUNET_TIME_Timestamp ts; - - if (NULL == cc) - { - cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; - *con_cls = cc; - } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - 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; - enum GNUNET_GenericReturnValue ret; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("request_uid", - &uuid), - TALER_JSON_spec_amount ("amount", - h->currency, - &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 != - (ret = TALER_MHD_parse_json_data (connection, - json, - spec))) - { - GNUNET_break_op (0); - json_decref (json); - return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; - } - { - enum GNUNET_GenericReturnValue ret; - - credit = TALER_xtalerbank_account_from_payto (credit_account); - if (NULL == credit) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PAYTO_URI_MALFORMED, - credit_account); - } - ret = make_transfer (h, - account, - credit, - &amount, - &wtid, - base_url, - &uuid, - &row_id, - &ts); - if (GNUNET_OK != ret) - { - MHD_RESULT res; - char *uids; - - GNUNET_break (0); - uids = GNUNET_STRINGS_data_to_string_alloc (&uuid, - sizeof (uuid)); - json_decref (json); - res = TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED, - uids); - GNUNET_free (uids); - return res; - } - 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, - GNUNET_JSON_pack_uint64 ("row_id", - row_id), - GNUNET_JSON_pack_timestamp ("timestamp", - ts)); -} - - -/** - * Handle incoming HTTP request for / (home page). - * - * @param h the fakebank handle - * @param connection the connection - * @return MHD result code - */ -static MHD_RESULT -handle_home_page (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection) -{ - MHD_RESULT ret; - struct MHD_Response *resp; -#define HELLOMSG "Hello, Fakebank!" - - (void) h; - 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; -} - - -/** - * Parse URL history arguments, of _both_ APIs: - * /history/incoming and /history/outgoing. - * - * @param h bank handle to work on - * @param connection MHD connection. - * @param[out] ha will contain the parsed values. - * @return #GNUNET_OK only if the parsing succeeds, - * #GNUNET_SYSERR if it failed, - * #GNUNET_NO if it failed and an error was returned - */ -static enum GNUNET_GenericReturnValue -parse_history_common_args (const struct TALER_FAKEBANK_Handle *h, - 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; - char dummy; - - 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%c", - &d, - &dummy)) ) - { - /* 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_op (0); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "delta")) - ? GNUNET_NO - : GNUNET_SYSERR; - } - if ( (NULL != long_poll_ms) && - (1 != sscanf (long_poll_ms, - "%llu%c", - &lp_timeout, - &dummy)) ) - { - /* 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_op (0); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "long_poll_ms")) - ? GNUNET_NO - : GNUNET_SYSERR; - } - if ( (NULL != start) && - (1 != sscanf (start, - "%llu%c", - &sval, - &dummy)) ) - { - /* 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_op (0); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "start")) - ? GNUNET_NO - : GNUNET_SYSERR; - } - if (NULL == start) - ha->start_idx = (d > 0) ? 0 : h->serial_counter; - else - ha->start_idx = (uint64_t) sval; - ha->delta = (int64_t) d; - if (0 == ha->delta) - { - GNUNET_break_op (0); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "delta")) - ? GNUNET_NO - : GNUNET_SYSERR; - } - ha->lp_timeout - = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, - lp_timeout); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Request for %lld records from %llu\n", - (long long) ha->delta, - (unsigned long long) ha->start_idx); - return GNUNET_OK; -} - - -/** - * Task run when a long poller is about to time out. - * Only used in single-threaded mode. - * - * @param cls a `struct TALER_FAKEBANK_Handle *` - */ -static void -lp_timeout (void *cls) -{ - struct TALER_FAKEBANK_Handle *h = cls; - struct LongPoller *lp; - - h->lp_task = NULL; - while (NULL != (lp = GNUNET_CONTAINER_heap_peek (h->lp_heap))) - { - if (GNUNET_TIME_absolute_is_future (lp->timeout)) - break; - GNUNET_assert (lp == - GNUNET_CONTAINER_heap_remove_root (h->lp_heap)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Timeout reached for long poller %p\n", - lp->conn); - lp_trigger (lp, - h); - } - if (NULL == lp) - return; - h->lp_task = GNUNET_SCHEDULER_add_at (lp->timeout, - &lp_timeout, - h); -} - - -/** - * Reschedule the timeout task of @a h for time @a t. - * - * @param h fakebank handle - * @param t when will the next connection timeout expire - */ -static void -reschedule_lp_timeout (struct TALER_FAKEBANK_Handle *h, - struct GNUNET_TIME_Absolute t) -{ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Scheduling timeout task for %s\n", - GNUNET_STRINGS_absolute_time_to_string (t)); -#ifdef __linux__ - if (-1 != h->lp_event) -#else - if (-1 != h->lp_event_in && -1 != h->lp_event_out) -#endif - { - uint64_t num = 1; - - GNUNET_break (sizeof (num) == -#ifdef __linux__ - write (h->lp_event, -#else - write (h->lp_event_in, -#endif - &num, - sizeof (num))); - } - else - { - if (NULL != h->lp_task) - GNUNET_SCHEDULER_cancel (h->lp_task); - h->lp_task = GNUNET_SCHEDULER_add_at (t, - &lp_timeout, - h); - } -} - - -/** - * Start long-polling for @a connection and @a acc - * for transfers in @a dir. Must be called with the - * "big lock" held. - * - * @param[in,out] h fakebank handle - * @param[in,out] connection to suspend - * @param[in,out] acc account affected - * @param lp_timeout how long to suspend - * @param dir direction of transfers to watch for - * @param wo withdraw operation to watch, only - * if @a dir is #LP_WITHDRAW - */ -static void -start_lp (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - struct Account *acc, - struct GNUNET_TIME_Relative lp_timeout, - enum LongPollType dir, - const struct WithdrawalOperation *wo) -{ - struct LongPoller *lp; - bool toc; - - lp = GNUNET_new (struct LongPoller); - lp->account = acc; - lp->wo = wo; - lp->conn = connection; - lp->timeout = GNUNET_TIME_relative_to_absolute (lp_timeout); - lp->type = dir; - lp->hn = GNUNET_CONTAINER_heap_insert (h->lp_heap, - lp, - lp->timeout.abs_value_us); - toc = (lp == - GNUNET_CONTAINER_heap_peek (h->lp_heap)); - GNUNET_CONTAINER_DLL_insert (acc->lp_head, - acc->lp_tail, - lp); - MHD_suspend_connection (connection); - if (toc) - reschedule_lp_timeout (h, - lp->timeout); - -} - - -/** - * Handle incoming HTTP request for /history/outgoing - * - * @param h the fakebank handle - * @param connection the connection - * @param account which account the request is about - * @param con_cls closure for request - */ -static MHD_RESULT -handle_debit_history (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account, - void **con_cls) -{ - struct ConnectionContext *cc = *con_cls; - struct HistoryContext *hc; - struct Transaction *pos; - enum GNUNET_GenericReturnValue ret; - - if (NULL == cc) - { - cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &history_cleanup; - *con_cls = cc; - hc = GNUNET_new (struct HistoryContext); - cc->ctx = hc; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Handling /history/outgoing connection %p\n", - connection); - if (GNUNET_OK != - (ret = parse_history_common_args (h, - connection, - &hc->ha))) - { - GNUNET_break_op (0); - return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; - } - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - hc->acc = lookup_account (h, - account, - NULL); - if (NULL == hc->acc) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_UNKNOWN_ACCOUNT, - account); - } - GNUNET_asprintf (&hc->payto_uri, - "payto://x-taler-bank/localhost/%s?receiver-name=%s", - account, - hc->acc->receiver_name); - /* New invariant: */ - GNUNET_assert (0 == strcmp (hc->payto_uri, - hc->acc->payto_uri)); - hc->history = json_array (); - if (NULL == hc->history) - { - GNUNET_break (0); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return MHD_NO; - } - hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout); - } - else - { - hc = cc->ctx; - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - } - - if (! hc->ha.have_start) - { - pos = (0 > hc->ha.delta) - ? hc->acc->out_tail - : hc->acc->out_head; - } - else - { - struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit]; - bool overflow; - uint64_t dir; - bool skip = true; - - dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1; - overflow = (t->row_id != hc->ha.start_idx); - /* If account does not match, linear scan for - first matching account. */ - while ( (! overflow) && - (NULL != t) && - (t->debit_account != hc->acc) ) - { - skip = false; - t = h->transactions[(t->row_id + dir) % h->ram_limit]; - if ( (NULL != t) && - (t->row_id == hc->ha.start_idx) ) - overflow = true; /* full circle, give up! */ - } - if ( (NULL == t) || - overflow) - { - /* FIXME: these conditions are unclear to me. */ - if ( (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout)) && - (0 < hc->ha.delta)) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - if (overflow) - { - return TALER_MHD_reply_with_ec ( - connection, - TALER_EC_BANK_ANCIENT_TRANSACTION_GONE, - NULL); - } - goto finish; - } - if (h->in_shutdown) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - goto finish; - } - start_lp (h, - connection, - hc->acc, - GNUNET_TIME_absolute_get_remaining (hc->timeout), - LP_DEBIT, - NULL); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return MHD_YES; - } - if (t->debit_account != hc->acc) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid start specified, transaction %llu not with account %s!\n", - (unsigned long long) hc->ha.start_idx, - account); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return MHD_NO; - } - if (skip) - { - /* range is exclusive, skip the matching entry */ - if (0 > hc->ha.delta) - pos = t->prev_out; - else - pos = t->next_out; - } - else - { - pos = t; - } - } - if (NULL != pos) - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning %lld debit transactions starting (inclusive) from %llu\n", - (long long) hc->ha.delta, - (unsigned long long) pos->row_id); - while ( (0 != hc->ha.delta) && - (NULL != pos) ) - { - json_t *trans; - char *credit_payto; - - if (T_DEBIT != pos->type) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Unexpected CREDIT transaction #%llu for account `%s'\n", - (unsigned long long) pos->row_id, - account); - if (0 > hc->ha.delta) - pos = pos->prev_in; - if (0 < hc->ha.delta) - pos = pos->next_in; - continue; - } - GNUNET_asprintf (&credit_payto, - "payto://x-taler-bank/localhost/%s?receiver-name=%s", - pos->credit_account->account_name, - pos->credit_account->receiver_name); - - trans = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("row_id", - pos->row_id), - GNUNET_JSON_pack_timestamp ("date", - pos->date), - TALER_JSON_pack_amount ("amount", - &pos->amount), - GNUNET_JSON_pack_string ("credit_account", - credit_payto), - GNUNET_JSON_pack_string ("exchange_base_url", - pos->subject.debit.exchange_base_url), - GNUNET_JSON_pack_data_auto ("wtid", - &pos->subject.debit.wtid)); - GNUNET_assert (NULL != trans); - GNUNET_free (credit_payto); - GNUNET_assert (0 == - json_array_append_new (hc->history, - trans)); - if (hc->ha.delta > 0) - hc->ha.delta--; - else - hc->ha.delta++; - if (0 > hc->ha.delta) - pos = pos->prev_out; - if (0 < hc->ha.delta) - pos = pos->next_out; - } - if ( (0 == json_array_size (hc->history)) && - (! h->in_shutdown) && - (GNUNET_TIME_absolute_is_future (hc->timeout)) && - (0 < hc->ha.delta)) - { - start_lp (h, - connection, - hc->acc, - GNUNET_TIME_absolute_get_remaining (hc->timeout), - LP_DEBIT, - NULL); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return MHD_YES; - } - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); -finish: - if (0 == json_array_size (hc->history)) - { - GNUNET_break (h->in_shutdown || - (! GNUNET_TIME_absolute_is_future (hc->timeout))); - return TALER_MHD_reply_static (connection, - MHD_HTTP_NO_CONTENT, - NULL, - NULL, - 0); - } - { - json_t *h = hc->history; - - hc->history = NULL; - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_string ( - "debit_account", - hc->payto_uri), - GNUNET_JSON_pack_array_steal ( - "outgoing_transactions", - h)); - } -} - - -/** - * Handle incoming HTTP request for /history/incoming - * - * @param h the fakebank handle - * @param connection the connection - * @param account which account the request is about - * @param con_cls closure for request (NULL or &special_ptr) - * @return MHD result code - */ -static MHD_RESULT -handle_credit_history (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account, - void **con_cls) -{ - struct ConnectionContext *cc = *con_cls; - struct HistoryContext *hc; - const struct Transaction *pos; - enum GNUNET_GenericReturnValue ret; - - if (NULL == cc) - { - cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &history_cleanup; - *con_cls = cc; - hc = GNUNET_new (struct HistoryContext); - cc->ctx = hc; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Handling /history/incoming connection %p\n", - connection); - if (GNUNET_OK != - (ret = parse_history_common_args (h, - connection, - &hc->ha))) - { - GNUNET_break_op (0); - return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; - } - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - hc->acc = lookup_account (h, - account, - NULL); - if (NULL == hc->acc) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_UNKNOWN_ACCOUNT, - account); - } - /* FIXME: was simply: acc->payto_uri -- same!? */ - GNUNET_asprintf (&hc->payto_uri, - "payto://x-taler-bank/%s/%s?receiver-name=%s", - h->hostname, - account, - hc->acc->receiver_name); - GNUNET_assert (0 == strcmp (hc->payto_uri, - hc->acc->payto_uri)); - hc->history = json_array (); - if (NULL == hc->history) - { - GNUNET_break (0); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return MHD_NO; - } - hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout); - } - else - { - hc = cc->ctx; - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - } - - if (! hc->ha.have_start) - { - pos = (0 > hc->ha.delta) - ? hc->acc->in_tail - : hc->acc->in_head; - } - else - { - struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit]; - bool overflow; - uint64_t dir; - bool skip = true; - - overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) ); - dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1; - /* If account does not match, linear scan for - first matching account. */ - while ( (! overflow) && - (NULL != t) && - (t->credit_account != hc->acc) ) - { - skip = false; - t = h->transactions[(t->row_id + dir) % h->ram_limit]; - if ( (NULL != t) && - (t->row_id == hc->ha.start_idx) ) - overflow = true; /* full circle, give up! */ - } - if ( (NULL == t) || - overflow) - { - /* FIXME: these conditions are unclear to me. */ - if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) && - (0 < hc->ha.delta)) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - if (overflow) - return TALER_MHD_reply_with_ec ( - connection, - TALER_EC_BANK_ANCIENT_TRANSACTION_GONE, - NULL); - goto finish; - } - if (h->in_shutdown) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - goto finish; - } - start_lp (h, - connection, - hc->acc, - GNUNET_TIME_absolute_get_remaining (hc->timeout), - LP_CREDIT, - NULL); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return MHD_YES; - } - if (skip) - { - /* range from application is exclusive, skip the - matching entry */ - if (0 > hc->ha.delta) - pos = t->prev_in; - else - pos = t->next_in; - } - else - { - pos = t; - } - } - if (NULL != pos) - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning %lld credit transactions starting (inclusive) from %llu\n", - (long long) hc->ha.delta, - (unsigned long long) pos->row_id); - while ( (0 != hc->ha.delta) && - (NULL != pos) ) - { - json_t *trans; - - if (T_CREDIT != pos->type) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Unexpected DEBIT transaction #%llu for account `%s'\n", - (unsigned long long) pos->row_id, - account); - if (0 > hc->ha.delta) - pos = pos->prev_in; - if (0 < hc->ha.delta) - pos = pos->next_in; - continue; - } - trans = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("row_id", - pos->row_id), - GNUNET_JSON_pack_timestamp ("date", - pos->date), - TALER_JSON_pack_amount ("amount", - &pos->amount), - GNUNET_JSON_pack_string ("debit_account", - pos->debit_account->payto_uri), - GNUNET_JSON_pack_data_auto ("reserve_pub", - &pos->subject.credit.reserve_pub)); - GNUNET_assert (NULL != trans); - GNUNET_assert (0 == - json_array_append_new (hc->history, - trans)); - if (hc->ha.delta > 0) - hc->ha.delta--; - else - hc->ha.delta++; - if (0 > hc->ha.delta) - pos = pos->prev_in; - if (0 < hc->ha.delta) - pos = pos->next_in; - } - if ( (0 == json_array_size (hc->history)) && - (! h->in_shutdown) && - (GNUNET_TIME_absolute_is_future (hc->timeout)) && - (0 < hc->ha.delta)) - { - start_lp (h, - connection, - hc->acc, - GNUNET_TIME_absolute_get_remaining (hc->timeout), - LP_CREDIT, - NULL); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return MHD_YES; - } - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); -finish: - if (0 == json_array_size (hc->history)) - { - GNUNET_break (h->in_shutdown || - (! GNUNET_TIME_absolute_is_future (hc->timeout))); - return TALER_MHD_reply_static (connection, - MHD_HTTP_NO_CONTENT, - NULL, - NULL, - 0); - } - { - json_t *h = hc->history; - - hc->history = NULL; - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_string ( - "credit_account", - hc->payto_uri), - GNUNET_JSON_pack_array_steal ( - "incoming_transactions", - h)); - } -} - - -/** - * 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 - * @return MHD result code - */ -static MHD_RESULT -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 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) - { - if ( (0 == strcmp (url, - "/history/incoming")) && - (NULL != account) ) - return handle_credit_history (h, - connection, - account, - con_cls); - if ( (0 == strcmp (url, - "/history/outgoing")) && - (NULL != account) ) - return handle_debit_history (h, - connection, - account, - con_cls); - if (0 == strcmp (url, - "/")) - return handle_home_page (h, - connection); - } - else if (0 == strcasecmp (method, - MHD_HTTP_METHOD_POST)) - { - if ( (0 == strcmp (url, - "/admin/add-incoming")) && - (NULL != account) ) - return handle_admin_add_incoming (h, - connection, - account, - upload_data, - upload_data_size, - con_cls); - if ( (0 == strcmp (url, - "/transfer")) && - (NULL != account) ) - return handle_transfer (h, - connection, - account, - upload_data, - upload_data_size, - con_cls); - } - /* Unexpected URL path, just close the connection. */ - TALER_LOG_ERROR ("Breaking URL: %s %s\n", - method, - url); - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_GENERIC_ENDPOINT_UNKNOWN, - url); -} - - -/** - * Handle GET /withdrawal-operation/{wopid} request. - * - * @param h the handle - * @param connection the connection - * @param wopid the withdrawal operation identifier - * @param lp how long is the long-polling timeout - * @param con_cls closure for request - * @return MHD result code - */ -static MHD_RESULT -get_withdrawal_operation (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *wopid, - struct GNUNET_TIME_Relative lp, - void **con_cls) -{ - struct ConnectionContext *cc = *con_cls; - struct WithdrawContext *wc; - - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - if (NULL == cc) - { - cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &withdraw_cleanup; - *con_cls = cc; - wc = GNUNET_new (struct WithdrawContext); - cc->ctx = wc; - wc->wo = lookup_withdrawal_operation (h, - wopid); - if (NULL == wc->wo) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_TRANSACTION_NOT_FOUND, - wopid); - } - wc->timeout = GNUNET_TIME_relative_to_absolute (lp); - } - else - { - wc = cc->ctx; - } - if (GNUNET_TIME_absolute_is_past (wc->timeout) || - h->in_shutdown || - wc->wo->confirmation_done || - wc->wo->aborted) - { - json_t *wt; - - wt = json_array (); - GNUNET_assert (NULL != wt); - GNUNET_assert (0 == - json_array_append_new (wt, - json_string ("x-taler-bank"))); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_bool ("aborted", - wc->wo->aborted), - GNUNET_JSON_pack_bool ("selection_done", - wc->wo->selection_done), - GNUNET_JSON_pack_bool ("transfer_done", - wc->wo->confirmation_done), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("suggested_exchange", - h->exchange_url)), - TALER_JSON_pack_amount ("amount", - &wc->wo->amount), - GNUNET_JSON_pack_array_steal ("wire_types", - wt)); - } - - start_lp (h, - connection, - wc->wo->debit_account, - GNUNET_TIME_absolute_get_remaining (wc->timeout), - LP_WITHDRAW, - wc->wo); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return MHD_YES; -} - - -/** - * Handle POST /withdrawal-operation/ request. - * - * @param h our handle - * @param connection the connection - * @param wopid the withdrawal operation identifier - * @param reserve_pub public key of the reserve - * @param exchange_payto_uri payto://-URI of the exchange - * @return MHD result code - */ -static MHD_RESULT -do_post_withdrawal (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *wopid, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *exchange_payto_uri) -{ - struct WithdrawalOperation *wo; - char *credit_name; - struct Account *credit_account; - - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - wo = lookup_withdrawal_operation (h, - wopid); - if (NULL == wo) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_TRANSACTION_NOT_FOUND, - wopid); - } - if ( (wo->selection_done) && - (0 != GNUNET_memcmp (&wo->reserve_pub, - reserve_pub)) ) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT, - "reserve public key changed"); - } - { - /* check if reserve_pub is already in use */ - const struct GNUNET_PeerIdentity *pid; - - pid = (const struct GNUNET_PeerIdentity *) &wo->reserve_pub; - if (GNUNET_CONTAINER_multipeermap_contains (h->rpubs, - pid)) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT, - NULL); - } - } - credit_name = TALER_xtalerbank_account_from_payto (exchange_payto_uri); - if (NULL == credit_name) - { - GNUNET_break_op (0); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PAYTO_URI_MALFORMED, - NULL); - } - credit_account = lookup_account (h, - credit_name, - NULL); - if (NULL == credit_account) - { - MHD_RESULT res; - - GNUNET_break_op (0); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - res = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_UNKNOWN_ACCOUNT, - credit_name); - GNUNET_free (credit_name); - return res; - } - GNUNET_free (credit_name); - if ( (NULL != wo->exchange_account) && - (credit_account != wo->exchange_account) ) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT, - "exchange account changed"); - } - wo->exchange_account = credit_account; - wo->reserve_pub = *reserve_pub; - wo->selection_done = true; - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_bool ("transfer_done", - wo->confirmation_done)); -} - - -/** - * Handle POST /withdrawal-operation/ request. - * - * @param h our fakebank handle - * @param connection the connection - * @param wopid the withdrawal operation identifier - * @param upload_data request data - * @param upload_data_size size of @a upload_data in bytes - * @param con_cls closure for request - * @return MHD result code - */ -static MHD_RESULT -post_withdrawal_operation (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *wopid, - const void *upload_data, - size_t *upload_data_size, - void **con_cls) -{ - struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; - json_t *json; - MHD_RESULT res; - - if (NULL == cc) - { - cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; - *con_cls = cc; - } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - 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 TALER_ReservePublicKeyP reserve_pub; - const char *exchange_payto_url; - enum GNUNET_GenericReturnValue ret; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_pub", - &reserve_pub), - GNUNET_JSON_spec_string ("selected_exchange", - &exchange_payto_url), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - (ret = TALER_MHD_parse_json_data (connection, - json, - spec))) - { - GNUNET_break_op (0); - json_decref (json); - return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; - } - res = do_post_withdrawal (h, - connection, - wopid, - &reserve_pub, - exchange_payto_url); - } - json_decref (json); - return res; -} - - -/** - * Handle incoming HTTP request to the bank integration API. - * - * @param h our fakebank handle - * @param connection the connection - * @param url the requested url - * @param method the method (POST, GET, ...) - * @param upload_data request data - * @param upload_data_size size of @a upload_data in bytes - * @param con_cls closure for request - * @return MHD result code - */ -static MHD_RESULT -handle_bank_integration (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) -{ - if (0 == strcasecmp (method, - MHD_HTTP_METHOD_HEAD)) - method = MHD_HTTP_METHOD_GET; - if ( (0 == strcmp (url, - "/config")) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) ) - { - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_string ("version", - "0:0:0"), - GNUNET_JSON_pack_string ("currency", - h->currency), - GNUNET_JSON_pack_string ("name", - "taler-bank-integration")); - } - if ( (0 == strncmp (url, - "/withdrawal-operation/", - strlen ("/withdrawal-operation/"))) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) ) - { - const char *wopid = &url[strlen ("/withdrawal-operation/")]; - const char *lp_s - = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "long_poll_ms"); - struct GNUNET_TIME_Relative lp = GNUNET_TIME_UNIT_ZERO; - - if (NULL != lp_s) - { - unsigned long long d; - char dummy; - - if (1 != sscanf (lp_s, - "%llu%c", - &d, - &dummy)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "long_poll_ms"); - } - lp = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, - d); - } - return get_withdrawal_operation (h, - connection, - wopid, - lp, - con_cls); - - } - if ( (0 == strncmp (url, - "/withdrawal-operation/", - strlen ("/withdrawal-operation/"))) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_POST)) ) - { - const char *wopid = &url[strlen ("/withdrawal-operation/")]; - return post_withdrawal_operation (h, - connection, - wopid, - upload_data, - upload_data_size, - con_cls); - } - - TALER_LOG_ERROR ("Breaking URL: %s %s\n", - method, - url); - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_GENERIC_ENDPOINT_UNKNOWN, - url); -} - - -/** - * Handle GET /accounts/${account_name} request - * to the Taler bank access API. - * - * @param h the handle - * @param connection the connection - * @param account_name name of the account - * @return MHD result code - */ -static MHD_RESULT -get_account_access (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account_name) -{ - struct Account *acc; - - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - acc = lookup_account (h, - account_name, - NULL); - if (NULL == acc) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_UNKNOWN_ACCOUNT, - account_name); - } - - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_string ("payto_uri", - acc->payto_uri), - GNUNET_JSON_pack_object_steal ( - "balance", - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("credit_debit_indicator", - acc->is_negative - ? "debit" - : "credit"), - TALER_JSON_pack_amount ("amount", - &acc->balance)))); -} - - -/** - * Handle GET /accounts/${account_name}/withdrawals/{withdrawal_id} request - * to the Taler bank access API. - * - * @param h the handle - * @param connection the connection - * @param account_name name of the account - * @param withdrawal_id withdrawal ID to return status of - * @return MHD result code - */ -static MHD_RESULT -get_account_withdrawals_access (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account_name, - const char *withdrawal_id) -{ - struct WithdrawalOperation *wo; - struct Account *acc; - - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - wo = lookup_withdrawal_operation (h, - withdrawal_id); - if (NULL == wo) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_TRANSACTION_NOT_FOUND, - withdrawal_id); - } - acc = lookup_account (h, - account_name, - NULL); - if (NULL == acc) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_UNKNOWN_ACCOUNT, - account_name); - } - if (wo->debit_account != acc) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_TRANSACTION_NOT_FOUND, - account_name); - } - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_bool ("aborted", - wo->aborted), - GNUNET_JSON_pack_bool ("selection_done", - wo->selection_done), - GNUNET_JSON_pack_bool ("transfer_done", - wo->confirmation_done), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("selected_exchange_account", - wo->exchange_account->payto_uri)), - GNUNET_JSON_pack_allow_null ( - wo->selection_done - ? GNUNET_JSON_pack_data_auto ("selected_reserve_pub", - &wo->reserve_pub) - : GNUNET_JSON_pack_string ("selected_reserve_pub", - NULL)), - TALER_JSON_pack_amount ("amount", - &wo->amount)); -} - - -/** - * Handle POST /accounts/$account_name/withdrawals request. - * - * @param h our fakebank handle - * @param connection the connection - * @param account_name name of the account - * @param amount amont to withdraw - * @return MHD result code - */ -static MHD_RESULT -do_post_account_withdrawals_access (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account_name, - const struct TALER_Amount *amount) -{ - struct Account *acc; - struct WithdrawalOperation *wo; - - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - acc = lookup_account (h, - account_name, - NULL); - if (NULL == acc) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_UNKNOWN_ACCOUNT, - account_name); - } - wo = GNUNET_new (struct WithdrawalOperation); - wo->debit_account = acc; - wo->amount = *amount; - if (NULL == h->wops) - { - h->wops = GNUNET_CONTAINER_multishortmap_create (32, - GNUNET_YES); - } - while (1) - { - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, - &wo->wopid, - sizeof (wo->wopid)); - if (GNUNET_OK == - GNUNET_CONTAINER_multishortmap_put (h->wops, - &wo->wopid, - wo, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) - break; - } - { - char *wopids; - char *uri; - MHD_RESULT res; - - wopids = GNUNET_STRINGS_data_to_string_alloc (&wo->wopid, - sizeof (wo->wopid)); - GNUNET_asprintf (&uri, - "taler+http://withdraw/%s:%u/taler-bank-integration/%s", - h->hostname, - (unsigned int) h->port, - wopids); - GNUNET_free (wopids); - res = TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_string ("taler_withdraw_uri", - uri), - GNUNET_JSON_pack_data_auto ("withdrawal_id", - &wo->wopid)); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - GNUNET_free (uri); - return res; - } -} - - -/** - * Handle POST /accounts/$account_name/withdrawals request. - * - * @param h our fakebank handle - * @param connection the connection - * @param account_name name of the account - * @param upload_data request data - * @param upload_data_size size of @a upload_data in bytes - * @param con_cls closure for request - * @return MHD result code - */ -static MHD_RESULT -post_account_withdrawals_access (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account_name, - const void *upload_data, - size_t *upload_data_size, - void **con_cls) -{ - struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; - json_t *json; - MHD_RESULT res; - - if (NULL == cc) - { - cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; - *con_cls = cc; - } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - 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 TALER_Amount amount; - enum GNUNET_GenericReturnValue ret; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("amount", - h->currency, - &amount), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - (ret = TALER_MHD_parse_json_data (connection, - json, - spec))) - { - GNUNET_break_op (0); - json_decref (json); - return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; - } - res = do_post_account_withdrawals_access (h, - connection, - account_name, - &amount); - } - json_decref (json); - return res; -} - - -/** - * Handle POST /testing/register request. - * - * @param h our fakebank handle - * @param connection the connection - * @param upload_data request data - * @param upload_data_size size of @a upload_data in bytes - * @param con_cls closure for request - * @return MHD result code - */ -static MHD_RESULT -post_testing_register (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const void *upload_data, - size_t *upload_data_size, - void **con_cls) -{ - struct ConnectionContext *cc = *con_cls; - enum GNUNET_JSON_PostResult pr; - json_t *json; - MHD_RESULT res; - - if (NULL == cc) - { - cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; - *con_cls = cc; - } - pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, - connection, - &cc->ctx, - 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 *username; - const char *password; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("username", - &username), - GNUNET_JSON_spec_string ("password", - &password), - GNUNET_JSON_spec_end () - }; - enum GNUNET_GenericReturnValue ret; - struct Account *acc; - - if (GNUNET_OK != - (ret = TALER_MHD_parse_json_data (connection, - json, - spec))) - { - GNUNET_break_op (0); - json_decref (json); - return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; - } - acc = lookup_account (h, - username, - NULL); - if (NULL != acc) - { - if (0 != strcmp (password, - acc->password)) - { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_BANK_REGISTER_CONFLICT, - "password"); - } - } - else - { - acc = lookup_account (h, - username, - username); - GNUNET_assert (NULL != acc); - acc->password = GNUNET_strdup (password); - acc->balance = h->signup_bonus; /* magic money creation! */ - } - res = TALER_MHD_reply_static (connection, - MHD_HTTP_NO_CONTENT, - NULL, - NULL, - 0); - } - json_decref (json); - return res; -} +#include "platform.h" +#include +#include +#ifdef __linux__ +#include +#endif +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_bank.h" +#include "fakebank_common_lp.h" +#include "fakebank_tbi.h" +#include "fakebank_tbr.h" +#include "fakebank_twg.h" /** - * Notify long pollers that a @a wo was updated. - * Must be called with the "big_lock" still held. + * Function called whenever MHD is done with a request. If the + * request was a POST, we may have stored a `struct Buffer *` in the + * @a con_cls that might still need to be cleaned up. Call the + * respective function to free the memory. * - * @param h fakebank handle - * @param wo withdraw operation that finished + * @param cls a `struct TALER_FAKEBANK_Handle *` + * @param connection connection handle + * @param con_cls a `struct ConnectionContext *` + * the #MHD_AccessHandlerCallback + * @param toe reason for request termination + * @see #MHD_OPTION_NOTIFY_COMPLETED + * @ingroup request */ static void -notify_withdrawal (struct TALER_FAKEBANK_Handle *h, - const struct WithdrawalOperation *wo) -{ - struct Account *debit_acc = wo->debit_account; - struct LongPoller *nxt; - - for (struct LongPoller *lp = debit_acc->lp_head; - NULL != lp; - lp = nxt) - { - nxt = lp->next; - if ( (LP_WITHDRAW == lp->type) && - (wo == lp->wo) ) - { - GNUNET_assert (lp == - GNUNET_CONTAINER_heap_remove_node (lp->hn)); - lp_trigger (lp, - h); - } - } -} - - -/** - * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/abort request. - * - * @param h our fakebank handle - * @param connection the connection - * @param account_name name of the debited account - * @param withdrawal_id the withdrawal operation identifier - * @return MHD result code - */ -static MHD_RESULT -access_withdrawals_abort (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account_name, - const char *withdrawal_id) -{ - struct WithdrawalOperation *wo; - struct Account *acc; - - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - wo = lookup_withdrawal_operation (h, - withdrawal_id); - if (NULL == wo) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_TRANSACTION_NOT_FOUND, - withdrawal_id); - } - acc = lookup_account (h, - account_name, - NULL); - if (NULL == acc) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_UNKNOWN_ACCOUNT, - account_name); - } - if (wo->debit_account != acc) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_TRANSACTION_NOT_FOUND, - account_name); - } - if (wo->confirmation_done) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_BANK_ABORT_CONFIRM_CONFLICT, - account_name); - } - wo->aborted = true; - notify_withdrawal (h, - wo); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_json (connection, - json_object (), /* FIXME: #7301 */ - MHD_HTTP_OK); -} - - -/** - * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/confirm request. - * - * @param h our fakebank handle - * @param connection the connection - * @param account_name name of the debited account - * @param withdrawal_id the withdrawal operation identifier - * @return MHD result code - */ -static MHD_RESULT -access_withdrawals_confirm (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account_name, - const char *withdrawal_id) -{ - struct WithdrawalOperation *wo; - struct Account *acc; - - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - wo = lookup_withdrawal_operation (h, - withdrawal_id); - if (NULL == wo) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_TRANSACTION_NOT_FOUND, - withdrawal_id); - } - acc = lookup_account (h, - account_name, - NULL); - if (NULL == acc) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_UNKNOWN_ACCOUNT, - account_name); - } - if (wo->debit_account != acc) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_TRANSACTION_NOT_FOUND, - account_name); - } - if (NULL == wo->exchange_account) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_BANK_POST_WITHDRAWAL_OPERATION_REQUIRED, - NULL); - } - if (wo->aborted) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_BANK_CONFIRM_ABORT_CONFLICT, - account_name); - } - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - if (GNUNET_OK != - make_admin_transfer (h, - wo->debit_account->account_name, - wo->exchange_account->account_name, - &wo->amount, - &wo->reserve_pub, - &wo->row_id, - &wo->timestamp)) - { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT, - NULL); - } - /* Re-acquiring the lock and continuing to operate on 'wo' - is currently (!) acceptable because we NEVER free 'wo' - until shutdown. We may want to revise this if keeping - all withdraw operations in RAM becomes an issue... */ - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - wo->confirmation_done = true; - notify_withdrawal (h, - wo); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_json (connection, - json_object (), - MHD_HTTP_OK); -} - - -/** - * Handle incoming HTTP request to the Taler bank access API. - * - * @param h our fakebank handle - * @param connection the connection - * @param url the requested url - * @param method the method (POST, GET, ...) - * @param upload_data request data - * @param upload_data_size size of @a upload_data in bytes - * @param con_cls closure for request - * @return MHD result code - */ -static MHD_RESULT -handle_bank_access (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) -{ - if (0 == strcasecmp (method, - MHD_HTTP_METHOD_HEAD)) - method = MHD_HTTP_METHOD_GET; - if ( (0 == strcmp (url, - "/config")) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) ) - { - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_string ("version", - "0:0:0"), - GNUNET_JSON_pack_string ("currency", - h->currency), - GNUNET_JSON_pack_string ("name", - "taler-bank-access")); - } - if ( (0 == strcmp (url, - "/public-accounts")) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) ) - { - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_array_steal ("public_accounts", - json_array ())); - } - if ( (0 == strncmp (url, - "/accounts/", - strlen ("/accounts/"))) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_POST)) ) - { - const char *acc_name = &url[strlen ("/accounts/")]; - const char *end_acc = strchr (acc_name, - '/'); - char *acc; - MHD_RESULT ret; - - if ( (NULL == end_acc) || - (0 != strncmp (end_acc, - "/withdrawals", - strlen ("/withdrawals"))) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_GENERIC_ENDPOINT_UNKNOWN, - acc_name); - } - acc = GNUNET_strndup (acc_name, - end_acc - acc_name); - end_acc += strlen ("/withdrawals"); - if ('/' == *end_acc) - { - const char *wid = end_acc + 1; - const char *opid = strchr (wid, - '/'); - char *wi; - - if ( (NULL == opid) || - ( (0 != strcmp (opid, - "/abort")) && - (0 != strcmp (opid, - "/confirm")) ) ) - { - GNUNET_break_op (0); - GNUNET_free (acc); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_GENERIC_ENDPOINT_UNKNOWN, - acc_name); - } - wi = GNUNET_strndup (wid, - opid - wid); - if (0 == strcmp (opid, - "/abort")) - { - ret = access_withdrawals_abort (h, - connection, - acc, - wi); - GNUNET_free (wi); - GNUNET_free (acc); - return ret; - } - if (0 == strcmp (opid, - "/confirm")) - { - ret = access_withdrawals_confirm (h, - connection, - acc, - wi); - GNUNET_free (wi); - GNUNET_free (acc); - return ret; - } - GNUNET_assert (0); - } - ret = post_account_withdrawals_access (h, - connection, - acc, - upload_data, - upload_data_size, - con_cls); - GNUNET_free (acc); - return ret; - } - - if ( (0 == strncmp (url, - "/accounts/", - strlen ("/accounts/"))) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) ) - { - const char *acc_name = &url[strlen ("/accounts/")]; - const char *end_acc = strchr (acc_name, - '/'); - const char *wid; - char *acc; - MHD_RESULT ret; - - if (NULL == end_acc) - { - ret = get_account_access (h, - connection, - acc_name); - return ret; - } - if (0 != strncmp (end_acc, - "/withdrawals/", - strlen ("/withdrawals/"))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_GENERIC_ENDPOINT_UNKNOWN, - acc_name); - } - acc = GNUNET_strndup (acc_name, - end_acc - acc_name); - wid = &end_acc[strlen ("/withdrawals/")]; - ret = get_account_withdrawals_access (h, - connection, - acc, - wid); - GNUNET_free (acc); - return ret; - } - /* FIXME: implement transactions API: 1.12.2 */ - - /* registration API */ - if ( (0 == strcmp (url, - "/testing/register")) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_POST)) ) - { - return post_testing_register (h, - connection, - upload_data, - upload_data_size, - con_cls); - } - - TALER_LOG_ERROR ("Breaking URL: %s %s\n", - method, - url); - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_GENERIC_ENDPOINT_UNKNOWN, - url); -} - - -/** - * Handle incoming HTTP request for /history/incoming - * of the Anastasis API. This one can return transactions - * created by debits from the exchange! - * - * @param h the fakebank handle - * @param connection the connection - * @param account which account the request is about - * @param con_cls closure for request (NULL or &special_ptr) - * @return MHD result code - */ -static MHD_RESULT -handle_anastasis_credit_history ( - struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - const char *account, - void **con_cls) +handle_mhd_completion_callback (void *cls, + struct MHD_Connection *connection, + void **con_cls, + enum MHD_RequestTerminationCode toe) { + struct TALER_FAKEBANK_Handle *h = cls; struct ConnectionContext *cc = *con_cls; - struct HistoryContext *hc; - const struct Transaction *pos; - enum GNUNET_GenericReturnValue ret; + (void) h; + (void) connection; + (void) toe; if (NULL == cc) - { - cc = GNUNET_new (struct ConnectionContext); - cc->ctx_cleaner = &history_cleanup; - *con_cls = cc; - hc = GNUNET_new (struct HistoryContext); - cc->ctx = hc; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Handling /history/incoming connection %p\n", - connection); - if (GNUNET_OK != - (ret = parse_history_common_args (h, - connection, - &hc->ha))) - { - GNUNET_break_op (0); - return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; - } - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - hc->acc = lookup_account (h, - account, - NULL); - if (NULL == hc->acc) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_BANK_UNKNOWN_ACCOUNT, - account); - } - /* FIXME: was simply: acc->payto_uri -- same!? */ - GNUNET_asprintf (&hc->payto_uri, - "payto://x-taler-bank/localhost/%s?receiver-name=%s", - account, - hc->acc->receiver_name); - GNUNET_assert (0 == strcmp (hc->payto_uri, - hc->acc->payto_uri)); - hc->history = json_array (); - if (NULL == hc->history) - { - GNUNET_break (0); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return MHD_NO; - } - hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout); - } - else - { - hc = cc->ctx; - GNUNET_assert (0 == - pthread_mutex_lock (&h->big_lock)); - } - - if (! hc->ha.have_start) - { - pos = (0 > hc->ha.delta) - ? hc->acc->in_tail - : hc->acc->in_head; - } - else - { - struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit]; - bool overflow; - uint64_t dir; - bool skip = true; - - overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) ); - dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1; - /* If account does not match, linear scan for - first matching account. */ - while ( (! overflow) && - (NULL != t) && - (t->credit_account != hc->acc) ) - { - skip = false; - t = h->transactions[(t->row_id + dir) % h->ram_limit]; - if ( (NULL != t) && - (t->row_id == hc->ha.start_idx) ) - overflow = true; /* full circle, give up! */ - } - if ( (NULL == t) || - overflow) - { - /* FIXME: these conditions are unclear to me. */ - if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) && - (0 < hc->ha.delta)) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - if (overflow) - return TALER_MHD_reply_with_ec ( - connection, - TALER_EC_BANK_ANCIENT_TRANSACTION_GONE, - NULL); - goto finish; - } - if (h->in_shutdown) - { - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - goto finish; - } - start_lp (h, - connection, - hc->acc, - GNUNET_TIME_absolute_get_remaining (hc->timeout), - LP_CREDIT, - NULL); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return MHD_YES; - } - if (skip) - { - /* range from application is exclusive, skip the - matching entry */ - if (0 > hc->ha.delta) - pos = t->prev_in; - else - pos = t->next_in; - } - else - { - pos = t; - } - } - if (NULL != pos) - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning %lld credit transactions starting (inclusive) from %llu\n", - (long long) hc->ha.delta, - (unsigned long long) pos->row_id); - while ( (0 != hc->ha.delta) && - (NULL != pos) ) - { - json_t *trans; - char *subject; - - if (T_DEBIT != pos->type) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Unexpected CREDIT transaction #%llu for account `%s'\n", - (unsigned long long) pos->row_id, - account); - if (0 > hc->ha.delta) - pos = pos->prev_in; - if (0 < hc->ha.delta) - pos = pos->next_in; - continue; - } - - { - char *wtids; - - wtids = GNUNET_STRINGS_data_to_string_alloc ( - &pos->subject.debit.wtid, - sizeof (pos->subject.debit.wtid)); - GNUNET_asprintf (&subject, - "%s %s", - wtids, - pos->subject.debit.exchange_base_url); - GNUNET_free (wtids); - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Found transaction over %s with subject %s\n", - TALER_amount2s (&pos->amount), - subject); - trans = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("type", - "RESERVE"), - GNUNET_JSON_pack_uint64 ("row_id", - pos->row_id), - GNUNET_JSON_pack_timestamp ("date", - pos->date), - TALER_JSON_pack_amount ("amount", - &pos->amount), - GNUNET_JSON_pack_string ("debit_account", - pos->debit_account->payto_uri), - GNUNET_JSON_pack_string ("subject", - subject)); - GNUNET_free (subject); - GNUNET_assert (NULL != trans); - GNUNET_assert (0 == - json_array_append_new (hc->history, - trans)); - if (hc->ha.delta > 0) - hc->ha.delta--; - else - hc->ha.delta++; - if (0 > hc->ha.delta) - pos = pos->prev_in; - if (0 < hc->ha.delta) - pos = pos->next_in; - } - if ( (0 == json_array_size (hc->history)) && - (! h->in_shutdown) && - (GNUNET_TIME_absolute_is_future (hc->timeout)) && - (0 < hc->ha.delta)) - { - start_lp (h, - connection, - hc->acc, - GNUNET_TIME_absolute_get_remaining (hc->timeout), - LP_CREDIT, - NULL); - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); - return MHD_YES; - } - GNUNET_assert (0 == - pthread_mutex_unlock (&h->big_lock)); -finish: - if (0 == json_array_size (hc->history)) - { - GNUNET_break (h->in_shutdown || - (! GNUNET_TIME_absolute_is_future (hc->timeout))); - return TALER_MHD_reply_static (connection, - MHD_HTTP_NO_CONTENT, - NULL, - NULL, - 0); - } - { - json_t *h = hc->history; - - hc->history = NULL; - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_string ( - "credit_account", - hc->payto_uri), - GNUNET_JSON_pack_array_steal ( - "incoming_transactions", - h)); - } -} - - -/** - * Handle incoming HTTP request to Anastasis API. - * - * @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 - * @return MHD result code - */ -static MHD_RESULT -handle_anastasis_api ( - 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 - Anastasis API: serving URL `%s' for account `%s'\n", - url, - account); - if (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) - { - if ( (0 == strcmp (url, - "/history/incoming")) && - (NULL != account) ) - return handle_anastasis_credit_history (h, - connection, - account, - con_cls); - if (0 == strcmp (url, - "/")) - return handle_home_page (h, - connection); - } - /* Unexpected URL path, just close the connection. */ - TALER_LOG_ERROR ("Breaking URL: %s %s\n", - method, - url); - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_GENERIC_ENDPOINT_UNKNOWN, - url); + return; + cc->ctx_cleaner (cc->ctx); + GNUNET_free (cc); } @@ -4418,26 +106,26 @@ handle_mhd_request (void *cls, strlen ("/taler-bank-integration/"))) { url += strlen ("/taler-bank-integration"); - return handle_bank_integration (h, - connection, - url, - method, - upload_data, - upload_data_size, - con_cls); + return TALER_FAKEBANK_tbi_main_ (h, + connection, + url, + method, + upload_data, + upload_data_size, + con_cls); } if (0 == strncmp (url, "/taler-bank-access/", strlen ("/taler-bank-access/"))) { url += strlen ("/taler-bank-access"); - return handle_bank_access (h, - connection, - url, - method, - upload_data, - upload_data_size, - con_cls); + return TALER_FAKEBANK_bank_main_ (h, + connection, + url, + method, + upload_data, + upload_data_size, + con_cls); } /* Next is duplication to be more libeufin-like: */ if (0 == strncmp (url, @@ -4445,13 +133,13 @@ handle_mhd_request (void *cls, strlen ("/access-api/"))) { url += strlen ("/access-api"); - return handle_bank_access (h, - connection, - url, - method, - upload_data, - upload_data_size, - con_cls); + return TALER_FAKEBANK_bank_main_ (h, + connection, + url, + method, + upload_data, + upload_data_size, + con_cls); } if (0 == strncmp (url, "/anastasis-api/", @@ -4465,14 +153,14 @@ handle_mhd_request (void *cls, end - url - 1); url = end; } - return handle_anastasis_api (h, - connection, - account, - url, - method, - upload_data, - upload_data_size, - con_cls); + return TALER_FAKEBANK_tbr_main_ (h, + connection, + account, + url, + method, + upload_data, + upload_data_size, + con_cls); } if (0 == strncmp (url, @@ -4489,14 +177,14 @@ handle_mhd_request (void *cls, end - url - 1); url = end; } - ret = serve (h, - connection, - account, - url, - method, - upload_data, - upload_data_size, - con_cls); + ret = TALER_FAKEBANK_twg_main_ (h, + connection, + account, + url, + method, + upload_data, + upload_data_size, + con_cls); GNUNET_free (account); return ret; } @@ -4529,7 +217,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); } @@ -4603,7 +291,7 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h) tv, wrs, wws, - &run_mhd, + &TALER_FAKEBANK_run_mhd_, h); if (NULL != wrs) GNUNET_NETWORK_fdset_destroy (wrs); @@ -4620,8 +308,8 @@ 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; @@ -4770,22 +458,23 @@ TALER_FAKEBANK_start3 (const char *hostname, (unsigned int) port); if (0 == num_threads) { - h->mhd_bank = MHD_start_daemon (MHD_USE_DEBUG + h->mhd_bank = MHD_start_daemon ( + MHD_USE_DEBUG #if EPOLL_SUPPORT - | MHD_USE_EPOLL + | MHD_USE_EPOLL #endif - | 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); + | 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); @@ -4828,7 +517,7 @@ TALER_FAKEBANK_start3 (const char *hostname, if (0 != pthread_create (&h->lp_thread, NULL, - &lp_expiration_thread, + &TALER_FAKEBANK_lp_expiration_thread_, h)) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, @@ -4845,24 +534,25 @@ TALER_FAKEBANK_start3 (const char *hostname, 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); + 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); diff --git a/src/bank-lib/fakebank.h b/src/bank-lib/fakebank.h new file mode 100644 index 000000000..a9285ec13 --- /dev/null +++ b/src/bank-lib/fakebank.h @@ -0,0 +1,696 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank.h + * @brief general state of the fakebank + * @author Christian Grothoff + */ +#ifndef FAKEBANK_H +#define FAKEBANK_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include + + +/** + * How long are exchange base URLs allowed to be at most? + * Set to a relatively low number as this does contribute + * significantly to our RAM consumption. + */ +#define MAX_URL_LEN 64 + + +/** + * Maximum POST request size. + */ +#define REQUEST_BUFFER_MAX (4 * 1024) + + +/** + * Per account information. + */ +struct Account; + + +/** + * Types of long polling activities. + */ +enum LongPollType +{ + /** + * Transfer TO the exchange. + */ + LP_CREDIT, + + /** + * Transfer FROM the exchange. + */ + LP_DEBIT, + + /** + * Withdraw operation completion/abort. + */ + LP_WITHDRAW + +}; + +/** + * Client waiting for activity on this account. + */ +struct LongPoller +{ + + /** + * Kept in a DLL. + */ + struct LongPoller *next; + + /** + * Kept in a DLL. + */ + struct LongPoller *prev; + + /** + * Fakebank this long poller belongs with. + */ + struct TALER_FAKEBANK_Handle *h; + + /** + * Account this long poller is waiting on. + */ + struct Account *account; + + /** + * Withdraw operation we are waiting on, + * only if @e type is #LP_WITHDRAW, otherwise NULL. + */ + const struct WithdrawalOperation *wo; + + /** + * Entry in the heap for this long poller. + */ + struct GNUNET_CONTAINER_HeapNode *hn; + + /** + * Client that is waiting for transactions. + */ + struct MHD_Connection *conn; + + /** + * When will this long poller time out? + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * What does the @e connection wait for? + */ + enum LongPollType type; + +}; + + +/** + * Details about a transcation we (as the simulated bank) received. + */ +struct Transaction; + + +/** + * Information we keep per withdraw operation. + */ +struct WithdrawalOperation +{ + /** + * Unique (random) operation ID. + */ + struct GNUNET_ShortHashCode wopid; + + /** + * Debited account. + */ + struct Account *debit_account; + + /** + * Target exchange account, or NULL if unknown. + */ + const struct Account *exchange_account; + + /** + * RowID of the resulting transaction, if any. Otherwise 0. + */ + uint64_t row_id; + + /** + * Amount transferred. + */ + struct TALER_Amount amount; + + /** + * Public key of the reserve, wire transfer subject. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * When was the transaction made? 0 if not yet. + */ + struct GNUNET_TIME_Timestamp timestamp; + + /** + * Was the withdrawal aborted? + */ + bool aborted; + + /** + * Did the bank confirm the withdrawal? + */ + bool confirmation_done; + + /** + * Is @e reserve_pub initialized? + */ + bool selection_done; + +}; + + +/** + * Per account information. + */ +struct Account +{ + + /** + * Inbound transactions for this account in a MDLL. + */ + struct Transaction *in_head; + + /** + * Inbound transactions for this account in a MDLL. + */ + struct Transaction *in_tail; + + /** + * Outbound transactions for this account in a MDLL. + */ + struct Transaction *out_head; + + /** + * Outbound transactions for this account in a MDLL. + */ + struct Transaction *out_tail; + + /** + * Kept in a DLL. + */ + struct LongPoller *lp_head; + + /** + * Kept in a DLL. + */ + struct LongPoller *lp_tail; + + /** + * Account name (string, not payto!) + */ + char *account_name; + + /** + * Receiver name for payto:// URIs. + */ + char *receiver_name; + + /** + * Payto URI for this account. + */ + char *payto_uri; + + /** + * Password set for the account (if any). + */ + char *password; + + /** + * Current account balance. + */ + struct TALER_Amount balance; + + /** + * true if the balance is negative. + */ + bool is_negative; + +}; + + +/** + * Details about a transcation we (as the simulated bank) received. + */ +struct Transaction +{ + /** + * We store inbound transactions in a MDLL. + */ + struct Transaction *next_in; + + /** + * We store inbound transactions in a MDLL. + */ + struct Transaction *prev_in; + + /** + * We store outbound transactions in a MDLL. + */ + struct Transaction *next_out; + + /** + * We store outbound transactions in a MDLL. + */ + struct Transaction *prev_out; + + /** + * Amount to be transferred. + */ + struct TALER_Amount amount; + + /** + * Account to debit. + */ + struct Account *debit_account; + + /** + * Account to credit. + */ + struct Account *credit_account; + + /** + * Random unique identifier for the request. + * Used to detect idempotent requests. + */ + struct GNUNET_HashCode request_uid; + + /** + * When did the transaction happen? + */ + struct GNUNET_TIME_Timestamp date; + + /** + * Number of this transaction. + */ + uint64_t row_id; + + /** + * What does the @e subject contain? + */ + enum + { + /** + * Transfer TO the exchange. + */ + T_CREDIT, + + /** + * Transfer FROM the exchange. + */ + T_DEBIT, + + /** + * Exchange-to-exchange WAD transfer. + */ + T_WAD, + } 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[MAX_URL_LEN]; + + } debit; + + /** + * Used if @e type is T_CREDIT. + */ + struct + { + + /** + * Reserve public key of the credit operation. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + } credit; + + /** + * Used if @e type is T_WAD. + */ + struct + { + + /** + * Subject of the transfer. + */ + struct TALER_WadIdentifierP wad; + + /** + * Base URL of the originating exchange. + */ + char origin_base_url[MAX_URL_LEN]; + + } wad; + + } subject; + + /** + * Has this transaction not yet been subjected to + * #TALER_FAKEBANK_check_credit() or #TALER_FAKEBANK_check_debit() and + * should thus be counted in #TALER_FAKEBANK_check_empty()? + */ + bool unchecked; +}; + + +/** + * Function called to clean up context of a connection. + * + * @param ctx context to clean up + */ +typedef void +(*ConnectionCleaner)(void *ctx); + +/** + * Universal context we keep per connection. + */ +struct ConnectionContext +{ + /** + * Function we call upon completion to clean up. + */ + ConnectionCleaner ctx_cleaner; + + /** + * Request-handler specific context. + */ + void *ctx; +}; + + +/** + * 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, exclusive (!). + */ + 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; + + /** + * true if starting point was given. + */ + bool have_start; + +}; + + +/** + * Context we keep per history request. + */ +struct HistoryContext +{ + /** + * When does this request time out. + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * Client arguments for this request. + */ + struct HistoryArgs ha; + + /** + * Account the request is about. + */ + struct Account *acc; + + /** + * Payto URI of the account. + */ + char *payto_uri; + + /** + * JSON object we are building to return. + */ + json_t *history; + +}; + + +/** + * Context we keep per get withdrawal operation request. + */ +struct WithdrawContext +{ + /** + * When does this request time out. + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * The withdrawal operation this is about. + */ + struct WithdrawalOperation *wo; + +}; + + +/** + * Handle for the fake bank. + */ +struct TALER_FAKEBANK_Handle +{ + /** + * We store transactions in a revolving array. + */ + struct Transaction **transactions; + + /** + * HTTP server we run to pretend to be the "test" bank. + */ + struct MHD_Daemon *mhd_bank; + + /** + * Task running HTTP server for the "test" bank, + * unless we are using a thread pool (then NULL). + */ + struct GNUNET_SCHEDULER_Task *mhd_task; + + /** + * Task for expiring long-polling connections, + * unless we are using a thread pool (then NULL). + */ + struct GNUNET_SCHEDULER_Task *lp_task; + + /** + * Task for expiring long-polling connections, unless we are using the + * GNUnet scheduler (then NULL). + */ + pthread_t lp_thread; + + /** + * MIN-heap of long pollers, sorted by timeout. + */ + struct GNUNET_CONTAINER_Heap *lp_heap; + + /** + * Hashmap of reserve public keys to + * `struct Transaction` with that reserve public + * key. Used to prevent public-key re-use. + */ + struct GNUNET_CONTAINER_MultiPeerMap *rpubs; + + /** + * Hashmap of short hashes (wopids) to + * `struct WithdrawalOperation`. + * Used to lookup withdrawal operations. + */ + struct GNUNET_CONTAINER_MultiShortmap *wops; + + /** + * (Base) URL to suggest for the exchange. Can + * be NULL if there is no suggestion to be made. + */ + char *exchange_url; + + /** + * Lock for accessing @a rpubs map. + */ + pthread_mutex_t rpubs_lock; + + /** + * Hashmap of hashes of account names to `struct Account`. + */ + struct GNUNET_CONTAINER_MultiHashMap *accounts; + + /** + * Lock for accessing @a accounts hash map. + */ + pthread_mutex_t accounts_lock; + + /** + * Hashmap of hashes of transaction request_uids to `struct Transaction`. + */ + struct GNUNET_CONTAINER_MultiHashMap *uuid_map; + + /** + * Lock for accessing @a uuid_map. + */ + pthread_mutex_t uuid_map_lock; + + /** + * Lock for accessing the internals of + * accounts and transaction array entries. + */ + pthread_mutex_t big_lock; + + /** + * How much money should be put into new accounts + * on /register. + */ + struct TALER_Amount signup_bonus; + + /** + * Current transaction counter. + */ + uint64_t serial_counter; + + /** + * Number of transactions we keep in memory (at most). + */ + uint64_t ram_limit; + + /** + * Currency used by the fakebank. + */ + char *currency; + + /** + * Hostname of the fakebank. + */ + char *hostname; + + /** + * BaseURL of the fakebank. + */ + char *my_baseurl; + + /** + * Our port number. + */ + uint16_t port; + +#ifdef __linux__ + /** + * Event FD to signal @a lp_thread a change in + * @a lp_heap. + */ + int lp_event; +#else + /** + * Pipe input to signal @a lp_thread a change in + * @a lp_heap. + */ + int lp_event_in; + + /** + * Pipe output to signal @a lp_thread a change in + * @a lp_heap. + */ + int lp_event_out; +#endif + + /** + * Set to true once we are shutting down. + */ + bool in_shutdown; + + /** + * Should we run MHD immediately again? + */ + bool mhd_again; + +#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 +}; + + +/** + * Task run whenever HTTP server operations are pending. + * + * @param cls the `struct TALER_FAKEBANK_Handle` + */ +void +TALER_FAKEBANK_run_mhd_ (void *cls); + + +#endif diff --git a/src/bank-lib/fakebank_api_check.c b/src/bank-lib/fakebank_api_check.c new file mode 100644 index 000000000..04656ebab --- /dev/null +++ b/src/bank-lib/fakebank_api_check.c @@ -0,0 +1,238 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_api_check.c + * @brief library that fakes being a Taler bank for testcases + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_lookup.h" + + +/** + * 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 (uint64_t i = 0; iram_limit; i++) + { + struct Transaction *t = h->transactions[i]; + + if (NULL == t) + continue; + if (! t->unchecked) + continue; + switch (t->type) + { + case T_DEBIT: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%s -> %s (%s) %s (%s)\n", + t->debit_account->account_name, + t->credit_account->account_name, + TALER_amount2s (&t->amount), + t->subject.debit.exchange_base_url, + "DEBIT"); + break; + case T_CREDIT: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%s -> %s (%s) %s (%s)\n", + t->debit_account->account_name, + t->credit_account->account_name, + TALER_amount2s (&t->amount), + TALER_B2S (&t->subject.credit.reserve_pub), + "CREDIT"); + break; + case T_WAD: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%s -> %s (%s) %s[%s] (%s)\n", + t->debit_account->account_name, + t->credit_account->account_name, + TALER_amount2s (&t->amount), + t->subject.wad.origin_base_url, + TALER_B2S (&t->subject.wad), + "WAD"); + break; + } + } +} + + +enum GNUNET_GenericReturnValue +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) +{ + struct Account *debit_account; + struct Account *credit_account; + + GNUNET_assert (0 == + strcasecmp (want_amount->currency, + h->currency)); + debit_account = TALER_FAKEBANK_lookup_account_ (h, + want_debit, + NULL); + credit_account = TALER_FAKEBANK_lookup_account_ (h, + want_credit, + NULL); + if (NULL == debit_account) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "I wanted: %s->%s (%s) from exchange %s (DEBIT), but debit account does not even exist!\n", + want_debit, + want_credit, + TALER_amount2s (want_amount), + exchange_base_url); + return GNUNET_SYSERR; + } + if (NULL == credit_account) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "I wanted: %s->%s (%s) from exchange %s (DEBIT), but credit account does not even exist!\n", + want_debit, + want_credit, + TALER_amount2s (want_amount), + exchange_base_url); + return GNUNET_SYSERR; + } + for (struct Transaction *t = debit_account->out_tail; + NULL != t; + t = t->prev_out) + { + if ( (t->unchecked) && + (credit_account == t->credit_account) && + (T_DEBIT == t->type) && + (0 == TALER_amount_cmp (want_amount, + &t->amount)) && + (0 == strcasecmp (exchange_base_url, + t->subject.debit.exchange_base_url)) ) + { + *wtid = t->subject.debit.wtid; + t->unchecked = false; + 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; +} + + +enum GNUNET_GenericReturnValue +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) +{ + struct Account *debit_account; + struct Account *credit_account; + + GNUNET_assert (0 == strcasecmp (want_amount->currency, + h->currency)); + debit_account = TALER_FAKEBANK_lookup_account_ (h, + want_debit, + NULL); + credit_account = TALER_FAKEBANK_lookup_account_ (h, + want_credit, + NULL); + if (NULL == debit_account) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "I wanted:\n%s -> %s (%s) with subject %s (CREDIT) but debit account is unknown.\n", + want_debit, + want_credit, + TALER_amount2s (want_amount), + TALER_B2S (reserve_pub)); + return GNUNET_SYSERR; + } + if (NULL == credit_account) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "I wanted:\n%s -> %s (%s) with subject %s (CREDIT) but credit account is unknown.\n", + want_debit, + want_credit, + TALER_amount2s (want_amount), + TALER_B2S (reserve_pub)); + return GNUNET_SYSERR; + } + for (struct Transaction *t = credit_account->in_tail; + NULL != t; + t = t->prev_in) + { + if ( (t->unchecked) && + (debit_account == t->debit_account) && + (T_CREDIT == t->type) && + (0 == TALER_amount_cmp (want_amount, + &t->amount)) && + (0 == GNUNET_memcmp (reserve_pub, + &t->subject.credit.reserve_pub)) ) + { + t->unchecked = false; + 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; +} + + +enum GNUNET_GenericReturnValue +TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h) +{ + for (uint64_t i = 0; iram_limit; i++) + { + struct Transaction *t = h->transactions[i]; + + if ( (NULL != t) && + (t->unchecked) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected empty transaction set, but I have:\n"); + check_log (h); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} diff --git a/src/bank-lib/fakebank_bank.c b/src/bank-lib/fakebank_bank.c new file mode 100644 index 000000000..ede148a41 --- /dev/null +++ b/src/bank-lib/fakebank_bank.c @@ -0,0 +1,224 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank.c + * @brief Main dispatcher for the Taler Bank API + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_bank.h" +#include "fakebank_bank_get_accounts.h" +#include "fakebank_bank_get_accounts_withdrawals.h" +#include "fakebank_bank_get_root.h" +#include "fakebank_bank_post_accounts_withdrawals.h" +#include "fakebank_bank_post_accounts_withdrawals_abort.h" +#include "fakebank_bank_post_accounts_withdrawals_confirm.h" +#include "fakebank_bank_testing_register.h" + + +MHD_RESULT +TALER_FAKEBANK_bank_main_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + if (0 == strcasecmp (method, + MHD_HTTP_METHOD_HEAD)) + method = MHD_HTTP_METHOD_GET; + if ( (0 == strcmp (url, + "/config")) && + (0 == strcasecmp (method, + MHD_HTTP_METHOD_GET)) ) + { + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ("version", + "0:0:0"), + GNUNET_JSON_pack_string ("currency", + h->currency), + GNUNET_JSON_pack_string ("name", + "taler-bank-access")); + } + if ( (0 == strcmp (url, + "/public-accounts")) && + (0 == strcasecmp (method, + MHD_HTTP_METHOD_GET)) ) + { + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_array_steal ("public_accounts", + json_array ())); + } + if ( (0 == strncmp (url, + "/accounts/", + strlen ("/accounts/"))) && + (0 == strcasecmp (method, + MHD_HTTP_METHOD_POST)) ) + { + const char *acc_name = &url[strlen ("/accounts/")]; + const char *end_acc = strchr (acc_name, + '/'); + char *acc; + MHD_RESULT ret; + + if ( (NULL == end_acc) || + (0 != strncmp (end_acc, + "/withdrawals", + strlen ("/withdrawals"))) ) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + acc_name); + } + acc = GNUNET_strndup (acc_name, + end_acc - acc_name); + end_acc += strlen ("/withdrawals"); + if ('/' == *end_acc) + { + const char *wid = end_acc + 1; + const char *opid = strchr (wid, + '/'); + char *wi; + + if ( (NULL == opid) || + ( (0 != strcmp (opid, + "/abort")) && + (0 != strcmp (opid, + "/confirm")) ) ) + { + GNUNET_break_op (0); + GNUNET_free (acc); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + acc_name); + } + wi = GNUNET_strndup (wid, + opid - wid); + if (0 == strcmp (opid, + "/abort")) + { + ret = TALER_FAKEBANK_bank_withdrawals_abort_ (h, + connection, + acc, + wi); + GNUNET_free (wi); + GNUNET_free (acc); + return ret; + } + if (0 == strcmp (opid, + "/confirm")) + { + ret = TALER_FAKEBANK_bank_withdrawals_confirm_ (h, + connection, + acc, + wi); + GNUNET_free (wi); + GNUNET_free (acc); + return ret; + } + GNUNET_assert (0); + } + ret = TALER_FAKEBANK_bank_post_account_withdrawals_ ( + h, + connection, + acc, + upload_data, + upload_data_size, + con_cls); + GNUNET_free (acc); + return ret; + } + + if ( (0 == strncmp (url, + "/accounts/", + strlen ("/accounts/"))) && + (0 == strcasecmp (method, + MHD_HTTP_METHOD_GET)) ) + { + const char *acc_name = &url[strlen ("/accounts/")]; + const char *end_acc = strchr (acc_name, + '/'); + const char *wid; + char *acc; + MHD_RESULT ret; + + if (NULL == end_acc) + { + return TALER_FAKEBANK_bank_get_accounts_ (h, + connection, + acc_name); + } + if (0 != strncmp (end_acc, + "/withdrawals/", + strlen ("/withdrawals/"))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + acc_name); + } + acc = GNUNET_strndup (acc_name, + end_acc - acc_name); + wid = &end_acc[strlen ("/withdrawals/")]; + ret = TALER_FAKEBANK_bank_get_accounts_withdrawals_ (h, + connection, + acc, + wid); + GNUNET_free (acc); + return ret; + } + /* FIXME: implement transactions API: 1.12.2 */ + + /* registration API */ + if ( (0 == strcmp (url, + "/testing/register")) && + (0 == strcasecmp (method, + MHD_HTTP_METHOD_POST)) ) + { + return TALER_FAKEBANK_bank_testing_register_ (h, + connection, + upload_data, + upload_data_size, + con_cls); + } + TALER_LOG_ERROR ("Breaking URL: %s %s\n", + method, + url); + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + url); +} diff --git a/src/bank-lib/fakebank_bank.h b/src/bank-lib/fakebank_bank.h new file mode 100644 index 000000000..1c51f88f8 --- /dev/null +++ b/src/bank-lib/fakebank_bank.h @@ -0,0 +1,54 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank.h + * @brief Main dispatcher for the Taler Bank API + * @author Christian Grothoff + */ +#ifndef FAKEBANK_BANK_H +#define FAKEBANK_BANK_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + +/** + * Handle incoming HTTP request to the Taler bank API. + * + * @param h our fakebank handle + * @param connection the connection + * @param url the requested url + * @param method the method (POST, GET, ...) + * @param upload_data request data + * @param upload_data_size size of @a upload_data in bytes + * @param con_cls closure for request + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_main_ (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *upload_data, + size_t *upload_data_size, + void **con_cls); + +#endif diff --git a/src/bank-lib/fakebank_bank_accounts_withdrawals.c b/src/bank-lib/fakebank_bank_accounts_withdrawals.c new file mode 100644 index 000000000..ffcff0e20 --- /dev/null +++ b/src/bank-lib/fakebank_bank_accounts_withdrawals.c @@ -0,0 +1,101 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_accounts_withdrawals.c + * @brief library that fakes being a Taler bank for testcases + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_bank_accounts_withdrawals.h" +#include "fakebank_common_lookup.h" + + +MHD_RESULT +TALER_FAKEBANK_bank_account_withdrawals_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name, + const char *withdrawal_id) +{ + struct WithdrawalOperation *wo; + struct Account *acc; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h, + withdrawal_id); + if (NULL == wo) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_TRANSACTION_NOT_FOUND, + withdrawal_id); + } + acc = TALER_FAKEBANK_lookup_account_ (h, + account_name, + NULL); + if (NULL == acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_UNKNOWN_ACCOUNT, + account_name); + } + if (wo->debit_account != acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_TRANSACTION_NOT_FOUND, + account_name); + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_bool ("aborted", + wo->aborted), + GNUNET_JSON_pack_bool ("selection_done", + wo->selection_done), + GNUNET_JSON_pack_bool ("transfer_done", + wo->confirmation_done), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("selected_exchange_account", + wo->exchange_account->payto_uri)), + GNUNET_JSON_pack_allow_null ( + wo->selection_done + ? GNUNET_JSON_pack_data_auto ("selected_reserve_pub", + &wo->reserve_pub) + : GNUNET_JSON_pack_string ("selected_reserve_pub", + NULL)), + TALER_JSON_pack_amount ("amount", + &wo->amount)); +} diff --git a/src/bank-lib/fakebank_bank_accounts_withdrawals.h b/src/bank-lib/fakebank_bank_accounts_withdrawals.h new file mode 100644 index 000000000..2a598dee9 --- /dev/null +++ b/src/bank-lib/fakebank_bank_accounts_withdrawals.h @@ -0,0 +1,50 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_accounts_withdrawals.h + * @brief library that fakes being a Taler bank for testcases + * @author Christian Grothoff + */ +#ifndef FAKEBANK_BANK_ACCOUNTS_WITHDRAWALS_H +#define FAKEBANK_BANK_ACCOUNTS_WITHDRAWALS_H +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + +/** + * Handle GET /accounts/${account_name}/withdrawals/{withdrawal_id} request + * to the Taler bank access API. + * + * @param h the handle + * @param connection the connection + * @param account_name name of the account + * @param withdrawal_id withdrawal ID to return status of + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_account_withdrawals_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name, + const char *withdrawal_id); + +#endif diff --git a/src/bank-lib/fakebank_bank_get_accounts.c b/src/bank-lib/fakebank_bank_get_accounts.c new file mode 100644 index 000000000..e85387d2a --- /dev/null +++ b/src/bank-lib/fakebank_bank_get_accounts.c @@ -0,0 +1,80 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_get_accounts.c + * @brief implements the Taler Bank API "GET /accounts/" handler + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_bank_get_accounts.h" +#include "fakebank_common_lookup.h" + +/** + * Handle GET /accounts/${account_name} request of the Taler bank API. + * + * @param h the handle + * @param connection the connection + * @param account_name name of the account + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_get_accounts_ (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name) +{ + struct Account *acc; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + acc = TALER_FAKEBANK_lookup_account_ (h, + account_name, + NULL); + if (NULL == acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_UNKNOWN_ACCOUNT, + account_name); + } + + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ("payto_uri", + acc->payto_uri), + GNUNET_JSON_pack_object_steal ( + "balance", + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("credit_debit_indicator", + acc->is_negative + ? "debit" + : "credit"), + TALER_JSON_pack_amount ("amount", + &acc->balance)))); +} diff --git a/src/bank-lib/fakebank_bank_get_accounts.h b/src/bank-lib/fakebank_bank_get_accounts.h new file mode 100644 index 000000000..7d3872133 --- /dev/null +++ b/src/bank-lib/fakebank_bank_get_accounts.h @@ -0,0 +1,48 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_get_accounts.h + * @brief implements the Taler Bank API "GET /accounts/" handler + * @author Christian Grothoff + */ +#ifndef FAKEBANK_BANK_GET_ACCOUNTS_H +#define FAKEBANK_BANK_GET_ACCOUNTS_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * Handle GET /accounts/${account_name} request of the Taler bank API. + * + * @param h the handle + * @param connection the connection + * @param account_name name of the account + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_get_accounts_ (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name); + + +#endif diff --git a/src/bank-lib/fakebank_bank_get_accounts_withdrawals.c b/src/bank-lib/fakebank_bank_get_accounts_withdrawals.c new file mode 100644 index 000000000..176b6fb42 --- /dev/null +++ b/src/bank-lib/fakebank_bank_get_accounts_withdrawals.c @@ -0,0 +1,111 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_get_accounts_withdrawals.c + * @brief implements the Taler Bank API "GET /accounts/ACC/withdrawals/WID" handler + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_bank_get_accounts_withdrawals.h" +#include "fakebank_common_lookup.h" + + +/** + * Handle GET /accounts/${account_name}/withdrawals/{withdrawal_id} request + * to the Taler bank access API. + * + * @param h the handle + * @param connection the connection + * @param account_name name of the account + * @param withdrawal_id withdrawal ID to return status of + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_get_accounts_withdrawals_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name, + const char *withdrawal_id) +{ + struct WithdrawalOperation *wo; + struct Account *acc; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h, + withdrawal_id); + if (NULL == wo) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_TRANSACTION_NOT_FOUND, + withdrawal_id); + } + acc = TALER_FAKEBANK_lookup_account_ (h, + account_name, + NULL); + if (NULL == acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_UNKNOWN_ACCOUNT, + account_name); + } + if (wo->debit_account != acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_TRANSACTION_NOT_FOUND, + account_name); + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_bool ("aborted", + wo->aborted), + GNUNET_JSON_pack_bool ("selection_done", + wo->selection_done), + GNUNET_JSON_pack_bool ("transfer_done", + wo->confirmation_done), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("selected_exchange_account", + wo->exchange_account->payto_uri)), + GNUNET_JSON_pack_allow_null ( + wo->selection_done + ? GNUNET_JSON_pack_data_auto ("selected_reserve_pub", + &wo->reserve_pub) + : GNUNET_JSON_pack_string ("selected_reserve_pub", + NULL)), + TALER_JSON_pack_amount ("amount", + &wo->amount)); +} diff --git a/src/bank-lib/fakebank_bank_get_accounts_withdrawals.h b/src/bank-lib/fakebank_bank_get_accounts_withdrawals.h new file mode 100644 index 000000000..53dd873ec --- /dev/null +++ b/src/bank-lib/fakebank_bank_get_accounts_withdrawals.h @@ -0,0 +1,53 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_get_accounts_withdrawals.h + * @brief implements the Taler Bank API "GET /accounts/ACC/withdrawals/WID" handler + * @author Christian Grothoff + */ +#ifndef FAKEBANK_BANK_GET_ACCOUNTS_WITHDRAWALS_H +#define FAKEBANK_BANK_GET_ACCOUNTS_WITHDRAWALS_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_bank_get_accounts_withdrawals.h" + + +/** + * Handle GET /accounts/${account_name}/withdrawals/{withdrawal_id} request + * to the Taler bank access API. + * + * @param h the handle + * @param connection the connection + * @param account_name name of the account + * @param withdrawal_id withdrawal ID to return status of + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_get_accounts_withdrawals_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name, + const char *withdrawal_id); + + +#endif diff --git a/src/bank-lib/fakebank_bank_get_root.c b/src/bank-lib/fakebank_bank_get_root.c new file mode 100644 index 000000000..8c34697b4 --- /dev/null +++ b/src/bank-lib/fakebank_bank_get_root.c @@ -0,0 +1,58 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_get_root.c + * @brief handle a GET "/" request for the bank API + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_bank_get_root.h" + + +/** + * Handle incoming HTTP request for "/" (home page). + * + * @param h the fakebank handle + * @param connection the connection + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_get_root_ (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection) +{ + MHD_RESULT ret; + struct MHD_Response *resp; +#define HELLOMSG "Hello, Fakebank!" + + (void) h; + 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; +} diff --git a/src/bank-lib/fakebank_bank_get_root.h b/src/bank-lib/fakebank_bank_get_root.h new file mode 100644 index 000000000..2eedb94a1 --- /dev/null +++ b/src/bank-lib/fakebank_bank_get_root.h @@ -0,0 +1,44 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_get_root.c + * @brief handle a GET "/" request for the bank API + * @author Christian Grothoff + */ +#ifndef FAKEBANK_BANK_GET_ROOT_H +#define FAKEBANK_BANK_GET_ROOT_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + +/** + * Handle incoming HTTP request for "/" (home page). + * + * @param h the fakebank handle + * @param connection the connection + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_get_root_ (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection); + +#endif diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals.c b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.c new file mode 100644 index 000000000..f05faf14d --- /dev/null +++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.c @@ -0,0 +1,196 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_post_accounts_withdrawals.c + * @brief implementation of the bank API's POST /accounts/AID/withdrawals endpoint + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_bank_post_accounts_withdrawals.h" +#include "fakebank_common_lookup.h" + + +/** + * Execute POST /accounts/$account_name/withdrawals request. + * + * @param h our fakebank handle + * @param connection the connection + * @param account_name name of the account + * @param amount amont to withdraw + * @return MHD result code + */ +static MHD_RESULT +do_post_account_withdrawals ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name, + const struct TALER_Amount *amount) +{ + struct Account *acc; + struct WithdrawalOperation *wo; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + acc = TALER_FAKEBANK_lookup_account_ (h, + account_name, + NULL); + if (NULL == acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_UNKNOWN_ACCOUNT, + account_name); + } + wo = GNUNET_new (struct WithdrawalOperation); + wo->debit_account = acc; + wo->amount = *amount; + if (NULL == h->wops) + { + h->wops = GNUNET_CONTAINER_multishortmap_create (32, + GNUNET_YES); + } + while (1) + { + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &wo->wopid, + sizeof (wo->wopid)); + if (GNUNET_OK == + GNUNET_CONTAINER_multishortmap_put (h->wops, + &wo->wopid, + wo, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + break; + } + { + char *wopids; + char *uri; + MHD_RESULT res; + + wopids = GNUNET_STRINGS_data_to_string_alloc (&wo->wopid, + sizeof (wo->wopid)); + GNUNET_asprintf (&uri, + "taler+http://withdraw/%s:%u/taler-bank-integration/%s", + h->hostname, + (unsigned int) h->port, + wopids); + GNUNET_free (wopids); + res = TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ("taler_withdraw_uri", + uri), + GNUNET_JSON_pack_data_auto ("withdrawal_id", + &wo->wopid)); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + GNUNET_free (uri); + return res; + } +} + + +/** + * Handle POST /accounts/$account_name/withdrawals request. + * + * @param h our fakebank handle + * @param connection the connection + * @param account_name name of the account + * @param upload_data request data + * @param upload_data_size size of @a upload_data in bytes + * @param con_cls closure for request + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_post_account_withdrawals_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name, + const void *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + struct ConnectionContext *cc = *con_cls; + enum GNUNET_JSON_PostResult pr; + json_t *json; + MHD_RESULT res; + + if (NULL == cc) + { + cc = GNUNET_new (struct ConnectionContext); + cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + *con_cls = cc; + } + pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + 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 TALER_Amount amount; + enum GNUNET_GenericReturnValue ret; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("amount", + h->currency, + &amount), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + (ret = TALER_MHD_parse_json_data (connection, + json, + spec))) + { + GNUNET_break_op (0); + json_decref (json); + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + } + res = do_post_account_withdrawals (h, + connection, + account_name, + &amount); + } + json_decref (json); + return res; +} diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals.h b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.h new file mode 100644 index 000000000..1becf1efc --- /dev/null +++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.h @@ -0,0 +1,54 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_post_accounts_withdrawals.c + * @brief implementation of the bank API's POST /accounts/AID/withdrawals endpoint + * @author Christian Grothoff + */ +#ifndef FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_H +#define FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * Handle POST /accounts/$account_name/withdrawals request. + * + * @param h our fakebank handle + * @param connection the connection + * @param account_name name of the account + * @param upload_data request data + * @param upload_data_size size of @a upload_data in bytes + * @param con_cls closure for request + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_post_account_withdrawals_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name, + const void *upload_data, + size_t *upload_data_size, + void **con_cls); + +#endif diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.c b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.c new file mode 100644 index 000000000..4a252f9b8 --- /dev/null +++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.c @@ -0,0 +1,97 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_post_accounts_withdrawals_abort.c + * @brief implement bank API withdrawals /abort endpoint + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_bank_post_accounts_withdrawals_abort.h" +#include "fakebank_common_lookup.h" +#include "fakebank_common_lp.h" + + +MHD_RESULT +TALER_FAKEBANK_bank_withdrawals_abort_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name, + const char *withdrawal_id) +{ + struct WithdrawalOperation *wo; + struct Account *acc; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h, + withdrawal_id); + if (NULL == wo) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_TRANSACTION_NOT_FOUND, + withdrawal_id); + } + acc = TALER_FAKEBANK_lookup_account_ (h, + account_name, + NULL); + if (NULL == acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_UNKNOWN_ACCOUNT, + account_name); + } + if (wo->debit_account != acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_TRANSACTION_NOT_FOUND, + account_name); + } + if (wo->confirmation_done) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_BANK_ABORT_CONFIRM_CONFLICT, + account_name); + } + wo->aborted = true; + TALER_FAKEBANK_notify_withdrawal_ (h, + wo); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_json (connection, + json_object (), /* FIXME: #7301 */ + MHD_HTTP_OK); +} diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.h b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.h new file mode 100644 index 000000000..ba99e493e --- /dev/null +++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.h @@ -0,0 +1,50 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_post_accounts_withdrawals_abort.h + * @brief implement bank API withdrawals /abort endpoint + * @author Christian Grothoff + */ +#ifndef FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_ABORT_H +#define FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_ABORT_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/abort request. + * + * @param h our fakebank handle + * @param connection the connection + * @param account_name name of the debited account + * @param withdrawal_id the withdrawal operation identifier + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_withdrawals_abort_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name, + const char *withdrawal_id); + +#endif diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.c b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.c new file mode 100644 index 000000000..232b4f92f --- /dev/null +++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.c @@ -0,0 +1,139 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.c + * @brief implement bank API withdrawals /confirm endpoint + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_bank_post_accounts_withdrawals_confirm.h" +#include "fakebank_common_lookup.h" +#include "fakebank_common_lp.h" +#include "fakebank_common_make_admin_transfer.h" + + +/** + * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/confirm request. + * + * @param h our fakebank handle + * @param connection the connection + * @param account_name name of the debited account + * @param withdrawal_id the withdrawal operation identifier + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_withdrawals_confirm_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name, + const char *withdrawal_id) +{ + struct WithdrawalOperation *wo; + struct Account *acc; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h, + withdrawal_id); + if (NULL == wo) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_TRANSACTION_NOT_FOUND, + withdrawal_id); + } + acc = TALER_FAKEBANK_lookup_account_ (h, + account_name, + NULL); + if (NULL == acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_UNKNOWN_ACCOUNT, + account_name); + } + if (wo->debit_account != acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_TRANSACTION_NOT_FOUND, + account_name); + } + if (NULL == wo->exchange_account) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_BANK_POST_WITHDRAWAL_OPERATION_REQUIRED, + NULL); + } + if (wo->aborted) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_BANK_CONFIRM_ABORT_CONFLICT, + account_name); + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + if (GNUNET_OK != + TALER_FAKEBANK_make_admin_transfer_ ( + h, + wo->debit_account->account_name, + wo->exchange_account->account_name, + &wo->amount, + &wo->reserve_pub, + &wo->row_id, + &wo->timestamp)) + { + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT, + NULL); + } + /* Re-acquiring the lock and continuing to operate on 'wo' + is currently (!) acceptable because we NEVER free 'wo' + until shutdown. We may want to revise this if keeping + all withdraw operations in RAM becomes an issue... */ + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + wo->confirmation_done = true; + TALER_FAKEBANK_notify_withdrawal_ (h, + wo); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_json (connection, + json_object (), + MHD_HTTP_OK); +} diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.h b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.h new file mode 100644 index 000000000..2c301ca2c --- /dev/null +++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.h @@ -0,0 +1,50 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.h + * @brief implement bank API withdrawals /confirm endpoint + * @author Christian Grothoff + */ +#ifndef FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_CONFIRM_H +#define FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_CONFIRM_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/confirm request. + * + * @param h our fakebank handle + * @param connection the connection + * @param account_name name of the debited account + * @param withdrawal_id the withdrawal operation identifier + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_withdrawals_confirm_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account_name, + const char *withdrawal_id); + +#endif diff --git a/src/bank-lib/fakebank_bank_testing_register.c b/src/bank-lib/fakebank_bank_testing_register.c new file mode 100644 index 000000000..e5720f1a6 --- /dev/null +++ b/src/bank-lib/fakebank_bank_testing_register.c @@ -0,0 +1,128 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_testing_register.c + * @brief implementation of /testing/register endpoint for the bank API + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_bank_testing_register.h" + + +MHD_RESULT +TALER_FAKEBANK_bank_testing_register_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const void *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + struct ConnectionContext *cc = *con_cls; + enum GNUNET_JSON_PostResult pr; + json_t *json; + MHD_RESULT res; + + if (NULL == cc) + { + cc = GNUNET_new (struct ConnectionContext); + cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + *con_cls = cc; + } + pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + 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 *username; + const char *password; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("username", + &username), + GNUNET_JSON_spec_string ("password", + &password), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue ret; + struct Account *acc; + + if (GNUNET_OK != + (ret = TALER_MHD_parse_json_data (connection, + json, + spec))) + { + GNUNET_break_op (0); + json_decref (json); + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + } + acc = TALER_FAKEBANK_lookup_account_ (h, + username, + NULL); + if (NULL != acc) + { + if (0 != strcmp (password, + acc->password)) + { + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_BANK_REGISTER_CONFLICT, + "password"); + } + } + else + { + acc = TALER_FAKEBANK_lookup_account_ (h, + username, + username); + GNUNET_assert (NULL != acc); + acc->password = GNUNET_strdup (password); + acc->balance = h->signup_bonus; /* magic money creation! */ + } + res = TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); + } + json_decref (json); + return res; +} diff --git a/src/bank-lib/fakebank_bank_testing_register.h b/src/bank-lib/fakebank_bank_testing_register.h new file mode 100644 index 000000000..d8744ecc2 --- /dev/null +++ b/src/bank-lib/fakebank_bank_testing_register.h @@ -0,0 +1,53 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_bank_testing_register.h + * @brief implementation of /testing/register endpoint for the bank API + * @author Christian Grothoff + */ +#ifndef FAKEBANK_BANK_TESTING_REGISTER_H +#define FAKEBANK_BANK_TESTING_REGISTER_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_lookup.h" + + +/** + * Handle POST /testing/register request. + * + * @param h our fakebank handle + * @param connection the connection + * @param upload_data request data + * @param upload_data_size size of @a upload_data in bytes + * @param con_cls closure for request + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_bank_testing_register_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const void *upload_data, + size_t *upload_data_size, + void **con_cls); + +#endif diff --git a/src/bank-lib/fakebank_common_lookup.c b/src/bank-lib/fakebank_common_lookup.c new file mode 100644 index 000000000..b4f853871 --- /dev/null +++ b/src/bank-lib/fakebank_common_lookup.c @@ -0,0 +1,103 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_common_lookup.c + * @brief common helper functions related to lookups + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_lookup.h" + +struct WithdrawalOperation * +TALER_FAKEBANK_lookup_withdrawal_operation_ (struct TALER_FAKEBANK_Handle *h, + const char *wopid) +{ + struct GNUNET_ShortHashCode sh; + + if (NULL == h->wops) + return NULL; + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (wopid, + strlen (wopid), + &sh, + sizeof (sh))) + { + GNUNET_break_op (0); + return NULL; + } + return GNUNET_CONTAINER_multishortmap_get (h->wops, + &sh); +} + + +struct Account * +TALER_FAKEBANK_lookup_account_ (struct TALER_FAKEBANK_Handle *h, + const char *name, + const char *receiver_name) +{ + struct GNUNET_HashCode hc; + size_t slen; + struct Account *account; + + memset (&hc, + 0, + sizeof (hc)); + slen = strlen (name); + GNUNET_CRYPTO_hash (name, + slen, + &hc); + GNUNET_assert (0 == + pthread_mutex_lock (&h->accounts_lock)); + account = GNUNET_CONTAINER_multihashmap_get (h->accounts, + &hc); + if (NULL == account) + { + if (NULL == receiver_name) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->accounts_lock)); + return NULL; + } + account = GNUNET_new (struct Account); + account->account_name = GNUNET_strdup (name); + account->receiver_name = GNUNET_strdup (receiver_name); + GNUNET_asprintf (&account->payto_uri, + "payto://x-taler-bank/%s/%s?receiver-name=%s", + h->hostname, + account->account_name, + account->receiver_name); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (h->currency, + &account->balance)); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (h->accounts, + &hc, + account, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->accounts_lock)); + return account; +} diff --git a/src/bank-lib/fakebank_common_lookup.h b/src/bank-lib/fakebank_common_lookup.h new file mode 100644 index 000000000..b93447743 --- /dev/null +++ b/src/bank-lib/fakebank_common_lookup.h @@ -0,0 +1,62 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_common_lookup.h + * @brief common helper functions related to lookups + * @author Christian Grothoff + */ + +#ifndef FAKEBANK_COMMON_LOOKUP_H +#define FAKEBANK_COMMON_LOOKUP_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * Lookup account with @a name, and if it does not exist, create it. + * + * @param[in,out] h bank to lookup account at + * @param name account name to resolve + * @param receiver_name receiver name in payto:// URI, + * NULL if the account must already exist + * @return account handle, NULL if account does not yet exist + */ +struct Account * +TALER_FAKEBANK_lookup_account_ ( + struct TALER_FAKEBANK_Handle *h, + const char *name, + const char *receiver_name); + + +/** + * Find withdrawal operation @a wopid in @a h. + * + * @param h fakebank handle + * @param wopid withdrawal operation ID as a string + * @return NULL if operation was not found + */ +struct WithdrawalOperation * +TALER_FAKEBANK_lookup_withdrawal_operation_ (struct TALER_FAKEBANK_Handle *h, + const char *wopid); + +#endif diff --git a/src/bank-lib/fakebank_common_lp.c b/src/bank-lib/fakebank_common_lp.c new file mode 100644 index 000000000..22a9e3ab4 --- /dev/null +++ b/src/bank-lib/fakebank_common_lp.c @@ -0,0 +1,344 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_common_lp.c + * @brief long-polling support for fakebank + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#ifdef __linux__ +#include +#endif +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +void +TALER_FAKEBANK_lp_trigger_ (struct LongPoller *lp) +{ + struct TALER_FAKEBANK_Handle *h = lp->h; + struct Account *acc = lp->account; + + GNUNET_CONTAINER_DLL_remove (acc->lp_head, + acc->lp_tail, + lp); + MHD_resume_connection (lp->conn); + GNUNET_free (lp); + h->mhd_again = true; +#ifdef __linux__ + if (-1 == h->lp_event) +#else + if ( (-1 == h->lp_event_in) && + (-1 == h->lp_event_out) ) +#endif + { + if (NULL != h->mhd_task) + GNUNET_SCHEDULER_cancel (h->mhd_task); + h->mhd_task = + GNUNET_SCHEDULER_add_now (&TALER_FAKEBANK_run_mhd_, + h); + } +} + + +void * +TALER_FAKEBANK_lp_expiration_thread_ (void *cls) +{ + struct TALER_FAKEBANK_Handle *h = cls; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + while (! h->in_shutdown) + { + struct LongPoller *lp; + int timeout_ms; + + lp = GNUNET_CONTAINER_heap_peek (h->lp_heap); + while ( (NULL != lp) && + GNUNET_TIME_absolute_is_past (lp->timeout)) + { + GNUNET_assert (lp == + GNUNET_CONTAINER_heap_remove_root (h->lp_heap)); + TALER_FAKEBANK_lp_trigger_ (lp); + lp = GNUNET_CONTAINER_heap_peek (h->lp_heap); + } + if (NULL != lp) + { + struct GNUNET_TIME_Relative rem; + unsigned long long left_ms; + + rem = GNUNET_TIME_absolute_get_remaining (lp->timeout); + left_ms = rem.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; + if (left_ms > INT_MAX) + timeout_ms = INT_MAX; + else + timeout_ms = (int) left_ms; + } + else + { + timeout_ms = -1; /* infinity */ + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + { + struct pollfd p = { +#ifdef __linux__ + .fd = h->lp_event, +#else + .fd = h->lp_event_out, +#endif + .events = POLLIN + }; + int ret; + + ret = poll (&p, + 1, + timeout_ms); + if (-1 == ret) + { + if (EINTR != errno) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "poll"); + } + else if (1 == ret) + { + /* clear event */ + uint64_t ev; + ssize_t iret; + +#ifdef __linux__ + iret = read (h->lp_event, + &ev, + sizeof (ev)); +#else + iret = read (h->lp_event_out, + &ev, + sizeof (ev)); +#endif + if (-1 == iret) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "read"); + } + else + { + GNUNET_break (sizeof (uint64_t) == iret); + } + } + } + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return NULL; +} + + +/** + * Trigger long pollers that might have been waiting + * for @a t. + * + * @param h fakebank handle + * @param t transaction to notify on + */ +void +TALER_FAKEBANK_notify_transaction_ ( + struct TALER_FAKEBANK_Handle *h, + struct Transaction *t) +{ + struct Account *debit_acc = t->debit_account; + struct Account *credit_acc = t->credit_account; + struct LongPoller *nxt; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + for (struct LongPoller *lp = debit_acc->lp_head; + NULL != lp; + lp = nxt) + { + nxt = lp->next; + if (LP_DEBIT == lp->type) + { + GNUNET_assert (lp == + GNUNET_CONTAINER_heap_remove_node (lp->hn)); + TALER_FAKEBANK_lp_trigger_ (lp); + } + } + for (struct LongPoller *lp = credit_acc->lp_head; + NULL != lp; + lp = nxt) + { + nxt = lp->next; + if (LP_CREDIT == lp->type) + { + GNUNET_assert (lp == + GNUNET_CONTAINER_heap_remove_node (lp->hn)); + TALER_FAKEBANK_lp_trigger_ (lp); + } + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); +} + + +/** + * Notify long pollers that a @a wo was updated. + * Must be called with the "big_lock" still held. + * + * @param h fakebank handle + * @param wo withdraw operation that finished + */ +void +TALER_FAKEBANK_notify_withdrawal_ ( + struct TALER_FAKEBANK_Handle *h, + const struct WithdrawalOperation *wo) +{ + struct Account *debit_acc = wo->debit_account; + struct LongPoller *nxt; + + for (struct LongPoller *lp = debit_acc->lp_head; + NULL != lp; + lp = nxt) + { + nxt = lp->next; + if ( (LP_WITHDRAW == lp->type) && + (wo == lp->wo) ) + { + GNUNET_assert (lp == + GNUNET_CONTAINER_heap_remove_node (lp->hn)); + TALER_FAKEBANK_lp_trigger_ (lp); + } + } +} + + +/** + * Task run when a long poller is about to time out. + * Only used in single-threaded mode. + * + * @param cls a `struct TALER_FAKEBANK_Handle *` + */ +static void +lp_timeout (void *cls) +{ + struct TALER_FAKEBANK_Handle *h = cls; + struct LongPoller *lp; + + h->lp_task = NULL; + while (NULL != (lp = GNUNET_CONTAINER_heap_peek (h->lp_heap))) + { + if (GNUNET_TIME_absolute_is_future (lp->timeout)) + break; + GNUNET_assert (lp == + GNUNET_CONTAINER_heap_remove_root (h->lp_heap)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Timeout reached for long poller %p\n", + lp->conn); + TALER_FAKEBANK_lp_trigger_ (lp); + } + if (NULL == lp) + return; + h->lp_task = GNUNET_SCHEDULER_add_at (lp->timeout, + &lp_timeout, + h); +} + + +/** + * Reschedule the timeout task of @a h for time @a t. + * + * @param h fakebank handle + * @param t when will the next connection timeout expire + */ +static void +reschedule_lp_timeout (struct TALER_FAKEBANK_Handle *h, + struct GNUNET_TIME_Absolute t) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Scheduling timeout task for %s\n", + GNUNET_STRINGS_absolute_time_to_string (t)); +#ifdef __linux__ + if (-1 != h->lp_event) +#else + if (-1 != h->lp_event_in && -1 != h->lp_event_out) +#endif + { + uint64_t num = 1; + + GNUNET_break (sizeof (num) == +#ifdef __linux__ + write (h->lp_event, + &num, + sizeof (num))); +#else + write (h->lp_event_in, + &num, + sizeof (num))); +#endif + } + else + { + if (NULL != h->lp_task) + GNUNET_SCHEDULER_cancel (h->lp_task); + h->lp_task = GNUNET_SCHEDULER_add_at (t, + &lp_timeout, + h); + } +} + + +void +TALER_FAKEBANK_start_lp_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + struct Account *acc, + struct GNUNET_TIME_Relative lp_timeout, + enum LongPollType dir, + const struct WithdrawalOperation *wo) +{ + struct LongPoller *lp; + bool toc; + + lp = GNUNET_new (struct LongPoller); + lp->account = acc; + lp->h = h; + lp->wo = wo; + lp->conn = connection; + lp->timeout = GNUNET_TIME_relative_to_absolute (lp_timeout); + lp->type = dir; + lp->hn = GNUNET_CONTAINER_heap_insert (h->lp_heap, + lp, + lp->timeout.abs_value_us); + toc = (lp == + GNUNET_CONTAINER_heap_peek (h->lp_heap)); + GNUNET_CONTAINER_DLL_insert (acc->lp_head, + acc->lp_tail, + lp); + MHD_suspend_connection (connection); + if (toc) + reschedule_lp_timeout (h, + lp->timeout); + +} diff --git a/src/bank-lib/fakebank_common_lp.h b/src/bank-lib/fakebank_common_lp.h new file mode 100644 index 000000000..37094e12b --- /dev/null +++ b/src/bank-lib/fakebank_common_lp.h @@ -0,0 +1,100 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_common_lp.h + * @brief long-polling support for fakebank + * @author Christian Grothoff + */ +#ifndef FAKEBANK_COMMON_LP_H +#define FAKEBANK_COMMON_LP_H +#include "taler_fakebank_lib.h" + + +/** + * Trigger the @a lp. Frees associated resources, except the entry of @a lp in + * the timeout heap. Must be called while the ``big lock`` is held. + * + * @param[in] lp long poller to trigger + */ +void +TALER_FAKEBANK_lp_trigger_ (struct LongPoller *lp); + + +/** + * Trigger long pollers that might have been waiting + * for @a t. + * + * @param h fakebank handle + * @param t transaction to notify on + */ +void +TALER_FAKEBANK_notify_transaction_ ( + struct TALER_FAKEBANK_Handle *h, + struct Transaction *t); + + +/** + * Notify long pollers that a @a wo was updated. + * Must be called with the "big_lock" still held. + * + * @param h fakebank handle + * @param wo withdraw operation that finished + */ +void +TALER_FAKEBANK_notify_withdrawal_ ( + struct TALER_FAKEBANK_Handle *h, + const struct WithdrawalOperation *wo); + + +/** + * Start long-polling for @a connection and @a acc + * for transfers in @a dir. Must be called with the + * "big lock" held. + * + * @param[in,out] h fakebank handle + * @param[in,out] connection to suspend + * @param[in,out] acc account affected + * @param lp_timeout how long to suspend + * @param dir direction of transfers to watch for + * @param wo withdraw operation to watch, only + * if @a dir is #LP_WITHDRAW + */ +void +TALER_FAKEBANK_start_lp_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + struct Account *acc, + struct GNUNET_TIME_Relative lp_timeout, + enum LongPollType dir, + const struct WithdrawalOperation *wo); + + +/** + * Main routine of a thread that is run to wake up connections that have hit + * their timeout. Runs until in_shutdown is set to true. Must be send signals + * via lp_event on shutdown and/or whenever the heap changes to an earlier + * timeout. + * + * @param cls a `struct TALER_FAKEBANK_Handle *` + * @return NULL + */ +void * +TALER_FAKEBANK_lp_expiration_thread_ (void *cls); + +#endif diff --git a/src/bank-lib/fakebank_common_make_admin_transfer.c b/src/bank-lib/fakebank_common_make_admin_transfer.c new file mode 100644 index 000000000..4a11d412c --- /dev/null +++ b/src/bank-lib/fakebank_common_make_admin_transfer.c @@ -0,0 +1,117 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_common_make_admin_transfer.c + * @brief routine to create transfers to the exchange + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_lookup.h" +#include "fakebank_common_lp.h" +#include "fakebank_common_transact.h" + + +enum GNUNET_GenericReturnValue +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, + uint64_t *row_id, + struct GNUNET_TIME_Timestamp *timestamp) +{ + struct Transaction *t; + const struct GNUNET_PeerIdentity *pid; + struct Account *debit_acc; + struct Account *credit_acc; + + GNUNET_static_assert (sizeof (*pid) == + sizeof (*reserve_pub)); + pid = (const struct GNUNET_PeerIdentity *) reserve_pub; + GNUNET_assert (NULL != debit_account); + GNUNET_assert (NULL != credit_account); + 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://"))); + debit_acc = TALER_FAKEBANK_lookup_account_ (h, + debit_account, + debit_account); + credit_acc = TALER_FAKEBANK_lookup_account_ (h, + credit_account, + credit_account); + GNUNET_assert (0 == + pthread_mutex_lock (&h->rpubs_lock)); + t = GNUNET_CONTAINER_multipeermap_get (h->rpubs, + pid); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->rpubs_lock)); + if (NULL != t) + { + /* duplicate reserve public key not allowed */ + GNUNET_break_op (0); + return GNUNET_NO; + } + + t = GNUNET_new (struct Transaction); + t->unchecked = true; + t->debit_account = debit_acc; + t->credit_account = credit_acc; + t->amount = *amount; + t->date = GNUNET_TIME_timestamp_get (); + if (NULL != timestamp) + *timestamp = t->date; + t->type = T_CREDIT; + t->subject.credit.reserve_pub = *reserve_pub; + TALER_FAKEBANK_transact_ (h, + t); + if (NULL != row_id) + *row_id = t->row_id; + GNUNET_assert (0 == + pthread_mutex_lock (&h->rpubs_lock)); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multipeermap_put ( + h->rpubs, + pid, + t, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->rpubs_lock)); + 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); + TALER_FAKEBANK_notify_transaction_ (h, + t); + return GNUNET_OK; +} diff --git a/src/bank-lib/fakebank_common_make_admin_transfer.h b/src/bank-lib/fakebank_common_make_admin_transfer.h new file mode 100644 index 000000000..841cfb481 --- /dev/null +++ b/src/bank-lib/fakebank_common_make_admin_transfer.h @@ -0,0 +1,56 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_common_make_admin_transfer.h + * @brief routine to create transfers to the exchange + * @author Christian Grothoff + */ +#ifndef FAKEBANK_COMMON_MAKE_ADMIN_TRANSFER_H +#define FAKEBANK_COMMON_MAKE_ADMIN_TRANSFER_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * 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 + * @param[out] row_id serial_id of the transfer + * @param[out] timestamp when was the transfer made + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +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, + uint64_t *row_id, + struct GNUNET_TIME_Timestamp *timestamp); + +#endif diff --git a/src/bank-lib/fakebank_common_parser.c b/src/bank-lib/fakebank_common_parser.c new file mode 100644 index 000000000..98b8d6097 --- /dev/null +++ b/src/bank-lib/fakebank_common_parser.c @@ -0,0 +1,138 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_common_parser.c + * @brief functions to help parse REST requests + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +enum GNUNET_GenericReturnValue +TALER_FAKEBANK_common_parse_history_args ( + const struct TALER_FAKEBANK_Handle *h, + 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; + char dummy; + + 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%c", + &d, + &dummy)) ) + { + /* 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_op (0); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "delta")) + ? GNUNET_NO + : GNUNET_SYSERR; + } + if ( (NULL != long_poll_ms) && + (1 != sscanf (long_poll_ms, + "%llu%c", + &lp_timeout, + &dummy)) ) + { + /* 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_op (0); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "long_poll_ms")) + ? GNUNET_NO + : GNUNET_SYSERR; + } + if ( (NULL != start) && + (1 != sscanf (start, + "%llu%c", + &sval, + &dummy)) ) + { + /* 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_op (0); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "start")) + ? GNUNET_NO + : GNUNET_SYSERR; + } + if (NULL == start) + ha->start_idx = (d > 0) ? 0 : h->serial_counter; + else + ha->start_idx = (uint64_t) sval; + ha->delta = (int64_t) d; + if (0 == ha->delta) + { + GNUNET_break_op (0); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "delta")) + ? GNUNET_NO + : GNUNET_SYSERR; + } + ha->lp_timeout + = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + lp_timeout); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Request for %lld records from %llu\n", + (long long) ha->delta, + (unsigned long long) ha->start_idx); + return GNUNET_OK; +} diff --git a/src/bank-lib/fakebank_common_parser.h b/src/bank-lib/fakebank_common_parser.h new file mode 100644 index 000000000..8e0d14649 --- /dev/null +++ b/src/bank-lib/fakebank_common_parser.h @@ -0,0 +1,50 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_common_parser.h + * @brief functions to help parse REST requests + * @author Christian Grothoff + */ +#ifndef FAKEBANK_COMMON_PARSER_H +#define FAKEBANK_COMMON_PARSER_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + +/** + * Parse URL history arguments, of _both_ APIs: + * /history/incoming and /history/outgoing. + * + * @param h bank handle to work on + * @param connection MHD connection. + * @param[out] ha will contain the parsed values. + * @return #GNUNET_OK only if the parsing succeeds, + * #GNUNET_SYSERR if it failed, + * #GNUNET_NO if it failed and an error was returned + */ +enum GNUNET_GenericReturnValue +TALER_FAKEBANK_common_parse_history_args ( + const struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + struct HistoryArgs *ha); + +#endif diff --git a/src/bank-lib/fakebank_common_transact.c b/src/bank-lib/fakebank_common_transact.c new file mode 100644 index 000000000..a099ef966 --- /dev/null +++ b/src/bank-lib/fakebank_common_transact.c @@ -0,0 +1,261 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_common_transact.c + * @brief actual transaction logic for FAKEBANK + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_lookup.h" +#include "fakebank_common_lp.h" +#include "fakebank_common_transact.h" + + +/** + * Update @a account balance by @a amount. + * + * The @a big_lock must already be locked when calling + * this function. + * + * @param[in,out] account account to update + * @param amount balance change + * @param debit true to subtract, false to add @a amount + */ +static void +update_balance (struct Account *account, + const struct TALER_Amount *amount, + bool debit) +{ + if (debit == account->is_negative) + { + GNUNET_assert (0 <= + TALER_amount_add (&account->balance, + &account->balance, + amount)); + return; + } + if (0 <= TALER_amount_cmp (&account->balance, + amount)) + { + GNUNET_assert (0 <= + TALER_amount_subtract (&account->balance, + &account->balance, + amount)); + } + else + { + GNUNET_assert (0 <= + TALER_amount_subtract (&account->balance, + amount, + &account->balance)); + account->is_negative = ! account->is_negative; + } +} + + +/** + * Add transaction to the debit and credit accounts, + * updating the balances as needed. + * + * The transaction @a t must already be locked + * when calling this function! + * + * @param[in,out] h bank handle + * @param[in,out] t transaction to add to account lists + */ +void +TALER_FAKEBANK_transact_ (struct TALER_FAKEBANK_Handle *h, + struct Transaction *t) +{ + struct Account *debit_acc = t->debit_account; + struct Account *credit_acc = t->credit_account; + uint64_t row_id; + struct Transaction *old; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + row_id = ++h->serial_counter; + old = h->transactions[row_id % h->ram_limit]; + h->transactions[row_id % h->ram_limit] = t; + t->row_id = row_id; + GNUNET_CONTAINER_MDLL_insert_tail (out, + debit_acc->out_head, + debit_acc->out_tail, + t); + update_balance (debit_acc, + &t->amount, + true); + GNUNET_CONTAINER_MDLL_insert_tail (in, + credit_acc->in_head, + credit_acc->in_tail, + t); + update_balance (credit_acc, + &t->amount, + false); + if (NULL != old) + { + struct Account *da; + struct Account *ca; + + da = old->debit_account; + ca = old->credit_account; + /* slot was already in use, must clean out old + entry first! */ + GNUNET_CONTAINER_MDLL_remove (out, + da->out_head, + da->out_tail, + old); + GNUNET_CONTAINER_MDLL_remove (in, + ca->in_head, + ca->in_tail, + old); + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + if ( (NULL != old) && + (T_DEBIT == old->type) ) + { + GNUNET_assert (0 == + pthread_mutex_lock (&h->uuid_map_lock)); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_remove (h->uuid_map, + &old->request_uid, + old)); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->uuid_map_lock)); + } + GNUNET_free (old); +} + + +enum GNUNET_GenericReturnValue +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 GNUNET_TIME_Timestamp *timestamp) +{ + struct Transaction *t; + struct Account *debit_acc; + struct Account *credit_acc; + size_t url_len; + + 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://"))); + url_len = strlen (exchange_base_url); + GNUNET_assert (url_len < MAX_URL_LEN); + debit_acc = TALER_FAKEBANK_lookup_account_ (h, + debit_account, + debit_account); + credit_acc = TALER_FAKEBANK_lookup_account_ (h, + credit_account, + credit_account); + if (NULL != request_uid) + { + GNUNET_assert (0 == + pthread_mutex_lock (&h->uuid_map_lock)); + t = GNUNET_CONTAINER_multihashmap_get (h->uuid_map, + request_uid); + if (NULL != t) + { + if ( (debit_acc != t->debit_account) || + (credit_acc != 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); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->uuid_map_lock)); + return GNUNET_SYSERR; + } + *ret_row_id = t->row_id; + *timestamp = t->date; + GNUNET_assert (0 == + pthread_mutex_unlock (&h->uuid_map_lock)); + return GNUNET_OK; + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->uuid_map_lock)); + } + t = GNUNET_new (struct Transaction); + t->unchecked = true; + t->debit_account = debit_acc; + t->credit_account = credit_acc; + t->amount = *amount; + t->date = GNUNET_TIME_timestamp_get (); + if (NULL != timestamp) + *timestamp = t->date; + t->type = T_DEBIT; + GNUNET_memcpy (t->subject.debit.exchange_base_url, + exchange_base_url, + url_len); + 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; + TALER_FAKEBANK_transact_ (h, + t); + GNUNET_assert (0 == + pthread_mutex_lock (&h->uuid_map_lock)); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put ( + h->uuid_map, + &t->request_uid, + t, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->uuid_map_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Making transfer %llu from %s to %s over %s and subject %s; for exchange: %s\n", + (unsigned long long) t->row_id, + debit_account, + credit_account, + TALER_amount2s (amount), + TALER_B2S (subject), + exchange_base_url); + *ret_row_id = t->row_id; + TALER_FAKEBANK_notify_transaction_ (h, + t); + return GNUNET_OK; +} diff --git a/src/bank-lib/fakebank_common_transact.h b/src/bank-lib/fakebank_common_transact.h new file mode 100644 index 000000000..0914785e9 --- /dev/null +++ b/src/bank-lib/fakebank_common_transact.h @@ -0,0 +1,76 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_common_transact.h + * @brief actual transaction logic for FAKEBANK + * @author Christian Grothoff + */ +#ifndef FAKEBANK_COMMON_TRANSACT_H +#define FAKEBANK_COMMON_TRANSACT_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * Add transaction to the debit and credit accounts, + * updating the balances as needed. + * + * The transaction @a t must already be locked + * when calling this function! + * + * @param[in,out] h bank handle + * @param[in,out] t transaction to add to account lists + */ +void +TALER_FAKEBANK_transact_ (struct TALER_FAKEBANK_Handle *h, + struct Transaction *t); + + +/** + * 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 + * @param[out] timestamp set to the time of the transfer + * @return #GNUNET_YES if the transfer was successful, + * #GNUNET_SYSERR if the request_uid was reused for a different transfer + */ +enum GNUNET_GenericReturnValue +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 GNUNET_TIME_Timestamp *timestamp); + +#endif diff --git a/src/bank-lib/fakebank_stop.c b/src/bank-lib/fakebank_stop.c new file mode 100644 index 000000000..e31d47523 --- /dev/null +++ b/src/bank-lib/fakebank_stop.c @@ -0,0 +1,192 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_stop.c + * @brief library that fakes being a Taler bank for testcases + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#ifdef __linux__ +#include +#endif +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_lp.h" + + +/** + * Helper function to free memory when finished. + * + * @param cls NULL + * @param key key of the account to free (ignored) + * @param val a `struct Account` to free. + */ +static enum GNUNET_GenericReturnValue +free_account (void *cls, + const struct GNUNET_HashCode *key, + void *val) +{ + struct Account *account = val; + + (void) cls; + (void) key; + GNUNET_assert (NULL == account->lp_head); + GNUNET_free (account->account_name); + GNUNET_free (account->receiver_name); + GNUNET_free (account->payto_uri); + GNUNET_free (account->password); + GNUNET_free (account); + return GNUNET_OK; +} + + +/** + * Helper function to free memory when finished. + * + * @param cls NULL + * @param key key of the operation to free (ignored) + * @param val a `struct WithdrawalOperation *` to free. + */ +static enum GNUNET_GenericReturnValue +free_withdraw_op (void *cls, + const struct GNUNET_ShortHashCode *key, + void *val) +{ + struct WithdrawalOperation *wo = val; + + (void) cls; + (void) key; + GNUNET_free (wo); + return GNUNET_OK; +} + + +void +TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h) +{ + if (NULL != h->lp_task) + { + GNUNET_SCHEDULER_cancel (h->lp_task); + h->lp_task = NULL; + } +#if EPOLL_SUPPORT + if (NULL != h->mhd_rfd) + { + GNUNET_NETWORK_socket_free_memory_only_ (h->mhd_rfd); + h->mhd_rfd = NULL; + } +#endif +#ifdef __linux__ + if (-1 != h->lp_event) +#else + if (-1 != h->lp_event_in && -1 != h->lp_event_out) +#endif + { + uint64_t val = 1; + void *ret; + struct LongPoller *lp; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + h->in_shutdown = true; + while (NULL != (lp = GNUNET_CONTAINER_heap_remove_root (h->lp_heap))) + TALER_FAKEBANK_lp_trigger_ (lp); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); +#ifdef __linux__ + GNUNET_break (sizeof (val) == + write (h->lp_event, + &val, + sizeof (val))); +#else + GNUNET_break (sizeof (val) == + write (h->lp_event_in, + &val, + sizeof (val))); +#endif + GNUNET_break (0 == + pthread_join (h->lp_thread, + &ret)); + GNUNET_break (NULL == ret); +#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 + } + else + { + struct LongPoller *lp; + + while (NULL != (lp = GNUNET_CONTAINER_heap_remove_root (h->lp_heap))) + TALER_FAKEBANK_lp_trigger_ (lp); + } + if (NULL != h->mhd_bank) + { + MHD_stop_daemon (h->mhd_bank); + h->mhd_bank = NULL; + } + if (NULL != h->mhd_task) + { + GNUNET_SCHEDULER_cancel (h->mhd_task); + h->mhd_task = NULL; + } + if (NULL != h->accounts) + { + GNUNET_CONTAINER_multihashmap_iterate (h->accounts, + &free_account, + NULL); + GNUNET_CONTAINER_multihashmap_destroy (h->accounts); + } + if (NULL != h->wops) + { + GNUNET_CONTAINER_multishortmap_iterate (h->wops, + &free_withdraw_op, + NULL); + GNUNET_CONTAINER_multishortmap_destroy (h->wops); + } + GNUNET_CONTAINER_multihashmap_destroy (h->uuid_map); + GNUNET_CONTAINER_multipeermap_destroy (h->rpubs); + GNUNET_CONTAINER_heap_destroy (h->lp_heap); + GNUNET_assert (0 == + pthread_mutex_destroy (&h->big_lock)); + GNUNET_assert (0 == + pthread_mutex_destroy (&h->uuid_map_lock)); + GNUNET_assert (0 == + pthread_mutex_destroy (&h->accounts_lock)); + GNUNET_assert (0 == + pthread_mutex_destroy (&h->rpubs_lock)); + for (uint64_t i = 0; iram_limit; i++) + GNUNET_free (h->transactions[i]); + GNUNET_free (h->transactions); + GNUNET_free (h->my_baseurl); + GNUNET_free (h->currency); + GNUNET_free (h->exchange_url); + GNUNET_free (h->hostname); + GNUNET_free (h); +} diff --git a/src/bank-lib/fakebank_tbi.c b/src/bank-lib/fakebank_tbi.c new file mode 100644 index 000000000..27b930d9f --- /dev/null +++ b/src/bank-lib/fakebank_tbi.c @@ -0,0 +1,126 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbi.c + * @brief main entry point to the Taler Bank Integration (TBI) API implementation + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_tbi.h" +#include "fakebank_tbi_get_withdrawal_operation.h" +#include "fakebank_tbi_post_withdrawal_operation.h" + + +MHD_RESULT +TALER_FAKEBANK_tbi_main_ (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + if (0 == strcasecmp (method, + MHD_HTTP_METHOD_HEAD)) + method = MHD_HTTP_METHOD_GET; + if ( (0 == strcmp (url, + "/config")) && + (0 == strcasecmp (method, + MHD_HTTP_METHOD_GET)) ) + { + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ("version", + "0:0:0"), + GNUNET_JSON_pack_string ("currency", + h->currency), + GNUNET_JSON_pack_string ("name", + "taler-bank-integration")); + } + if ( (0 == strncmp (url, + "/withdrawal-operation/", + strlen ("/withdrawal-operation/"))) && + (0 == strcasecmp (method, + MHD_HTTP_METHOD_GET)) ) + { + const char *wopid = &url[strlen ("/withdrawal-operation/")]; + const char *lp_s + = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "long_poll_ms"); + struct GNUNET_TIME_Relative lp = GNUNET_TIME_UNIT_ZERO; + + if (NULL != lp_s) + { + unsigned long long d; + char dummy; + + if (1 != sscanf (lp_s, + "%llu%c", + &d, + &dummy)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "long_poll_ms"); + } + lp = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + d); + } + return TALER_FAKEBANK_tbi_get_withdrawal_operation_ (h, + connection, + wopid, + lp, + con_cls); + + } + if ( (0 == strncmp (url, + "/withdrawal-operation/", + strlen ("/withdrawal-operation/"))) && + (0 == strcasecmp (method, + MHD_HTTP_METHOD_POST)) ) + { + const char *wopid = &url[strlen ("/withdrawal-operation/")]; + + return TALER_FAKEBANK_tbi_post_withdrawal (h, + connection, + wopid, + upload_data, + upload_data_size, + con_cls); + } + + TALER_LOG_ERROR ("Breaking URL: %s %s\n", + method, + url); + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + url); +} diff --git a/src/bank-lib/fakebank_tbi.h b/src/bank-lib/fakebank_tbi.h new file mode 100644 index 000000000..ef9f35fa2 --- /dev/null +++ b/src/bank-lib/fakebank_tbi.h @@ -0,0 +1,54 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbi.c + * @brief main entry point to the Taler Bank Integration (TBI) API implementation + * @author Christian Grothoff + */ +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include + + +#ifndef FAKEBANK_TBI_H +#define FAKEBANK_TBI_H + +/** + * Handle incoming HTTP request to the bank integration API. + * + * @param h our fakebank handle + * @param connection the connection + * @param url the requested url + * @param method the method (POST, GET, ...) + * @param upload_data request data + * @param upload_data_size size of @a upload_data in bytes + * @param con_cls closure for request + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_tbi_main_ (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *upload_data, + size_t *upload_data_size, + void **con_cls); + +#endif diff --git a/src/bank-lib/fakebank_tbi_get_withdrawal_operation.c b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.c new file mode 100644 index 000000000..fba8c5de6 --- /dev/null +++ b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.c @@ -0,0 +1,127 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbi_get_withdrawal_operation.c + * @brief Implementation of the GET /withdrawal-operation/ request of the Taler Bank Integration API + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_lookup.h" +#include "fakebank_common_lp.h" +#include "fakebank_tbi_get_withdrawal_operation.h" + +/** + * Function called to clean up a withdraw context. + * + * @param cls a `struct WithdrawContext *` + */ +static void +withdraw_cleanup (void *cls) +{ + struct WithdrawContext *wc = cls; + + GNUNET_free (wc); +} + + +MHD_RESULT +TALER_FAKEBANK_tbi_get_withdrawal_operation_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *wopid, + struct GNUNET_TIME_Relative lp, + void **con_cls) +{ + struct ConnectionContext *cc = *con_cls; + struct WithdrawContext *wc; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + if (NULL == cc) + { + cc = GNUNET_new (struct ConnectionContext); + cc->ctx_cleaner = &withdraw_cleanup; + *con_cls = cc; + wc = GNUNET_new (struct WithdrawContext); + cc->ctx = wc; + wc->wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h, + wopid); + if (NULL == wc->wo) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_TRANSACTION_NOT_FOUND, + wopid); + } + wc->timeout = GNUNET_TIME_relative_to_absolute (lp); + } + else + { + wc = cc->ctx; + } + if (GNUNET_TIME_absolute_is_past (wc->timeout) || + h->in_shutdown || + wc->wo->confirmation_done || + wc->wo->aborted) + { + json_t *wt; + + wt = json_array (); + GNUNET_assert (NULL != wt); + GNUNET_assert (0 == + json_array_append_new (wt, + json_string ("x-taler-bank"))); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_bool ("aborted", + wc->wo->aborted), + GNUNET_JSON_pack_bool ("selection_done", + wc->wo->selection_done), + GNUNET_JSON_pack_bool ("transfer_done", + wc->wo->confirmation_done), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("suggested_exchange", + h->exchange_url)), + TALER_JSON_pack_amount ("amount", + &wc->wo->amount), + GNUNET_JSON_pack_array_steal ("wire_types", + wt)); + } + + TALER_FAKEBANK_start_lp_ (h, + connection, + wc->wo->debit_account, + GNUNET_TIME_absolute_get_remaining (wc->timeout), + LP_WITHDRAW, + wc->wo); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return MHD_YES; +} diff --git a/src/bank-lib/fakebank_tbi_get_withdrawal_operation.h b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.h new file mode 100644 index 000000000..b42e5a768 --- /dev/null +++ b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.h @@ -0,0 +1,51 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbi_get_withdrawal_operation.h + * @brief Implementation of the GET /withdrawal-operation/ request of the Taler Bank Integration API + * @author Christian Grothoff + */ +#ifndef FAKEBANK_TBI_GET_WITHDRAWAL_OPERATION_H +#define FAKEBANK_TBI_GET_WITHDRAWAL_OPERATION_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + +/** + * Handle GET /withdrawal-operation/{wopid} request. + * + * @param h the handle + * @param connection the connection + * @param wopid the withdrawal operation identifier + * @param lp how long is the long-polling timeout + * @param con_cls closure for request + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_tbi_get_withdrawal_operation_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *wopid, + struct GNUNET_TIME_Relative lp, + void **con_cls); + +#endif diff --git a/src/bank-lib/fakebank_tbi_post_withdrawal_operation.c b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.c new file mode 100644 index 000000000..3dbbb3c91 --- /dev/null +++ b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.c @@ -0,0 +1,221 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbi_post_withdrawal_operation.c + * @brief library that fakes being a Taler bank for testcases + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_lookup.h" +#include "fakebank_tbi_post_withdrawal_operation.h" + + +/** + * Execute POST /withdrawal-operation/ request. + * + * @param h our handle + * @param connection the connection + * @param wopid the withdrawal operation identifier + * @param reserve_pub public key of the reserve + * @param exchange_payto_uri payto://-URI of the exchange + * @return MHD result code + */ +static MHD_RESULT +do_post_withdrawal ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *wopid, + const struct TALER_ReservePublicKeyP *reserve_pub, + const char *exchange_payto_uri) +{ + struct WithdrawalOperation *wo; + char *credit_name; + struct Account *credit_account; + + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h, + wopid); + if (NULL == wo) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_TRANSACTION_NOT_FOUND, + wopid); + } + if ( (wo->selection_done) && + (0 != GNUNET_memcmp (&wo->reserve_pub, + reserve_pub)) ) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT, + "reserve public key changed"); + } + { + /* check if reserve_pub is already in use */ + const struct GNUNET_PeerIdentity *pid; + + pid = (const struct GNUNET_PeerIdentity *) &wo->reserve_pub; + if (GNUNET_CONTAINER_multipeermap_contains (h->rpubs, + pid)) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT, + NULL); + } + } + credit_name = TALER_xtalerbank_account_from_payto (exchange_payto_uri); + if (NULL == credit_name) + { + GNUNET_break_op (0); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PAYTO_URI_MALFORMED, + NULL); + } + credit_account = TALER_FAKEBANK_lookup_account_ (h, + credit_name, + NULL); + if (NULL == credit_account) + { + MHD_RESULT res; + + GNUNET_break_op (0); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_UNKNOWN_ACCOUNT, + credit_name); + GNUNET_free (credit_name); + return res; + } + GNUNET_free (credit_name); + if ( (NULL != wo->exchange_account) && + (credit_account != wo->exchange_account) ) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT, + "exchange account changed"); + } + wo->exchange_account = credit_account; + wo->reserve_pub = *reserve_pub; + wo->selection_done = true; + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_bool ("transfer_done", + wo->confirmation_done)); +} + + +MHD_RESULT +TALER_FAKEBANK_tbi_post_withdrawal ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *wopid, + const void *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + struct ConnectionContext *cc = *con_cls; + enum GNUNET_JSON_PostResult pr; + json_t *json; + MHD_RESULT res; + + if (NULL == cc) + { + cc = GNUNET_new (struct ConnectionContext); + cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + *con_cls = cc; + } + pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + 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 TALER_ReservePublicKeyP reserve_pub; + const char *exchange_payto_url; + enum GNUNET_GenericReturnValue ret; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_pub", + &reserve_pub), + GNUNET_JSON_spec_string ("selected_exchange", + &exchange_payto_url), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + (ret = TALER_MHD_parse_json_data (connection, + json, + spec))) + { + GNUNET_break_op (0); + json_decref (json); + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + } + res = do_post_withdrawal (h, + connection, + wopid, + &reserve_pub, + exchange_payto_url); + } + json_decref (json); + return res; +} diff --git a/src/bank-lib/fakebank_tbi_post_withdrawal_operation.h b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.h new file mode 100644 index 000000000..8873bb5f6 --- /dev/null +++ b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.h @@ -0,0 +1,53 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbi_post_withdrawal_operation.c + * @brief Implementation of the Taler Bank Integration API for POAT /withdrawal-operation/ requests + * @author Christian Grothoff + */ +#ifndef FAKEBANK_TBI_POST_WITHDRAWAL_OPERATION_H +#define FAKEBANK_TBI_POST_WITHDRAWAL_OPERATION_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + +/** + * Handle POST /withdrawal-operation/ request. + * + * @param h our fakebank handle + * @param connection the connection + * @param wopid the withdrawal operation identifier + * @param upload_data request data + * @param upload_data_size size of @a upload_data in bytes + * @param con_cls closure for request + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_tbi_post_withdrawal ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *wopid, + const void *upload_data, + size_t *upload_data_size, + void **con_cls); + +#endif diff --git a/src/bank-lib/fakebank_tbr.c b/src/bank-lib/fakebank_tbr.c new file mode 100644 index 000000000..de96394e0 --- /dev/null +++ b/src/bank-lib/fakebank_tbr.c @@ -0,0 +1,74 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbr.c + * @brief main entry point for the Taler Bank Revenue API + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_tbr_get_history.h" +#include "fakebank_tbr_get_root.h" + + +MHD_RESULT +TALER_FAKEBANK_tbr_main_ ( + 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 - Anastasis API: serving URL `%s' for account `%s'\n", + url, + account); + if (0 == strcasecmp (method, + MHD_HTTP_METHOD_GET)) + { + if ( (0 == strcmp (url, + "/history/incoming")) && + (NULL != account) ) + return TALER_FAKEBANK_tbr_get_history_incoming (h, + connection, + account, + con_cls); + if (0 == strcmp (url, + "/")) + return TALER_FAKEBANK_tbr_get_root (h, + connection); + } + /* Unexpected URL path, just close the connection. */ + TALER_LOG_ERROR ("Breaking URL: %s %s\n", + method, + url); + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + url); +} diff --git a/src/bank-lib/fakebank_tbr.h b/src/bank-lib/fakebank_tbr.h new file mode 100644 index 000000000..fb9bdfefc --- /dev/null +++ b/src/bank-lib/fakebank_tbr.h @@ -0,0 +1,58 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbr.h + * @brief main entry point for the Taler Bank Revenue API + * @author Christian Grothoff + */ +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +#ifndef FAKEBANK_TBR_H +#define FAKEBANK_TBR_H + +/** + * Handle incoming HTTP request to the Taler Bank Revenue API. + * + * @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 + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_tbr_main_ ( + 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); + +#endif diff --git a/src/bank-lib/fakebank_tbr_get_history.c b/src/bank-lib/fakebank_tbr_get_history.c new file mode 100644 index 000000000..3a7bfa450 --- /dev/null +++ b/src/bank-lib/fakebank_tbr_get_history.c @@ -0,0 +1,305 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbr_get_history.c + * @brief library that fakes being a Taler bank for testcases + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_lookup.h" +#include "fakebank_common_lp.h" +#include "fakebank_common_parser.h" +#include "fakebank_tbr_get_history.h" + + +/** + * Function called to clean up a history context. + * + * @param cls a `struct HistoryContext *` + */ +static void +history_cleanup (void *cls) +{ + struct HistoryContext *hc = cls; + + GNUNET_free (hc->payto_uri); + json_decref (hc->history); + GNUNET_free (hc); +} + + +MHD_RESULT +TALER_FAKEBANK_tbr_get_history_incoming ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account, + void **con_cls) +{ + struct ConnectionContext *cc = *con_cls; + struct HistoryContext *hc; + const struct Transaction *pos; + enum GNUNET_GenericReturnValue ret; + + if (NULL == cc) + { + cc = GNUNET_new (struct ConnectionContext); + cc->ctx_cleaner = &history_cleanup; + *con_cls = cc; + hc = GNUNET_new (struct HistoryContext); + cc->ctx = hc; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling /history/incoming connection %p\n", + connection); + if (GNUNET_OK != + (ret = TALER_FAKEBANK_common_parse_history_args (h, + connection, + &hc->ha))) + { + GNUNET_break_op (0); + return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; + } + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + hc->acc = TALER_FAKEBANK_lookup_account_ (h, + account, + NULL); + if (NULL == hc->acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_UNKNOWN_ACCOUNT, + account); + } + /* FIXME: was simply: acc->payto_uri -- same!? */ + GNUNET_asprintf (&hc->payto_uri, + "payto://x-taler-bank/localhost/%s?receiver-name=%s", + account, + hc->acc->receiver_name); + GNUNET_assert (0 == strcmp (hc->payto_uri, + hc->acc->payto_uri)); + hc->history = json_array (); + if (NULL == hc->history) + { + GNUNET_break (0); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return MHD_NO; + } + hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout); + } + else + { + hc = cc->ctx; + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + } + + if (! hc->ha.have_start) + { + pos = (0 > hc->ha.delta) + ? hc->acc->in_tail + : hc->acc->in_head; + } + else + { + struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit]; + bool overflow; + uint64_t dir; + bool skip = true; + + overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) ); + dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1; + /* If account does not match, linear scan for + first matching account. */ + while ( (! overflow) && + (NULL != t) && + (t->credit_account != hc->acc) ) + { + skip = false; + t = h->transactions[(t->row_id + dir) % h->ram_limit]; + if ( (NULL != t) && + (t->row_id == hc->ha.start_idx) ) + overflow = true; /* full circle, give up! */ + } + if ( (NULL == t) || + overflow) + { + /* FIXME: these conditions are unclear to me. */ + if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) && + (0 < hc->ha.delta)) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + if (overflow) + return TALER_MHD_reply_with_ec ( + connection, + TALER_EC_BANK_ANCIENT_TRANSACTION_GONE, + NULL); + goto finish; + } + if (h->in_shutdown) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + goto finish; + } + TALER_FAKEBANK_start_lp_ (h, + connection, + hc->acc, + GNUNET_TIME_absolute_get_remaining ( + hc->timeout), + LP_CREDIT, + NULL); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return MHD_YES; + } + if (skip) + { + /* range from application is exclusive, skip the + matching entry */ + if (0 > hc->ha.delta) + pos = t->prev_in; + else + pos = t->next_in; + } + else + { + pos = t; + } + } + if (NULL != pos) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Returning %lld credit transactions starting (inclusive) from %llu\n", + (long long) hc->ha.delta, + (unsigned long long) pos->row_id); + while ( (0 != hc->ha.delta) && + (NULL != pos) ) + { + json_t *trans; + char *subject; + + if (T_DEBIT != pos->type) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unexpected CREDIT transaction #%llu for account `%s'\n", + (unsigned long long) pos->row_id, + account); + if (0 > hc->ha.delta) + pos = pos->prev_in; + if (0 < hc->ha.delta) + pos = pos->next_in; + continue; + } + + { + char *wtids; + + wtids = GNUNET_STRINGS_data_to_string_alloc ( + &pos->subject.debit.wtid, + sizeof (pos->subject.debit.wtid)); + GNUNET_asprintf (&subject, + "%s %s", + wtids, + pos->subject.debit.exchange_base_url); + GNUNET_free (wtids); + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Found transaction over %s with subject %s\n", + TALER_amount2s (&pos->amount), + subject); + trans = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "RESERVE"), + GNUNET_JSON_pack_uint64 ("row_id", + pos->row_id), + GNUNET_JSON_pack_timestamp ("date", + pos->date), + TALER_JSON_pack_amount ("amount", + &pos->amount), + GNUNET_JSON_pack_string ("debit_account", + pos->debit_account->payto_uri), + GNUNET_JSON_pack_string ("subject", + subject)); + GNUNET_free (subject); + GNUNET_assert (NULL != trans); + GNUNET_assert (0 == + json_array_append_new (hc->history, + trans)); + if (hc->ha.delta > 0) + hc->ha.delta--; + else + hc->ha.delta++; + if (0 > hc->ha.delta) + pos = pos->prev_in; + if (0 < hc->ha.delta) + pos = pos->next_in; + } + if ( (0 == json_array_size (hc->history)) && + (! h->in_shutdown) && + (GNUNET_TIME_absolute_is_future (hc->timeout)) && + (0 < hc->ha.delta)) + { + TALER_FAKEBANK_start_lp_ (h, + connection, + hc->acc, + GNUNET_TIME_absolute_get_remaining (hc->timeout), + LP_CREDIT, + NULL); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return MHD_YES; + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); +finish: + if (0 == json_array_size (hc->history)) + { + GNUNET_break (h->in_shutdown || + (! GNUNET_TIME_absolute_is_future (hc->timeout))); + return TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); + } + { + json_t *h = hc->history; + + hc->history = NULL; + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ( + "credit_account", + hc->payto_uri), + GNUNET_JSON_pack_array_steal ( + "incoming_transactions", + h)); + } +} diff --git a/src/bank-lib/fakebank_tbr_get_history.h b/src/bank-lib/fakebank_tbr_get_history.h new file mode 100644 index 000000000..99170ab7b --- /dev/null +++ b/src/bank-lib/fakebank_tbr_get_history.h @@ -0,0 +1,52 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbr_get_history.h + * @brief library that fakes being a Taler bank for testcases + * @author Christian Grothoff + */ +#ifndef FAKEBANK_TBR_GET_HISTORY_H +#define FAKEBANK_TBR_GET_HISTORY_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * Handle incoming HTTP request for /history/incoming + * of the Anastasis API. This one can return transactions + * created by debits from the exchange! + * + * @param h the fakebank handle + * @param connection the connection + * @param account which account the request is about + * @param con_cls closure for request (NULL or &special_ptr) + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_tbr_get_history_incoming ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account, + void **con_cls); + +#endif diff --git a/src/bank-lib/fakebank_tbr_get_root.c b/src/bank-lib/fakebank_tbr_get_root.c new file mode 100644 index 000000000..6e518d661 --- /dev/null +++ b/src/bank-lib/fakebank_tbr_get_root.c @@ -0,0 +1,50 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbr_get_root.c + * @brief return the main "/" page for the Taler Bank Revenue API + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +MHD_RESULT +TALER_FAKEBANK_tbr_get_root (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection) +{ + MHD_RESULT ret; + struct MHD_Response *resp; +#define HELLOMSG "Hello, Fakebank (Bank Revenue API here)!" + + (void) h; + 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; +} diff --git a/src/bank-lib/fakebank_tbr_get_root.h b/src/bank-lib/fakebank_tbr_get_root.h new file mode 100644 index 000000000..eda8060bf --- /dev/null +++ b/src/bank-lib/fakebank_tbr_get_root.h @@ -0,0 +1,45 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_tbr_get_root.h + * @brief return the main "/" page for the Taler Bank Revenue API + * @author Christian Grothoff + */ +#ifndef FAKEBANK_TBR_GET_ROOT_H +#define FAKEBANK_TBR_GET_ROOT_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * Handle incoming HTTP request for "/" (home page). + * + * @param h the fakebank handle + * @param connection the connection + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_tbr_get_root (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection); + +#endif diff --git a/src/bank-lib/fakebank_twg.c b/src/bank-lib/fakebank_twg.c new file mode 100644 index 000000000..f362dd4a8 --- /dev/null +++ b/src/bank-lib/fakebank_twg.c @@ -0,0 +1,106 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_twg.c + * @brief main entry point for the Taler Wire Gateway API + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_twg.h" +#include "fakebank_twg_admin_add_incoming.h" +#include "fakebank_twg_get_root.h" +#include "fakebank_twg_history.h" +#include "fakebank_twg_transfer.h" + + +MHD_RESULT +TALER_FAKEBANK_twg_main_ ( + 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 == strcasecmp (method, + MHD_HTTP_METHOD_GET)) + { + if ( (0 == strcmp (url, + "/history/incoming")) && + (NULL != account) ) + return TALER_FAKEBANK_twg_get_credit_history_ (h, + connection, + account, + con_cls); + if ( (0 == strcmp (url, + "/history/outgoing")) && + (NULL != account) ) + return TALER_FAKEBANK_twg_get_debit_history_ (h, + connection, + account, + con_cls); + if (0 == strcmp (url, + "/")) + return TALER_FAKEBANK_twg_get_root_ (h, + connection); + } + else if (0 == strcasecmp (method, + MHD_HTTP_METHOD_POST)) + { + if ( (0 == strcmp (url, + "/admin/add-incoming")) && + (NULL != account) ) + return TALER_FAKEBANK_twg_admin_add_incoming_ (h, + connection, + account, + upload_data, + upload_data_size, + con_cls); + if ( (0 == strcmp (url, + "/transfer")) && + (NULL != account) ) + return TALER_FAKEBANK_handle_transfer_ (h, + connection, + account, + upload_data, + upload_data_size, + con_cls); + } + /* Unexpected URL path, just close the connection. */ + TALER_LOG_ERROR ("Breaking URL: %s %s\n", + method, + url); + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + url); +} diff --git a/src/bank-lib/fakebank_twg.h b/src/bank-lib/fakebank_twg.h new file mode 100644 index 000000000..de808a21f --- /dev/null +++ b/src/bank-lib/fakebank_twg.h @@ -0,0 +1,56 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_twg.h + * @brief main entry point for the Taler Wire Gateway API + * @author Christian Grothoff + */ +#ifndef FAKEBANK_TWG_H +#define FAKEBANK_TWG_H + +#include "taler_fakebank_lib.h" +#include + + +/** + * Handle incoming HTTP request to the Taler Wire Gateway + * API. + * + * @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 + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_twg_main_ ( + 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); + +#endif diff --git a/src/bank-lib/fakebank_twg_admin_add_incoming.c b/src/bank-lib/fakebank_twg_admin_add_incoming.c new file mode 100644 index 000000000..2db4f1fe5 --- /dev/null +++ b/src/bank-lib/fakebank_twg_admin_add_incoming.c @@ -0,0 +1,160 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_twg_admin_add_incoming.c + * @brief library that fakes being a Taler bank for testcases + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_make_admin_transfer.h" +#include "fakebank_twg_admin_add_incoming.h" + +MHD_RESULT +TALER_FAKEBANK_twg_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) +{ + struct ConnectionContext *cc = *con_cls; + enum GNUNET_JSON_PostResult pr; + json_t *json; + uint64_t row_id; + struct GNUNET_TIME_Timestamp timestamp; + + if (NULL == cc) + { + cc = GNUNET_new (struct ConnectionContext); + cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + *con_cls = cc; + } + pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + 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; + enum GNUNET_GenericReturnValue ret; + 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", + h->currency, + &amount), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + (ret = TALER_MHD_parse_json_data (connection, + json, + spec))) + { + GNUNET_break_op (0); + json_decref (json); + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + } + if (0 != strcasecmp (amount.currency, + h->currency)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Currency `%s' does not match our configuration\n", + amount.currency); + json_decref (json); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_CONFLICT, + TALER_EC_GENERIC_CURRENCY_MISMATCH, + NULL); + } + debit = TALER_xtalerbank_account_from_payto (debit_account); + if (NULL == debit) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PAYTO_URI_MALFORMED, + 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)); + ret = TALER_FAKEBANK_make_admin_transfer_ (h, + debit, + account, + &amount, + &reserve_pub, + &row_id, + ×tamp); + GNUNET_free (debit); + if (GNUNET_OK != ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Reserve public key not unique\n"); + json_decref (json); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_CONFLICT, + TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT, + NULL); + } + } + json_decref (json); + + /* Finally build response object */ + return TALER_MHD_REPLY_JSON_PACK (connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_uint64 ("row_id", + row_id), + GNUNET_JSON_pack_timestamp ("timestamp", + timestamp)); +} diff --git a/src/bank-lib/fakebank_twg_admin_add_incoming.h b/src/bank-lib/fakebank_twg_admin_add_incoming.h new file mode 100644 index 000000000..220a10fc8 --- /dev/null +++ b/src/bank-lib/fakebank_twg_admin_add_incoming.h @@ -0,0 +1,52 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_twg_admin_add_incoming.h + * @brief library that fakes being a Taler bank for testcases + * @author Christian Grothoff + */ +#ifndef FAKEBANK_TWG_ADMIN_ADD_INCOMING_H +#define FAKEBANK_TWG_ADMIN_ADD_INCOMING_H +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + +/** + * 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 ConnectionContext *`) + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_twg_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); + +#endif diff --git a/src/bank-lib/fakebank_twg_get_root.c b/src/bank-lib/fakebank_twg_get_root.c new file mode 100644 index 000000000..09589890e --- /dev/null +++ b/src/bank-lib/fakebank_twg_get_root.c @@ -0,0 +1,58 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_twg_get_root.c + * @brief return the "/" page for the taler wire gateway + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * Handle incoming HTTP request for "/" (home page). + * + * @param h the fakebank handle + * @param connection the connection + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_twg_get_root_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection) +{ + MHD_RESULT ret; + struct MHD_Response *resp; +#define HELLOMSG "Hello, Fakebank (Taler Wire Gateway)!" + + (void) h; + 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; +} diff --git a/src/bank-lib/fakebank_twg_get_root.h b/src/bank-lib/fakebank_twg_get_root.h new file mode 100644 index 000000000..8bbcf4192 --- /dev/null +++ b/src/bank-lib/fakebank_twg_get_root.h @@ -0,0 +1,46 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_twg_get_root.h + * @brief return the "/" page for the taler wire gateway + * @author Christian Grothoff + */ +#ifndef FAKEBANK_TWG_GET_ROOT_H +#define FAKEBANK_TWG_GET_ROOT_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * Handle incoming HTTP request for "/" (home page). + * + * @param h the fakebank handle + * @param connection the connection + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_twg_get_root_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection); + +#endif diff --git a/src/bank-lib/fakebank_twg_history.c b/src/bank-lib/fakebank_twg_history.c new file mode 100644 index 000000000..062285078 --- /dev/null +++ b/src/bank-lib/fakebank_twg_history.c @@ -0,0 +1,537 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_twg_history.c + * @brief routines to return account histories for the Taler Wire Gateway API + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_lookup.h" +#include "fakebank_common_lp.h" +#include "fakebank_common_parser.h" + +/** + * Function called to clean up a history context. + * + * @param cls a `struct HistoryContext *` + */ +static void +history_cleanup (void *cls) +{ + struct HistoryContext *hc = cls; + + GNUNET_free (hc->payto_uri); + json_decref (hc->history); + GNUNET_free (hc); +} + + +MHD_RESULT +TALER_FAKEBANK_twg_get_debit_history_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account, + void **con_cls) +{ + struct ConnectionContext *cc = *con_cls; + struct HistoryContext *hc; + struct Transaction *pos; + enum GNUNET_GenericReturnValue ret; + + if (NULL == cc) + { + cc = GNUNET_new (struct ConnectionContext); + cc->ctx_cleaner = &history_cleanup; + *con_cls = cc; + hc = GNUNET_new (struct HistoryContext); + cc->ctx = hc; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling /history/outgoing connection %p\n", + connection); + if (GNUNET_OK != + (ret = TALER_FAKEBANK_common_parse_history_args (h, + connection, + &hc->ha))) + { + GNUNET_break_op (0); + return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; + } + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + hc->acc = TALER_FAKEBANK_lookup_account_ (h, + account, + NULL); + if (NULL == hc->acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_UNKNOWN_ACCOUNT, + account); + } + GNUNET_asprintf (&hc->payto_uri, + "payto://x-taler-bank/localhost/%s?receiver-name=%s", + account, + hc->acc->receiver_name); + /* New invariant: */ + GNUNET_assert (0 == strcmp (hc->payto_uri, + hc->acc->payto_uri)); + hc->history = json_array (); + if (NULL == hc->history) + { + GNUNET_break (0); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return MHD_NO; + } + hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout); + } + else + { + hc = cc->ctx; + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + } + + if (! hc->ha.have_start) + { + pos = (0 > hc->ha.delta) + ? hc->acc->out_tail + : hc->acc->out_head; + } + else + { + struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit]; + bool overflow; + uint64_t dir; + bool skip = true; + + dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1; + overflow = (t->row_id != hc->ha.start_idx); + /* If account does not match, linear scan for + first matching account. */ + while ( (! overflow) && + (NULL != t) && + (t->debit_account != hc->acc) ) + { + skip = false; + t = h->transactions[(t->row_id + dir) % h->ram_limit]; + if ( (NULL != t) && + (t->row_id == hc->ha.start_idx) ) + overflow = true; /* full circle, give up! */ + } + if ( (NULL == t) || + overflow) + { + /* FIXME: these conditions are unclear to me. */ + if ( (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout)) && + (0 < hc->ha.delta)) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + if (overflow) + { + return TALER_MHD_reply_with_ec ( + connection, + TALER_EC_BANK_ANCIENT_TRANSACTION_GONE, + NULL); + } + goto finish; + } + if (h->in_shutdown) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + goto finish; + } + TALER_FAKEBANK_start_lp_ (h, + connection, + hc->acc, + GNUNET_TIME_absolute_get_remaining ( + hc->timeout), + LP_DEBIT, + NULL); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return MHD_YES; + } + if (t->debit_account != hc->acc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid start specified, transaction %llu not with account %s!\n", + (unsigned long long) hc->ha.start_idx, + account); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return MHD_NO; + } + if (skip) + { + /* range is exclusive, skip the matching entry */ + if (0 > hc->ha.delta) + pos = t->prev_out; + else + pos = t->next_out; + } + else + { + pos = t; + } + } + if (NULL != pos) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Returning %lld debit transactions starting (inclusive) from %llu\n", + (long long) hc->ha.delta, + (unsigned long long) pos->row_id); + while ( (0 != hc->ha.delta) && + (NULL != pos) ) + { + json_t *trans; + char *credit_payto; + + if (T_DEBIT != pos->type) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unexpected CREDIT transaction #%llu for account `%s'\n", + (unsigned long long) pos->row_id, + account); + if (0 > hc->ha.delta) + pos = pos->prev_in; + if (0 < hc->ha.delta) + pos = pos->next_in; + continue; + } + GNUNET_asprintf (&credit_payto, + "payto://x-taler-bank/localhost/%s?receiver-name=%s", + pos->credit_account->account_name, + pos->credit_account->receiver_name); + + trans = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("row_id", + pos->row_id), + GNUNET_JSON_pack_timestamp ("date", + pos->date), + TALER_JSON_pack_amount ("amount", + &pos->amount), + GNUNET_JSON_pack_string ("credit_account", + credit_payto), + GNUNET_JSON_pack_string ("exchange_base_url", + pos->subject.debit.exchange_base_url), + GNUNET_JSON_pack_data_auto ("wtid", + &pos->subject.debit.wtid)); + GNUNET_assert (NULL != trans); + GNUNET_free (credit_payto); + GNUNET_assert (0 == + json_array_append_new (hc->history, + trans)); + if (hc->ha.delta > 0) + hc->ha.delta--; + else + hc->ha.delta++; + if (0 > hc->ha.delta) + pos = pos->prev_out; + if (0 < hc->ha.delta) + pos = pos->next_out; + } + if ( (0 == json_array_size (hc->history)) && + (! h->in_shutdown) && + (GNUNET_TIME_absolute_is_future (hc->timeout)) && + (0 < hc->ha.delta)) + { + TALER_FAKEBANK_start_lp_ (h, + connection, + hc->acc, + GNUNET_TIME_absolute_get_remaining (hc->timeout), + LP_DEBIT, + NULL); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return MHD_YES; + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); +finish: + if (0 == json_array_size (hc->history)) + { + GNUNET_break (h->in_shutdown || + (! GNUNET_TIME_absolute_is_future (hc->timeout))); + return TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); + } + { + json_t *h = hc->history; + + hc->history = NULL; + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ( + "debit_account", + hc->payto_uri), + GNUNET_JSON_pack_array_steal ( + "outgoing_transactions", + h)); + } +} + + +MHD_RESULT +TALER_FAKEBANK_twg_get_credit_history_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account, + void **con_cls) +{ + struct ConnectionContext *cc = *con_cls; + struct HistoryContext *hc; + const struct Transaction *pos; + enum GNUNET_GenericReturnValue ret; + + if (NULL == cc) + { + cc = GNUNET_new (struct ConnectionContext); + cc->ctx_cleaner = &history_cleanup; + *con_cls = cc; + hc = GNUNET_new (struct HistoryContext); + cc->ctx = hc; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling /history/incoming connection %p\n", + connection); + if (GNUNET_OK != + (ret = TALER_FAKEBANK_common_parse_history_args (h, + connection, + &hc->ha))) + { + GNUNET_break_op (0); + return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; + } + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + hc->acc = TALER_FAKEBANK_lookup_account_ (h, + account, + NULL); + if (NULL == hc->acc) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_BANK_UNKNOWN_ACCOUNT, + account); + } + /* FIXME: was simply: acc->payto_uri -- same!? */ + GNUNET_asprintf (&hc->payto_uri, + "payto://x-taler-bank/%s/%s?receiver-name=%s", + h->hostname, + account, + hc->acc->receiver_name); + GNUNET_assert (0 == strcmp (hc->payto_uri, + hc->acc->payto_uri)); + hc->history = json_array (); + if (NULL == hc->history) + { + GNUNET_break (0); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return MHD_NO; + } + hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout); + } + else + { + hc = cc->ctx; + GNUNET_assert (0 == + pthread_mutex_lock (&h->big_lock)); + } + + if (! hc->ha.have_start) + { + pos = (0 > hc->ha.delta) + ? hc->acc->in_tail + : hc->acc->in_head; + } + else + { + struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit]; + bool overflow; + uint64_t dir; + bool skip = true; + + overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) ); + dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1; + /* If account does not match, linear scan for + first matching account. */ + while ( (! overflow) && + (NULL != t) && + (t->credit_account != hc->acc) ) + { + skip = false; + t = h->transactions[(t->row_id + dir) % h->ram_limit]; + if ( (NULL != t) && + (t->row_id == hc->ha.start_idx) ) + overflow = true; /* full circle, give up! */ + } + if ( (NULL == t) || + overflow) + { + /* FIXME: these conditions are unclear to me. */ + if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) && + (0 < hc->ha.delta)) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + if (overflow) + return TALER_MHD_reply_with_ec ( + connection, + TALER_EC_BANK_ANCIENT_TRANSACTION_GONE, + NULL); + goto finish; + } + if (h->in_shutdown) + { + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + goto finish; + } + TALER_FAKEBANK_start_lp_ (h, + connection, + hc->acc, + GNUNET_TIME_absolute_get_remaining ( + hc->timeout), + LP_CREDIT, + NULL); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return MHD_YES; + } + if (skip) + { + /* range from application is exclusive, skip the + matching entry */ + if (0 > hc->ha.delta) + pos = t->prev_in; + else + pos = t->next_in; + } + else + { + pos = t; + } + } + if (NULL != pos) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Returning %lld credit transactions starting (inclusive) from %llu\n", + (long long) hc->ha.delta, + (unsigned long long) pos->row_id); + while ( (0 != hc->ha.delta) && + (NULL != pos) ) + { + json_t *trans; + + if (T_CREDIT != pos->type) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unexpected DEBIT transaction #%llu for account `%s'\n", + (unsigned long long) pos->row_id, + account); + if (0 > hc->ha.delta) + pos = pos->prev_in; + if (0 < hc->ha.delta) + pos = pos->next_in; + continue; + } + trans = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("row_id", + pos->row_id), + GNUNET_JSON_pack_timestamp ("date", + pos->date), + TALER_JSON_pack_amount ("amount", + &pos->amount), + GNUNET_JSON_pack_string ("debit_account", + pos->debit_account->payto_uri), + GNUNET_JSON_pack_data_auto ("reserve_pub", + &pos->subject.credit.reserve_pub)); + GNUNET_assert (NULL != trans); + GNUNET_assert (0 == + json_array_append_new (hc->history, + trans)); + if (hc->ha.delta > 0) + hc->ha.delta--; + else + hc->ha.delta++; + if (0 > hc->ha.delta) + pos = pos->prev_in; + if (0 < hc->ha.delta) + pos = pos->next_in; + } + if ( (0 == json_array_size (hc->history)) && + (! h->in_shutdown) && + (GNUNET_TIME_absolute_is_future (hc->timeout)) && + (0 < hc->ha.delta)) + { + TALER_FAKEBANK_start_lp_ (h, + connection, + hc->acc, + GNUNET_TIME_absolute_get_remaining (hc->timeout), + LP_CREDIT, + NULL); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); + return MHD_YES; + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->big_lock)); +finish: + if (0 == json_array_size (hc->history)) + { + GNUNET_break (h->in_shutdown || + (! GNUNET_TIME_absolute_is_future (hc->timeout))); + return TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); + } + { + json_t *h = hc->history; + + hc->history = NULL; + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ( + "credit_account", + hc->payto_uri), + GNUNET_JSON_pack_array_steal ( + "incoming_transactions", + h)); + } +} diff --git a/src/bank-lib/fakebank_twg_history.h b/src/bank-lib/fakebank_twg_history.h new file mode 100644 index 000000000..c49678aef --- /dev/null +++ b/src/bank-lib/fakebank_twg_history.h @@ -0,0 +1,67 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_twg_history.c + * @brief routines to return account histories for the Taler Wire Gateway API + * @author Christian Grothoff + */ +#ifndef FAKEBANK_TWG_HISTORY_H +#define FAKEBANK_TWG_HISTORY_H + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * Handle incoming HTTP request for /history/outgoing + * + * @param h the fakebank handle + * @param connection the connection + * @param account which account the request is about + * @param con_cls closure for request + */ +MHD_RESULT +TALER_FAKEBANK_twg_get_debit_history_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account, + void **con_cls); + + +/** + * Handle incoming HTTP request for /history/incoming + * + * @param h the fakebank handle + * @param connection the connection + * @param account which account the request is about + * @param con_cls closure for request (NULL or &special_ptr) + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_twg_get_credit_history_ ( + struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + const char *account, + void **con_cls); + + +#endif diff --git a/src/bank-lib/fakebank_twg_transfer.c b/src/bank-lib/fakebank_twg_transfer.c new file mode 100644 index 000000000..fef314a52 --- /dev/null +++ b/src/bank-lib/fakebank_twg_transfer.c @@ -0,0 +1,178 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_twg_transfer.c + * @brief implementation of the Taler Wire Gateway "/transfer" endpoint + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" +#include "fakebank_common_transact.h" +#include "fakebank_twg_transfer.h" + + +/** + * 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 ConnectionContext *`) + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_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) +{ + struct ConnectionContext *cc = *con_cls; + enum GNUNET_JSON_PostResult pr; + json_t *json; + uint64_t row_id; + struct GNUNET_TIME_Timestamp ts; + + if (NULL == cc) + { + cc = GNUNET_new (struct ConnectionContext); + cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup; + *con_cls = cc; + } + pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, + connection, + &cc->ctx, + 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; + enum GNUNET_GenericReturnValue ret; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("request_uid", + &uuid), + TALER_JSON_spec_amount ("amount", + h->currency, + &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 != + (ret = TALER_MHD_parse_json_data (connection, + json, + spec))) + { + GNUNET_break_op (0); + json_decref (json); + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + } + { + enum GNUNET_GenericReturnValue ret; + + credit = TALER_xtalerbank_account_from_payto (credit_account); + if (NULL == credit) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PAYTO_URI_MALFORMED, + credit_account); + } + ret = TALER_FAKEBANK_make_transfer_ (h, + account, + credit, + &amount, + &wtid, + base_url, + &uuid, + &row_id, + &ts); + if (GNUNET_OK != ret) + { + MHD_RESULT res; + char *uids; + + GNUNET_break (0); + uids = GNUNET_STRINGS_data_to_string_alloc (&uuid, + sizeof (uuid)); + json_decref (json); + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED, + uids); + GNUNET_free (uids); + return res; + } + 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, + GNUNET_JSON_pack_uint64 ("row_id", + row_id), + GNUNET_JSON_pack_timestamp ("timestamp", + ts)); +} diff --git a/src/bank-lib/fakebank_twg_transfer.h b/src/bank-lib/fakebank_twg_transfer.h new file mode 100644 index 000000000..2019565bf --- /dev/null +++ b/src/bank-lib/fakebank_twg_transfer.h @@ -0,0 +1,55 @@ +/* + This file is part of TALER + (C) 2016-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/fakebank_twg_transfer.h + * @brief implementation of the Taler Wire Gateway "/transfer" endpoint + * @author Christian Grothoff + */ +#ifndef FAKEBANK_TWG_TRANSFER_H +#define FAKEBANK_TWG_TRANSFER_H + + +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_mhd_lib.h" +#include +#include "fakebank.h" + + +/** + * 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 ConnectionContext *`) + * @return MHD result code + */ +MHD_RESULT +TALER_FAKEBANK_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); + +#endif -- cgit v1.2.3