/*
  This file is part of TALER
  (C) 2016-2021 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 
#include 
#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
};
/**
 * 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;
  /**
   * 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;
/**
 * 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;
  /**
   * 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_Absolute 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;
};
/**
 * 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;
  /**
   * 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;
  /**
   * 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;
  /**
   * BaseURL of the fakebank.
   */
  char *my_baseurl;
  /**
   * Our port number.
   */
  uint16_t port;
  /**
   * Event FD to signal @a lp_thread a change in
   * @a lp_heap.
   */
  int lp_event;
  /**
   * Set to true once we are shutting down.
   */
  bool in_shutdown;
#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
};
/**
 * Special address "con_cls" can point to to indicate that the handler has
 * been called more than once already (was previously suspended).
 */
static int special_ptr;
/**
 * Task run whenever HTTP server operations are pending.
 *
 * @param cls the `struct TALER_FAKEBANK_Handle`
 */
static void
run_mhd (void *cls);
/**
 * 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);
  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));
      GNUNET_assert (0 ==
                     pthread_mutex_lock (&h->big_lock));
      lp_trigger (lp,
                  h);
      GNUNET_assert (0 ==
                     pthread_mutex_unlock (&h->big_lock));
      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 = {
        .fd = h->lp_event,
        .events = POLLIN
      };
      int ret;
      ret = poll (&p,
                  1,
                  timeout_ms);
      if (-1 == ret)
      {
        GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
                             "poll");
      }
      else if (1 == ret)
      {
        /* clear event */
        uint64_t ev;
        ssize_t iret;
        iret = read (h->lp_event,
                     &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
 * @return account handle (never NULL)
 */
static struct Account *
lookup_account (struct TALER_FAKEBANK_Handle *h,
                const char *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)
  {
    account = GNUNET_new (struct Account);
    account->account_name = GNUNET_strdup (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;
    }
  }
}
int
TALER_FAKEBANK_check_debit (struct TALER_FAKEBANK_Handle *h,
                            const struct TALER_Amount *want_amount,
                            const char *want_debit,
                            const char *want_credit,
                            const char *exchange_base_url,
                            struct TALER_WireTransferIdentifierRawP *wtid)
{
  struct Account *debit_account;
  struct Account *credit_account;
  GNUNET_assert (0 ==
                 strcasecmp (want_amount->currency,
                             h->currency));
  debit_account = lookup_account (h,
                                  want_debit);
  credit_account = lookup_account (h,
                                   want_credit);
  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;
}
int
TALER_FAKEBANK_check_credit (struct TALER_FAKEBANK_Handle *h,
                             const struct TALER_Amount *want_amount,
                             const char *want_debit,
                             const char *want_credit,
                             const struct TALER_ReservePublicKeyP *reserve_pub)
{
  struct Account *debit_account;
  struct Account *credit_account;
  GNUNET_assert (0 == strcasecmp (want_amount->currency,
                                  h->currency));
  debit_account = lookup_account (h,
                                  want_debit);
  credit_account = lookup_account (h,
                                   want_credit);
  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);
  }
  {
    struct LongPoller *nxt;
    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));
  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);
}
/**
 * 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 int
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_Absolute *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_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);
  credit_acc = lookup_account (h,
                               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_absolute_get ();
  (void) GNUNET_TIME_round_abs (&t->date);
  *timestamp = t->date;
  t->type = T_DEBIT;
  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;
  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_Absolute *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);
  credit_acc = lookup_account (h,
                               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_absolute_get ();
  (void) GNUNET_TIME_round_abs (&t->date);
  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);
  return GNUNET_OK;
}
int
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;
}
static int
free_account (void *cls,
              const struct GNUNET_HashCode *key,
              void *val)
{
  struct Account *account = val;
  GNUNET_assert (NULL == account->lp_head);
  GNUNET_free (account->account_name);
  GNUNET_free (account);
  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
  if (-1 != h->lp_event)
  {
    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_break (sizeof (val) ==
                  write (h->lp_event,
                         &val,
                         sizeof (val)));
    GNUNET_assert (0 ==
                   pthread_mutex_unlock (&h->big_lock));
    GNUNET_break (0 ==
                  pthread_join (h->lp_thread,
                                &ret));
    GNUNET_break (NULL == ret);
    GNUNET_break (0 == close (h->lp_event));
    h->lp_event = -1;
  }
  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);
  }
  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);
}
/**
 * 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; */
  (void) cls;
  (void) connection;
  (void) toe;
  if (NULL == *con_cls)
    return;
  if (&special_ptr == *con_cls)
    return;
  GNUNET_JSON_post_parser_cleanup (*con_cls);
  *con_cls = NULL;
}
/**
 * Handle incoming HTTP request for /admin/add/incoming.
 *
 * @param h the fakebank handle
 * @param connection the connection
 * @param account account into which to deposit the funds (credit)
 * @param upload_data request data
 * @param upload_data_size size of @a upload_data in bytes
 * @param con_cls closure for request (a `struct Buffer *`)
 * @return MHD result code
 */
static 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)
{
  enum GNUNET_JSON_PostResult pr;
  json_t *json;
  uint64_t row_id;
  struct GNUNET_TIME_Absolute timestamp;
  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
                                connection,
                                con_cls,
                                upload_data,
                                upload_data_size,
                                &json);
  switch (pr)
  {
  case GNUNET_JSON_PR_OUT_OF_MEMORY:
    GNUNET_break (0);
    return MHD_NO;
  case GNUNET_JSON_PR_CONTINUE:
    return MHD_YES;
  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
    GNUNET_break (0);
    return MHD_NO;
  case GNUNET_JSON_PR_JSON_INVALID:
    GNUNET_break (0);
    return MHD_NO;
  case GNUNET_JSON_PR_SUCCESS:
    break;
  }
  {
    const char *debit_account;
    struct TALER_Amount amount;
    struct TALER_ReservePublicKeyP reserve_pub;
    char *debit;
    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);
      return TALER_MHD_reply_with_error (
        connection,
        MHD_HTTP_CONFLICT,
        TALER_EC_GENERIC_CURRENCY_MISMATCH,
        NULL);
    }
    debit = TALER_xtalerbank_account_from_payto (debit_account);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Receiving incoming wire transfer: %s->%s, subject: %s, amount: %s\n",
                debit,
                account,
                TALER_B2S (&reserve_pub),
                TALER_amount2s (&amount));
    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");
      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_time_abs ("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 Buffer *`)
 * @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)
{
  enum GNUNET_JSON_PostResult pr;
  json_t *json;
  uint64_t row_id;
  struct GNUNET_TIME_Absolute ts;
  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
                                connection,
                                con_cls,
                                upload_data,
                                upload_data_size,
                                &json);
  switch (pr)
  {
  case GNUNET_JSON_PR_OUT_OF_MEMORY:
    GNUNET_break (0);
    return MHD_NO;
  case GNUNET_JSON_PR_CONTINUE:
    return MHD_YES;
  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
    GNUNET_break (0);
    return MHD_NO;
  case GNUNET_JSON_PR_JSON_INVALID:
    GNUNET_break (0);
    return MHD_NO;
  case GNUNET_JSON_PR_SUCCESS:
    break;
  }
  {
    struct GNUNET_HashCode uuid;
    struct TALER_WireTransferIdentifierRawP wtid;
    const char *credit_account;
    char *credit;
    const char *base_url;
    struct TALER_Amount amount;
    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;
    }
    {
      int ret;
      credit = TALER_xtalerbank_account_from_payto (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_time_abs ("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;
}
/**
 * This is the "base" structure for both the /history and the
 * /history-range API calls.
 */
struct HistoryArgs
{
  /**
   * Bank account number of the requesting client.
   */
  uint64_t account_number;
  /**
   * Index of the starting transaction.
   */
  uint64_t start_idx;
  /**
   * Requested number of results and order
   * (positive: ascending, negative: descending)
   */
  int64_t delta;
  /**
   * Timeout for long polling.
   */
  struct GNUNET_TIME_Relative lp_timeout;
  /**
   * true if starting point was given.
   */
  bool have_start;
};
/**
 * 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));
  if (-1 != h->lp_event)
  {
    uint64_t num = 1;
    GNUNET_break (sizeof (num) ==
                  write (h->lp_event,
                         &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
 */
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)
{
  struct LongPoller *lp;
  bool toc;
  lp = GNUNET_new (struct LongPoller);
  lp->account = acc;
  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 (NULL or &special_ptr)
 */
static MHD_RESULT
handle_debit_history (struct TALER_FAKEBANK_Handle *h,
                      struct MHD_Connection *connection,
                      const char *account,
                      void **con_cls)
{
  struct HistoryArgs ha;
  struct Account *acc;
  struct Transaction *pos;
  json_t *history;
  char *debit_payto;
  enum GNUNET_GenericReturnValue ret;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Handling /history/outgoing connection %p\n",
              connection);
  if (GNUNET_OK !=
      (ret = parse_history_common_args (h,
                                        connection,
                                        &ha)))
  {
    return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
  }
  if (&special_ptr == *con_cls)
    ha.lp_timeout = GNUNET_TIME_UNIT_ZERO;
  acc = lookup_account (h,
                        account);
  GNUNET_asprintf (&debit_payto,
                   "payto://x-taler-bank/localhost/%s",
                   account);
  history = json_array ();
  if (NULL == history)
  {
    GNUNET_break (0);
    GNUNET_free (debit_payto);
    return MHD_NO;
  }
  GNUNET_assert (0 ==
                 pthread_mutex_lock (&h->big_lock));
  if (! ha.have_start)
  {
    pos = (0 > ha.delta)
          ? acc->out_tail
          : acc->out_head;
  }
  else
  {
    struct Transaction *t = h->transactions[ha.start_idx % h->ram_limit];
    bool overflow;
    uint64_t dir;
    bool skip = true;
    dir = (0 > ha.delta) ? (h->ram_limit - 1) : 1;
    overflow = (t->row_id != ha.start_idx);
    /* If account does not match, linear scan for
       first matching account. */
    while ( (! overflow) &&
            (NULL != t) &&
            (t->debit_account != acc) )
    {
      skip = false;
      t = h->transactions[(t->row_id + dir) % h->ram_limit];
      if ( (NULL != t) &&
           (t->row_id == ha.start_idx) )
        overflow = true; /* full circle, give up! */
    }
    if ( (NULL == t) ||
         overflow)
    {
      GNUNET_free (debit_payto);
      if (GNUNET_TIME_relative_is_zero (ha.lp_timeout) &&
          (0 < ha.delta))
      {
        GNUNET_assert (0 ==
                       pthread_mutex_unlock (&h->big_lock));
        return TALER_MHD_REPLY_JSON_PACK (
          connection,
          MHD_HTTP_OK,
          GNUNET_JSON_pack_array_steal (
            "outgoing_transactions",
            history));
      }
      *con_cls = &special_ptr;
      start_lp (h,
                connection,
                acc,
                ha.lp_timeout,
                LP_DEBIT);
      GNUNET_assert (0 ==
                     pthread_mutex_unlock (&h->big_lock));
      json_decref (history);
      return MHD_YES;
    }
    if (t->debit_account != acc)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Invalid start specified, transaction %llu not with account %s!\n",
                  (unsigned long long) ha.start_idx,
                  account);
      GNUNET_assert (0 ==
                     pthread_mutex_unlock (&h->big_lock));
      GNUNET_free (debit_payto);
      json_decref (history);
      return MHD_NO;
    }
    if (skip)
    {
      /* range is exclusive, skip the matching entry */
      if (0 > 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) ha.delta,
                (unsigned long long) pos->row_id);
  else
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "No debit transactions exist after given starting point\n");
  while ( (0 != 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 > ha.delta)
        pos = pos->prev_in;
      if (0 < ha.delta)
        pos = pos->next_in;
      continue;
    }
    GNUNET_asprintf (&credit_payto,
                     "payto://x-taler-bank/localhost/%s",
                     pos->credit_account->account_name);
    trans = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_uint64 ("row_id",
                               pos->row_id),
      GNUNET_JSON_pack_time_abs ("date",
                                 pos->date),
      TALER_JSON_pack_amount ("amount",
                              &pos->amount),
      GNUNET_JSON_pack_string ("credit_account",
                               credit_payto),
      GNUNET_JSON_pack_string ("debit_account",
                               debit_payto),          // FIXME: 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 (history,
                                          trans));
    if (ha.delta > 0)
      ha.delta--;
    else
      ha.delta++;
    if (0 > ha.delta)
      pos = pos->prev_out;
    if (0 < ha.delta)
      pos = pos->next_out;
  }
  if ( (0 == json_array_size (history)) &&
       (! GNUNET_TIME_relative_is_zero (ha.lp_timeout)) &&
       (0 < ha.delta))
  {
    *con_cls = &special_ptr;
    start_lp (h,
              connection,
              acc,
              ha.lp_timeout,
              LP_DEBIT);
    GNUNET_assert (0 ==
                   pthread_mutex_unlock (&h->big_lock));
    json_decref (history);
    return MHD_YES;
  }
  GNUNET_assert (0 ==
                 pthread_mutex_unlock (&h->big_lock));
  GNUNET_free (debit_payto);
  return TALER_MHD_REPLY_JSON_PACK (connection,
                                    MHD_HTTP_OK,
                                    GNUNET_JSON_pack_array_steal (
                                      "outgoing_transactions",
                                      history));
}
/**
 * Handle incoming HTTP request for /history/incoming
 *
 * @param h the fakebank handle
 * @param connection the connection
 * @param account which account the request is about
 * @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 HistoryArgs ha;
  struct Account *acc;
  const struct Transaction *pos;
  json_t *history;
  char *credit_payto;
  enum GNUNET_GenericReturnValue ret;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Handling /history/incoming connection %p\n",
              connection);
  if (GNUNET_OK !=
      (ret = parse_history_common_args (h,
                                        connection,
                                        &ha)))
  {
    return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
  }
  if (&special_ptr == *con_cls)
    ha.lp_timeout = GNUNET_TIME_UNIT_ZERO;
  *con_cls = &special_ptr;
  acc = lookup_account (h,
                        account);
  history = json_array ();
  GNUNET_assert (NULL != history);
  GNUNET_asprintf (&credit_payto,
                   "payto://x-taler-bank/localhost/%s",
                   account);
  GNUNET_assert (0 ==
                 pthread_mutex_lock (&h->big_lock));
  if (! ha.have_start)
  {
    pos = (0 > ha.delta)
          ? acc->in_tail
          : acc->in_head;
  }
  else
  {
    struct Transaction *t = h->transactions[ha.start_idx % h->ram_limit];
    bool overflow;
    uint64_t dir;
    bool skip = true;
    overflow = ( (NULL != t) && (t->row_id != ha.start_idx) );
    dir = (0 > 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 != acc) )
    {
      skip = false;
      t = h->transactions[(t->row_id + dir) % h->ram_limit];
      if ( (NULL != t) &&
           (t->row_id == ha.start_idx) )
        overflow = true; /* full circle, give up! */
    }
    if ( (NULL == t) ||
         overflow)
    {
      GNUNET_free (credit_payto);
      if (GNUNET_TIME_relative_is_zero (ha.lp_timeout) &&
          (0 < ha.delta))
      {
        GNUNET_assert (0 ==
                       pthread_mutex_unlock (&h->big_lock));
        return TALER_MHD_REPLY_JSON_PACK (connection,
                                          MHD_HTTP_OK,
                                          GNUNET_JSON_pack_array_steal (
                                            "incoming_transactions",
                                            history));
      }
      *con_cls = &special_ptr;
      start_lp (h,
                connection,
                acc,
                ha.lp_timeout,
                LP_CREDIT);
      GNUNET_assert (0 ==
                     pthread_mutex_unlock (&h->big_lock));
      json_decref (history);
      return MHD_YES;
    }
    if (skip)
    {
      /* range from application is exclusive, skip the
  matching entry */
      if (0 > 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) ha.delta,
                (unsigned long long) pos->row_id);
  else
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "No credit transactions exist after given starting point\n");
  while ( (0 != ha.delta) &&
          (NULL != pos) )
  {
    json_t *trans;
    char *debit_payto;
    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 > ha.delta)
        pos = pos->prev_in;
      if (0 < ha.delta)
        pos = pos->next_in;
      continue;
    }
    GNUNET_asprintf (&debit_payto,
                     "payto://x-taler-bank/localhost/%s",
                     pos->debit_account->account_name);
    trans = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_uint64 ("row_id",
                               pos->row_id),
      GNUNET_JSON_pack_time_abs ("date",
                                 pos->date),
      TALER_JSON_pack_amount ("amount",
                              &pos->amount),
      GNUNET_JSON_pack_string ("credit_account",
                               credit_payto),   // FIXME: inefficient to repeat this always here!
      GNUNET_JSON_pack_string ("debit_account",
                               debit_payto),
      GNUNET_JSON_pack_data_auto ("reserve_pub",
                                  &pos->subject.credit.reserve_pub));
    GNUNET_assert (NULL != trans);
    GNUNET_free (debit_payto);
    GNUNET_assert (0 ==
                   json_array_append_new (history,
                                          trans));
    if (ha.delta > 0)
      ha.delta--;
    else
      ha.delta++;
    if (0 > ha.delta)
      pos = pos->prev_in;
    if (0 < ha.delta)
      pos = pos->next_in;
  }
  if ( (0 == json_array_size (history)) &&
       (! GNUNET_TIME_relative_is_zero (ha.lp_timeout)) &&
       (0 < ha.delta))
  {
    *con_cls = &special_ptr;
    start_lp (h,
              connection,
              acc,
              ha.lp_timeout,
              LP_CREDIT);
    GNUNET_assert (0 ==
                   pthread_mutex_unlock (&h->big_lock));
    json_decref (history);
    return MHD_YES;
  }
  GNUNET_assert (0 ==
                 pthread_mutex_unlock (&h->big_lock));
  GNUNET_free (credit_payto);
  return TALER_MHD_REPLY_JSON_PACK (connection,
                                    MHD_HTTP_OK,
                                    GNUNET_JSON_pack_array_steal (
                                      "incoming_transactions",
                                      history));
}
/**
 * Handle incoming HTTP request.
 *
 * @param h our handle
 * @param connection the connection
 * @param url the requested url
 * @param method the method (POST, GET, ...)
 * @param account which account should process the request
 * @param upload_data request data
 * @param upload_data_size size of @a upload_data in bytes
 * @param con_cls closure
 * @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"))
      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 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;
  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;
  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;
  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;
  MHD_run (h->mhd_bank);
  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_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);
  h = GNUNET_new (struct TALER_FAKEBANK_Handle);
  h->lp_event = -1;
  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);
  GNUNET_asprintf (&h->my_baseurl,
                   "http://localhost:%u/",
                   (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
  {
    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;
    }
    if (0 !=
        pthread_create (&h->lp_thread,
                        NULL,
                        &lp_expiration_thread,
                        h))
    {
      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                           "pthread_create");
      GNUNET_break (0 == close (h->lp_event));
      h->lp_event = -1;
      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 */