/*
This file is part of TALER
(C) 2016-2022 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.c
* @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 ("debit_account",
hc->payto_uri), // FIXME #7275: inefficient to return this here always!
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_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/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;
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 ("credit_account",
hc->payto_uri), // FIXME #7275: inefficient to repeat this always here!
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_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 ("paytoUri", /* FIXME: #7300 */
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;
}
/**
* 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
*/
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 ("publicAccounts", /* FIXME: #7300 */
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.
*
* @param cls a `struct TALER_FAKEBANK_Handle`
* @param connection the connection
* @param url the requested url
* @param method the method (POST, GET, ...)
* @param version HTTP version (ignored)
* @param upload_data request data
* @param upload_data_size size of @a upload_data in bytes
* @param con_cls closure for request
* @return MHD result code
*/
static MHD_RESULT
handle_mhd_request (void *cls,
struct MHD_Connection *connection,
const char *url,
const char *method,
const char *version,
const char *upload_data,
size_t *upload_data_size,
void **con_cls)
{
struct TALER_FAKEBANK_Handle *h = cls;
char *account = NULL;
char *end;
MHD_RESULT ret;
(void) version;
if (0 == strncmp (url,
"/taler-bank-integration/",
strlen ("/taler-bank-integration/")))
{
url += strlen ("/taler-bank-integration");
return handle_bank_integration (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);
}
if (0 == strncmp (url,
"/taler-wire-gateway/",
strlen ("/taler-wire-gateway/")))
url += strlen ("/taler-wire-gateway");
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Handling request for `%s'\n",
url);
if ( (strlen (url) > 1) &&
(NULL != (end = strchr (url + 1, '/'))) )
{
account = GNUNET_strndup (url + 1,
end - url - 1);
url = end;
}
ret = serve (h,
connection,
account,
url,
method,
upload_data,
upload_data_size,
con_cls);
GNUNET_free (account);
return ret;
}
#if EPOLL_SUPPORT
/**
* Schedule MHD. This function should be called initially when an
* MHD is first getting its client socket, and will then automatically
* always be called later whenever there is work to be done.
*
* @param h fakebank handle to schedule MHD for
*/
static void
schedule_httpd (struct TALER_FAKEBANK_Handle *h)
{
int haveto;
MHD_UNSIGNED_LONG_LONG timeout;
struct GNUNET_TIME_Relative tv;
GNUNET_assert (-1 != h->mhd_fd);
haveto = MHD_get_timeout (h->mhd_bank,
&timeout);
if (MHD_YES == haveto)
tv.rel_value_us = (uint64_t) timeout * 1000LL;
else
tv = GNUNET_TIME_UNIT_FOREVER_REL;
if (NULL != h->mhd_task)
GNUNET_SCHEDULER_cancel (h->mhd_task);
h->mhd_task =
GNUNET_SCHEDULER_add_read_net (tv,
h->mhd_rfd,
&run_mhd,
h);
}
#else
/**
* Schedule MHD. This function should be called initially when an
* MHD is first getting its client socket, and will then automatically
* always be called later whenever there is work to be done.
*
* @param h fakebank handle to schedule MHD for
*/
static void
schedule_httpd (struct TALER_FAKEBANK_Handle *h)
{
fd_set rs;
fd_set ws;
fd_set es;
struct GNUNET_NETWORK_FDSet *wrs;
struct GNUNET_NETWORK_FDSet *wws;
int max;
int haveto;
MHD_UNSIGNED_LONG_LONG timeout;
struct GNUNET_TIME_Relative tv;
#ifdef __linux__
GNUNET_assert (-1 == h->lp_event);
#else
GNUNET_assert (-1 == h->lp_event_in);
GNUNET_assert (-1 == h->lp_event_out);
#endif
FD_ZERO (&rs);
FD_ZERO (&ws);
FD_ZERO (&es);
max = -1;
if (MHD_YES != MHD_get_fdset (h->mhd_bank,
&rs,
&ws,
&es,
&max))
{
GNUNET_assert (0);
return;
}
haveto = MHD_get_timeout (h->mhd_bank,
&timeout);
if (MHD_YES == haveto)
tv.rel_value_us = (uint64_t) timeout * 1000LL;
else
tv = GNUNET_TIME_UNIT_FOREVER_REL;
if (-1 != max)
{
wrs = GNUNET_NETWORK_fdset_create ();
wws = GNUNET_NETWORK_fdset_create ();
GNUNET_NETWORK_fdset_copy_native (wrs,
&rs,
max + 1);
GNUNET_NETWORK_fdset_copy_native (wws,
&ws,
max + 1);
}
else
{
wrs = NULL;
wws = NULL;
}
if (NULL != h->mhd_task)
GNUNET_SCHEDULER_cancel (h->mhd_task);
h->mhd_task =
GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
tv,
wrs,
wws,
&run_mhd,
h);
if (NULL != wrs)
GNUNET_NETWORK_fdset_destroy (wrs);
if (NULL != wws)
GNUNET_NETWORK_fdset_destroy (wws);
}
#endif
/**
* Task run whenever HTTP server operations are pending.
*
* @param cls the `struct TALER_FAKEBANK_Handle`
*/
static void
run_mhd (void *cls)
{
struct TALER_FAKEBANK_Handle *h = cls;
h->mhd_task = NULL;
h->mhd_again = true;
while (h->mhd_again)
{
h->mhd_again = false;
MHD_run (h->mhd_bank);
}
#ifdef __linux__
GNUNET_assert (-1 == h->lp_event);
#else
GNUNET_assert (-1 == h->lp_event_in);
GNUNET_assert (-1 == h->lp_event_out);
#endif
schedule_httpd (h);
}
struct TALER_FAKEBANK_Handle *
TALER_FAKEBANK_start (uint16_t port,
const char *currency)
{
return TALER_FAKEBANK_start2 (port,
currency,
65536, /* RAM limit */
1);
}
struct TALER_FAKEBANK_Handle *
TALER_FAKEBANK_start2 (uint16_t port,
const char *currency,
uint64_t ram_limit,
unsigned int num_threads)
{
struct TALER_Amount zero;
if (GNUNET_OK !=
TALER_amount_set_zero (currency,
&zero))
{
GNUNET_break (0);
return NULL;
}
return TALER_FAKEBANK_start3 ("localhost",
port,
NULL,
currency,
ram_limit,
num_threads,
&zero);
}
struct TALER_FAKEBANK_Handle *
TALER_FAKEBANK_start3 (const char *hostname,
uint16_t port,
const char *exchange_url,
const char *currency,
uint64_t ram_limit,
unsigned int num_threads,
const struct TALER_Amount *signup_bonus)
{
struct TALER_FAKEBANK_Handle *h;
if (SIZE_MAX / sizeof (struct Transaction *) < ram_limit)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"This CPU architecture does not support keeping %llu transactions in RAM\n",
(unsigned long long) ram_limit);
return NULL;
}
GNUNET_assert (strlen (currency) < TALER_CURRENCY_LEN);
if (0 != strcmp (signup_bonus->currency,
currency))
{
GNUNET_break (0);
return NULL;
}
h = GNUNET_new (struct TALER_FAKEBANK_Handle);
h->signup_bonus = *signup_bonus;
if (NULL != exchange_url)
h->exchange_url = GNUNET_strdup (exchange_url);
#ifdef __linux__
h->lp_event = -1;
#else
h->lp_event_in = -1;
h->lp_event_out = -1;
#endif
#if EPOLL_SUPPORT
h->mhd_fd = -1;
#endif
h->port = port;
h->ram_limit = ram_limit;
h->serial_counter = 0;
GNUNET_assert (0 ==
pthread_mutex_init (&h->accounts_lock,
NULL));
GNUNET_assert (0 ==
pthread_mutex_init (&h->rpubs_lock,
NULL));
GNUNET_assert (0 ==
pthread_mutex_init (&h->uuid_map_lock,
NULL));
GNUNET_assert (0 ==
pthread_mutex_init (&h->big_lock,
NULL));
h->transactions
= GNUNET_malloc_large (sizeof (struct Transaction *)
* ram_limit);
if (NULL == h->transactions)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"malloc");
TALER_FAKEBANK_stop (h);
return NULL;
}
h->accounts = GNUNET_CONTAINER_multihashmap_create (128,
GNUNET_NO);
h->uuid_map = GNUNET_CONTAINER_multihashmap_create (ram_limit * 4 / 3,
GNUNET_YES);
if (NULL == h->uuid_map)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"malloc");
TALER_FAKEBANK_stop (h);
return NULL;
}
h->rpubs = GNUNET_CONTAINER_multipeermap_create (ram_limit * 4 / 3,
GNUNET_NO);
if (NULL == h->rpubs)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"malloc");
TALER_FAKEBANK_stop (h);
return NULL;
}
h->lp_heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
h->currency = GNUNET_strdup (currency);
h->hostname = GNUNET_strdup (hostname);
GNUNET_asprintf (&h->my_baseurl,
"http://%s:%u/",
h->hostname,
(unsigned int) port);
if (0 == num_threads)
{
h->mhd_bank = MHD_start_daemon (MHD_USE_DEBUG
#if EPOLL_SUPPORT
| 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);
if (NULL == h->mhd_bank)
{
TALER_FAKEBANK_stop (h);
return NULL;
}
#if EPOLL_SUPPORT
h->mhd_fd = MHD_get_daemon_info (h->mhd_bank,
MHD_DAEMON_INFO_EPOLL_FD)->epoll_fd;
h->mhd_rfd = GNUNET_NETWORK_socket_box_native (h->mhd_fd);
#endif
schedule_httpd (h);
}
else
{
#ifdef __linux__
h->lp_event = eventfd (0,
EFD_CLOEXEC);
if (-1 == h->lp_event)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"eventfd");
TALER_FAKEBANK_stop (h);
return NULL;
}
#else
{
int pipefd[2];
if (0 != pipe (pipefd))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"pipe");
TALER_FAKEBANK_stop (h);
return NULL;
}
h->lp_event_out = pipefd[0];
h->lp_event_in = pipefd[1];
}
#endif
if (0 !=
pthread_create (&h->lp_thread,
NULL,
&lp_expiration_thread,
h))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"pthread_create");
#ifdef __linux__
GNUNET_break (0 == close (h->lp_event));
h->lp_event = -1;
#else
GNUNET_break (0 == close (h->lp_event_in));
GNUNET_break (0 == close (h->lp_event_out));
h->lp_event_in = -1;
h->lp_event_out = -1;
#endif
TALER_FAKEBANK_stop (h);
return NULL;
}
h->mhd_bank = MHD_start_daemon (MHD_USE_DEBUG
| MHD_USE_AUTO_INTERNAL_THREAD
| MHD_ALLOW_SUSPEND_RESUME
| MHD_USE_TURBO
| MHD_USE_TCP_FASTOPEN
| MHD_USE_DUAL_STACK,
port,
NULL, NULL,
&handle_mhd_request, h,
MHD_OPTION_NOTIFY_COMPLETED,
&handle_mhd_completion_callback, h,
MHD_OPTION_LISTEN_BACKLOG_SIZE,
(unsigned int) 1024,
MHD_OPTION_CONNECTION_LIMIT,
(unsigned int) 65536,
MHD_OPTION_THREAD_POOL_SIZE,
num_threads,
MHD_OPTION_END);
if (NULL == h->mhd_bank)
{
GNUNET_break (0);
TALER_FAKEBANK_stop (h);
return NULL;
}
}
return h;
}
/* end of fakebank.c */