/*
  This file is part of TALER
  Copyright (C) 2014-2017 GNUnet e.V. and Inria
  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 exchange/test_exchange_api.c
 * @brief testcase to test exchange's HTTP API interface
 * @author Sree Harsha Totakura 
 * @author Christian Grothoff
 */
#include "platform.h"
#include "taler_util.h"
#include "taler_signatures.h"
#include "taler_exchange_service.h"
#include "taler_json_lib.h"
#include 
#include 
#include "taler_fakebank_lib.h"
/**
 * Is the configuration file is set to include wire format 'test'?
 */
#define WIRE_TEST 1
/**
 * Is the configuration file is set to include wire format 'sepa'?
 */
#define WIRE_SEPA 1
/**
 * Main execution context for the main loop.
 */
static struct GNUNET_CURL_Context *ctx;
/**
 * Handle to access the exchange.
 */
static struct TALER_EXCHANGE_Handle *exchange;
/**
 * Context for running the CURL event loop.
 */
static struct GNUNET_CURL_RescheduleContext *rc;
/**
 * Handle to the exchange process.
 */
static struct GNUNET_OS_Process *exchanged;
/**
 * Task run on timeout.
 */
static struct GNUNET_SCHEDULER_Task *timeout_task;
/**
 * Handle to our fakebank.
 */
static struct TALER_FAKEBANK_Handle *fakebank;
/**
 * Result of the testcases, #GNUNET_OK on success
 */
static int result;
/**
 * Opcodes for the interpreter.
 */
enum OpCode
{
  /**
   * Termination code, stops the interpreter loop (with success).
   */
  OC_END = 0,
  /**
   * Add funds to a reserve by (faking) incoming wire transfer.
   */
  OC_ADMIN_ADD_INCOMING,
  /**
   * Check status of a reserve.
   */
  OC_WITHDRAW_STATUS,
  /**
   * Withdraw a coin from a reserve.
   */
  OC_WITHDRAW_SIGN,
  /**
   * Deposit a coin (pay with it).
   */
  OC_DEPOSIT,
  /**
   * Melt a (set of) coins.
   */
  OC_REFRESH_MELT,
  /**
   * Complete melting session by withdrawing melted coins.
   */
  OC_REFRESH_REVEAL,
  /**
   * Verify exchange's /refresh/link by linking original private key to
   * results from #OC_REFRESH_REVEAL step.
   */
  OC_REFRESH_LINK,
  /**
   * Verify the exchange's /wire-method.
   */
  OC_WIRE,
  /**
   * Verify exchange's /track/transfer method.
   */
  OC_WIRE_DEPOSITS,
  /**
   * Verify exchange's /track/transaction method.
   */
  OC_DEPOSIT_WTID,
  /**
   * Run the aggregator to execute deposits.
   */
  OC_RUN_AGGREGATOR,
  /**
   * Check that the fakebank has received a certain transaction.
   */
  OC_CHECK_BANK_TRANSFER,
  /**
   * Check that the fakebank has not received any other transactions.
   */
  OC_CHECK_BANK_TRANSFERS_EMPTY,
  /**
   * Refund some deposit.
   */
  OC_REFUND,
  /**
   * Revoke some denomination key.
   */
  OC_REVOKE,
  /**
   * Payback some coin.
   */
  OC_PAYBACK
};
/**
 * Structure specifying details about a coin to be melted.
 * Used in a NULL-terminated array as part of command
 * specification.
 */
struct MeltDetails
{
  /**
   * Amount to melt (including fee).
   */
  const char *amount;
  /**
   * Reference to reserve_withdraw operations for coin to
   * be used for the /refresh/melt operation.
   */
  const char *coin_ref;
};
/**
 * Information about a fresh coin generated by the refresh operation.
 */
struct FreshCoin
{
  /**
   * If @e amount is NULL, this specifies the denomination key to
   * use.  Otherwise, this will be set (by the interpreter) to the
   * denomination PK matching @e amount.
   */
  const struct TALER_EXCHANGE_DenomPublicKey *pk;
  /**
   * Set (by the interpreter) to the exchange's signature over the
   * coin's public key.
   */
  struct TALER_DenominationSignature sig;
  /**
   * Set (by the interpreter) to the coin's private key.
   */
  struct TALER_CoinSpendPrivateKeyP coin_priv;
};
/**
 * Details for a exchange operation to execute.
 */
struct Command
{
  /**
   * Opcode of the command.
   */
  enum OpCode oc;
  /**
   * Label for the command, can be NULL.
   */
  const char *label;
  /**
   * Which response code do we expect for this command?
   */
  unsigned int expected_response_code;
  /**
   * Details about the command.
   */
  union
  {
    /**
     * Information for a #OC_ADMIN_ADD_INCOMING command.
     */
    struct
    {
      /**
       * Label to another admin_add_incoming command if we
       * should deposit into an existing reserve, NULL if
       * a fresh reserve should be created.
       */
      const char *reserve_reference;
      /**
       * String describing the amount to add to the reserve.
       */
      const char *amount;
      /**
       * Sender account details (JSON).
       */
      const char *sender_details;
      /**
       * Transfer information identifier (JSON).
       */
      const char *transfer_details;
      /**
       * Set (by the interpreter) to the reserve's private key
       * we used to fill the reserve.
       */
      struct TALER_ReservePrivateKeyP reserve_priv;
      /**
       * Set to the API's handle during the operation.
       */
      struct TALER_EXCHANGE_AdminAddIncomingHandle *aih;
    } admin_add_incoming;
    /**
     * Information for a #OC_WITHDRAW_STATUS command.
     */
    struct
    {
      /**
       * Label to the #OC_ADMIN_ADD_INCOMING command which
       * created the reserve.
       */
      const char *reserve_reference;
      /**
       * Set to the API's handle during the operation.
       */
      struct TALER_EXCHANGE_ReserveStatusHandle *wsh;
      /**
       * Expected reserve balance.
       */
      const char *expected_balance;
    } reserve_status;
    /**
     * Information for a #OC_WITHDRAW_SIGN command.
     */
    struct
    {
      /**
       * Which reserve should we withdraw from?
       */
      const char *reserve_reference;
      /**
       * String describing the denomination value we should withdraw.
       * A corresponding denomination key must exist in the exchange's
       * offerings.  Can be NULL if @e pk is set instead.
       */
      const char *amount;
      /**
       * If @e amount is NULL, this specifies the denomination key to
       * use.  Otherwise, this will be set (by the interpreter) to the
       * denomination PK matching @e amount.
       */
      const struct TALER_EXCHANGE_DenomPublicKey *pk;
      /**
       * Set (by the interpreter) to the exchange's signature over the
       * coin's public key.
       */
      struct TALER_DenominationSignature sig;
      /**
       * Set (by the interpreter) to the coin's private key.
       */
      struct TALER_CoinSpendPrivateKeyP coin_priv;
      /**
       * Blinding key used for the operation.
       */
      struct TALER_DenominationBlindingKeyP blinding_key;
      /**
       * Withdraw handle (while operation is running).
       */
      struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh;
    } reserve_withdraw;
    /**
     * Information for a #OC_DEPOSIT command.
     */
    struct
    {
      /**
       * Amount to deposit.
       */
      const char *amount;
      /**
       * Reference to a reserve_withdraw operation for a coin to
       * be used for the /deposit operation.
       */
      const char *coin_ref;
      /**
       * If this @e coin_ref refers to an operation that generated
       * an array of coins, this value determines which coin to use.
       */
      unsigned int coin_idx;
      /**
       * JSON string describing the merchant's "wire details".
       */
      const char *wire_details;
      /**
       * JSON string describing what a proposal is about.
       */
      const char *proposal_data;
      /**
       * Relative time (to add to 'now') to compute the refund deadline.
       * Zero for no refunds.
       */
      struct GNUNET_TIME_Relative refund_deadline;
      /**
       * Set (by the interpreter) to a fresh private key of the merchant,
       * if @e refund_deadline is non-zero.
       */
      struct TALER_MerchantPrivateKeyP merchant_priv;
      /**
       * Deposit handle while operation is running.
       */
      struct TALER_EXCHANGE_DepositHandle *dh;
    } deposit;
    /**
     * Information for a #OC_REFRESH_MELT command.
     */
    struct
    {
      /**
       * Information about coins to be melted.
       */
      struct MeltDetails melted_coin;
      /**
       * Denominations of the fresh coins to withdraw.
       */
      const char **fresh_amounts;
      /**
       * Array of the public keys corresponding to
       * the @e fresh_amounts, set by the interpreter.
       */
      const struct TALER_EXCHANGE_DenomPublicKey **fresh_pks;
      /**
       * Melt handle while operation is running.
       */
      struct TALER_EXCHANGE_RefreshMeltHandle *rmh;
      /**
       * Data used in the refresh operation, set by the interpreter.
       */
      char *refresh_data;
      /**
       * Number of bytes in @e refresh_data, set by the interpreter.
       */
      size_t refresh_data_length;
      /**
       * Set by the interpreter (upon completion) to the noreveal
       * index selected by the exchange.
       */
      uint16_t noreveal_index;
    } refresh_melt;
    /**
     * Information for a #OC_REFRESH_REVEAL command.
     */
    struct
    {
      /**
       * Melt operation this is the matching reveal for.
       */
      const char *melt_ref;
      /**
       * Reveal handle while operation is running.
       */
      struct TALER_EXCHANGE_RefreshRevealHandle *rrh;
      /**
       * Number of fresh coins withdrawn, set by the interpreter.
       * Length of the @e fresh_coins array.
       */
      unsigned int num_fresh_coins;
      /**
       * Information about coins withdrawn, set by the interpreter.
       */
      struct FreshCoin *fresh_coins;
    } refresh_reveal;
    /**
     * Information for a #OC_REFRESH_LINK command.
     */
    struct
    {
      /**
       * Reveal operation this is the matching link for.
       */
      const char *reveal_ref;
      /**
       * Link handle while operation is running.
       */
      struct TALER_EXCHANGE_RefreshLinkHandle *rlh;
      /**
       * Which of the melted coins should be used for the linkage?
       */
      unsigned int coin_idx;
    } refresh_link;
    /**
     * Information for the /wire command.
     */
    struct {
      /**
       * Handle to the wire request.
       */
      struct TALER_EXCHANGE_WireHandle *wh;
      /**
       * Format we expect to see, others will be *ignored*.
       */
      const char *format;
      /**
       * Expected wire fee.
       */
      const char *expected_fee;
    } wire;
    /**
     * Information for the /track/transfer's command.
     */
    struct {
      /**
       * Handle to the wire deposits request.
       */
      struct TALER_EXCHANGE_TrackTransferHandle *wdh;
      /**
       * Reference to a command providing a WTID. If set, we use the
       * WTID from that command.  The command can be either an
       * #OC_DEPOSIT_WTID or an #OC_CHECK_BANK_TRANSFER.  In the
       * case of the bank transfer, we check that the total amount
       * claimed by the exchange matches the total amount transferred
       * by the bank.  In the case of a /track/transaction, we check
       * that the wire details match.
       */
      const char *wtid_ref;
      /**
       * WTID to use (used if @e wtid_ref is NULL).
       */
      struct TALER_WireTransferIdentifierRawP wtid;
      /**
       * What is the expected total amount? Only used if
       * @e expected_response_code was #MHD_HTTP_OK.
       */
      const char *total_amount_expected;
      /**
       * What is the expected wire fee? Only used if
       * @e expected_response_code was #MHD_HTTP_OK.
       */
      const char *wire_fee_expected;
      /* TODO: may want to add list of deposits we expected
         to see aggregated here in the future. */
    } wire_deposits;
    /**
     * Information for the /track/transaction command.
     */
    struct {
      /**
       * Handle to the deposit wtid request.
       */
      struct TALER_EXCHANGE_TrackTransactionHandle *dwh;
      /**
       * Which /deposit operation should we obtain WTID data for?
       */
      const char *deposit_ref;
      /**
       * Which #OC_CHECK_BANK_TRANSFER wtid should this match? NULL for none.
       */
      const char *bank_transfer_ref;
      /**
       * Wire transfer identifier, set if #MHD_HTTP_OK was the response code.
       */
      struct TALER_WireTransferIdentifierRawP wtid;
    } deposit_wtid;
    struct {
      /**
       * Process for the aggregator.
       */
      struct GNUNET_OS_Process *aggregator_proc;
      /**
       * ID of task called whenever we get a SIGCHILD.
       */
      struct GNUNET_SCHEDULER_Task *child_death_task;
    } run_aggregator;
    struct {
      /**
       * Which amount do we expect to see transferred?
       */
      const char *amount;
      /**
       * Which account do we expect to be debited?
       */
      uint64_t account_debit;
      /**
       * Which account do we expect to be credited?
       */
      uint64_t account_credit;
      /**
       * Which exchange base URL is expected?
       */
      const char *exchange_base_url;
      /**
       * Set (!) to the wire transfer identifier observed.
       */
      struct TALER_WireTransferIdentifierRawP wtid;
    } check_bank_transfer;
    struct {
      /**
       * Amount that should be refunded.
       */
      const char *amount;
      /**
       * Expected refund fee.
       */
      const char *fee;
      /**
       * Reference to the corresponding deposit operation.
       * Used to obtain proposal details, merchant keys,
       * fee structure, etc.
       */
      const char *deposit_ref;
      /**
       * Refund transaction identifier.
       */
      uint64_t rtransaction_id;
      /**
       * Handle to the refund operation (while it is ongoing).
       */
      struct TALER_EXCHANGE_RefundHandle *rh;
    } refund;
    struct {
      /**
       * Reference to a _coin's_ withdraw operation where the coin's denomination key
       * is the denomination key to be revoked.
       */
      const char *ref;
      /**
       * Process for the aggregator.
       */
      struct GNUNET_OS_Process *revoke_proc;
      /**
       * ID of task called whenever we get a SIGCHILD.
       */
      struct GNUNET_SCHEDULER_Task *child_death_task;
    } revoke;
    struct {
      /**
       * Reference to the _coin's_ withdraw operation.
       */
      const char *ref;
      /**
       * Amount that should be paid back.
       */
      const char *amount;
      /**
       * Handle to the ongoing /payback operation.
       */
      struct TALER_EXCHANGE_PaybackHandle *ph;
    } payback;
  } details;
};
/**
 * State of the interpreter loop.
 */
struct InterpreterState
{
  /**
   * Keys from the exchange.
   */
  const struct TALER_EXCHANGE_Keys *keys;
  /**
   * Commands the interpreter will run.
   */
  struct Command *commands;
  /**
   * Interpreter task (if one is scheduled).
   */
  struct GNUNET_SCHEDULER_Task *task;
  /**
   * Instruction pointer.  Tells #interpreter_run() which
   * instruction to run next.
   */
  unsigned int ip;
};
/**
 * Pipe used to communicate child death via signal.
 */
static struct GNUNET_DISK_PipeHandle *sigpipe;
/**
 * The testcase failed, return with an error code.
 *
 * @param is interpreter state to clean up
 */
static void
fail (struct InterpreterState *is)
{
  result = GNUNET_SYSERR;
  GNUNET_SCHEDULER_shutdown ();
}
/**
 * Find a command by label.
 *
 * @param is interpreter state to search
 * @param label label to look for
 * @return NULL if command was not found
 */
static const struct Command *
find_command (const struct InterpreterState *is,
              const char *label)
{
  unsigned int i;
  const struct Command *cmd;
  if (NULL == label)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Attempt to lookup command for empty label\n");
    return NULL;
  }
  for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++)
    if ( (NULL != cmd->label) &&
         (0 == strcmp (cmd->label,
                       label)) )
      return cmd;
  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
              "Command not found: %s\n",
              label);
  return NULL;
}
/**
 * Run the main interpreter loop that performs exchange operations.
 *
 * @param cls contains the `struct InterpreterState`
 */
static void
interpreter_run (void *cls);
/**
 * Run the next command with the interpreter.
 *
 * @param is current interpeter state.
 */
static void
next_command (struct InterpreterState *is)
{
  if (GNUNET_SYSERR == result)
    return; /* ignore, we already failed! */
  is->ip++;
  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                       is);
}
/**
 * Function called upon completion of our /admin/add/incoming request.
 *
 * @param cls closure with the interpreter state
 * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
 *                    0 if the exchange's reply is bogus (fails to follow the protocol)
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param full_response full response from the exchange (for logging, in case of errors)
 */
static void
add_incoming_cb (void *cls,
                 unsigned int http_status,
		 enum TALER_ErrorCode ec,
                 const json_t *full_response)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  cmd->details.admin_add_incoming.aih = NULL;
  if (MHD_HTTP_OK != http_status)
  {
    GNUNET_break (0);
    fail (is);
    return;
  }
  next_command (is);
}
/**
 * Check if the given historic event @a h corresponds to the given
 * command @a cmd.
 *
 * @param h event in history
 * @param cmd an #OC_ADMIN_ADD_INCOMING command
 * @return #GNUNET_OK if they match, #GNUNET_SYSERR if not
 */
static int
compare_admin_add_incoming_history (const struct TALER_EXCHANGE_ReserveHistory *h,
                                    const struct Command *cmd)
{
  struct TALER_Amount amount;
  if (TALER_EXCHANGE_RTT_DEPOSIT != h->type)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount (cmd->details.admin_add_incoming.amount,
                                         &amount));
  if (0 != TALER_amount_cmp (&amount,
                             &h->amount))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}
/**
 * Check if the given historic event @a h corresponds to the given
 * command @a cmd.
 *
 * @param h event in history
 * @param cmd an #OC_WITHDRAW_SIGN command
 * @return #GNUNET_OK if they match, #GNUNET_SYSERR if not
 */
static int
compare_reserve_withdraw_history (const struct TALER_EXCHANGE_ReserveHistory *h,
                                  const struct Command *cmd)
{
  struct TALER_Amount amount;
  struct TALER_Amount amount_with_fee;
  if (TALER_EXCHANGE_RTT_WITHDRAWAL != h->type)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount (cmd->details.reserve_withdraw.amount,
                                         &amount));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_add (&amount_with_fee,
                                   &amount,
                                   &cmd->details.reserve_withdraw.pk->fee_withdraw));
  if (0 != TALER_amount_cmp (&amount_with_fee,
                             &h->amount))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}
/**
 * Check if the given historic event @a h corresponds to the given
 * command @a cmd.
 *
 * @param h event in history
 * @param cmd an #OC_WITHDRAW_SIGN command
 * @return #GNUNET_OK if they match, #GNUNET_SYSERR if not
 */
static int
compare_reserve_payback_history (const struct TALER_EXCHANGE_ReserveHistory *h,
                                 const struct Command *cmd)
{
  struct TALER_Amount amount;
  if (TALER_EXCHANGE_RTT_PAYBACK != h->type)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount (cmd->details.payback.amount,
                                         &amount));
  if (0 != TALER_amount_cmp (&amount,
                             &h->amount))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}
/**
 * Function called with the result of a /reserve/status request.
 *
 * @param cls closure with the interpreter state
 * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
 *                    0 if the exchange's reply is bogus (fails to follow the protocol)
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param[in] json original response in JSON format (useful only for diagnostics)
 * @param balance current balance in the reserve, NULL on error
 * @param history_length number of entries in the transaction history, 0 on error
 * @param history detailed transaction history, NULL on error
 */
static void
reserve_status_cb (void *cls,
                   unsigned int http_status,
		   enum TALER_ErrorCode ec,
                   const json_t *json,
                   const struct TALER_Amount *balance,
                   unsigned int history_length,
                   const struct TALER_EXCHANGE_ReserveHistory *history)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  struct Command *rel;
  const struct Command *xrel;
  unsigned int i;
  unsigned int j;
  struct TALER_Amount amount;
  cmd->details.reserve_status.wsh = NULL;
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s\n",
                http_status,
                cmd->label);
    GNUNET_break (0);
    json_dumpf (json, stderr, 0);
    fail (is);
    return;
  }
  switch (http_status)
  {
  case MHD_HTTP_OK:
    /* FIXME: note that history events may come in a different
       order than the commands. However, for now this works... */
    j = 0;
    for (i=0;iip;i++)
    {
      switch ((rel = &is->commands[i])->oc)
      {
      case OC_ADMIN_ADD_INCOMING:
        if ( ( (NULL != rel->label) &&
               (0 == strcmp (cmd->details.reserve_status.reserve_reference,
                             rel->label) ) ) ||
             ( (NULL != rel->details.admin_add_incoming.reserve_reference) &&
               (0 == strcmp (cmd->details.reserve_status.reserve_reference,
                             rel->details.admin_add_incoming.reserve_reference) ) ) )
        {
          if ( (j >= history_length) ||
               (GNUNET_OK !=
                compare_admin_add_incoming_history (&history[j],
                                                    rel)) )
          {
            GNUNET_break (0);
            fail (is);
            return;
          }
          j++;
        }
        break;
      case OC_WITHDRAW_SIGN:
        if (0 == strcmp (cmd->details.reserve_status.reserve_reference,
                         rel->details.reserve_withdraw.reserve_reference))
        {
          if ( (j >= history_length) ||
               (GNUNET_OK !=
                compare_reserve_withdraw_history (&history[j],
                                                  rel)) )
          {
            GNUNET_break (0);
            fail (is);
            return;
          }
          j++;
        }
        break;
      case OC_PAYBACK:
        xrel = find_command (is,
                             rel->details.payback.ref);
        if (0 == strcmp (cmd->details.reserve_status.reserve_reference,
                         xrel->details.reserve_withdraw.reserve_reference))
        {
          if ( (j >= history_length) ||
               (GNUNET_OK !=
                compare_reserve_payback_history (&history[j],
                                                 rel)) )
          {
            GNUNET_break (0);
            fail (is);
            return;
          }
          j++;
        }
        break;
      default:
        /* unreleated, just skip */
        break;
      }
    }
    if (j != history_length)
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    if (NULL != cmd->details.reserve_status.expected_balance)
    {
      GNUNET_assert (GNUNET_OK ==
                     TALER_string_to_amount (cmd->details.reserve_status.expected_balance,
                                             &amount));
      if (0 != TALER_amount_cmp (&amount,
                                 balance))
      {
        GNUNET_break (0);
        fail (is);
        return;
      }
    }
    break;
  default:
    /* Unsupported status code (by test harness) */
    GNUNET_break (0);
    break;
  }
  next_command (is);
}
/**
 * Function called upon completion of our /reserve/withdraw request.
 *
 * @param cls closure with the interpreter state
 * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
 *                    0 if the exchange's reply is bogus (fails to follow the protocol)
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param sig signature over the coin, NULL on error
 * @param full_response full response from the exchange (for logging, in case of errors)
 */
static void
reserve_withdraw_cb (void *cls,
                     unsigned int http_status,
		     enum TALER_ErrorCode ec,
                     const struct TALER_DenominationSignature *sig,
                     const json_t *full_response)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  cmd->details.reserve_withdraw.wsh = NULL;
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s\n",
                http_status,
                cmd->label);
    json_dumpf (full_response, stderr, 0);
    GNUNET_break (0);
    fail (is);
    return;
  }
  switch (http_status)
  {
  case MHD_HTTP_OK:
    if (NULL == sig)
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    cmd->details.reserve_withdraw.sig.rsa_signature
      = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature);
    break;
  case MHD_HTTP_FORBIDDEN:
    /* nothing to check */
    break;
  case MHD_HTTP_NOT_FOUND:
    /* nothing to check */
    break;
  default:
    /* Unsupported status code (by test harness) */
    GNUNET_break (0);
    break;
  }
  next_command (is);
}
/**
 * Function called with the result of a /deposit operation.
 *
 * @param cls closure with the interpreter state
 * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit;
 *                    0 if the exchange's reply is bogus (fails to follow the protocol)
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param exchange_pub public key the exchange used for signing
 * @param obj the received JSON reply, should be kept as proof (and, in case of errors,
 *            be forwarded to the customer)
 */
static void
deposit_cb (void *cls,
            unsigned int http_status,
	    enum TALER_ErrorCode ec,
            const struct TALER_ExchangePublicKeyP *exchange_pub,
            const json_t *obj)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  cmd->details.deposit.dh = NULL;
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s\n",
                http_status,
                cmd->label);
    json_dumpf (obj, stderr, 0);
    fail (is);
    return;
  }
  next_command (is);
}
/**
 * Function called with the result of the /refresh/melt operation.
 *
 * @param cls closure with the interpreter state
 * @param http_status HTTP response code, never #MHD_HTTP_OK (200) as for successful intermediate response this callback is skipped.
 *                    0 if the exchange's reply is bogus (fails to follow the protocol)
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param noreveal_index choice by the exchange in the cut-and-choose protocol,
 *                    UINT16_MAX on error
 * @param exchange_pub public key the exchange used for signing
 * @param full_response full response from the exchange (for logging, in case of errors)
 */
static void
melt_cb (void *cls,
         unsigned int http_status,
	 enum TALER_ErrorCode ec,
         uint16_t noreveal_index,
         const struct TALER_ExchangePublicKeyP *exchange_pub,
         const json_t *full_response)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  cmd->details.refresh_melt.rmh = NULL;
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s\n",
                http_status,
                cmd->label);
    json_dumpf (full_response, stderr, 0);
    fail (is);
    return;
  }
  cmd->details.refresh_melt.noreveal_index = noreveal_index;
  next_command (is);
}
/**
 * Function called with the result of the /refresh/reveal operation.
 *
 * @param cls closure with the interpreter state
 * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
 *                    0 if the exchange's reply is bogus (fails to follow the protocol)
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param num_coins number of fresh coins created, length of the @a sigs and @a coin_privs arrays, 0 if the operation failed
 * @param coin_privs array of @a num_coins private keys for the coins that were created, NULL on error
 * @param sigs array of signature over @a num_coins coins, NULL on error
 * @param full_response full response from the exchange (for logging, in case of errors)
 */
static void
reveal_cb (void *cls,
           unsigned int http_status,
	   enum TALER_ErrorCode ec,
           unsigned int num_coins,
           const struct TALER_CoinSpendPrivateKeyP *coin_privs,
           const struct TALER_DenominationSignature *sigs,
           const json_t *full_response)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  const struct Command *ref;
  unsigned int i;
  cmd->details.refresh_reveal.rrh = NULL;
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s\n",
                http_status,
                cmd->label);
    json_dumpf (full_response, stderr, 0);
    fail (is);
    return;
  }
  ref = find_command (is,
                      cmd->details.refresh_reveal.melt_ref);
  GNUNET_assert (NULL != ref);
  cmd->details.refresh_reveal.num_fresh_coins = num_coins;
  switch (http_status)
  {
  case MHD_HTTP_OK:
    cmd->details.refresh_reveal.fresh_coins
      = GNUNET_new_array (num_coins,
                          struct FreshCoin);
    for (i=0;idetails.refresh_reveal.fresh_coins[i];
      fc->pk = ref->details.refresh_melt.fresh_pks[i];
      fc->coin_priv = coin_privs[i];
      fc->sig.rsa_signature
        = GNUNET_CRYPTO_rsa_signature_dup (sigs[i].rsa_signature);
    }
    break;
  default:
    break;
  }
  next_command (is);
}
/**
 * Function called with the result of a /refresh/link operation.
 *
 * @param cls closure with the interpreter state
 * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
 *                    0 if the exchange's reply is bogus (fails to follow the protocol)
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param num_coins number of fresh coins created, length of the @a sigs and @a coin_privs arrays, 0 if the operation failed
 * @param coin_privs array of @a num_coins private keys for the coins that were created, NULL on error
 * @param sigs array of signature over @a num_coins coins, NULL on error
 * @param pubs array of public keys for the @a sigs, NULL on error
 * @param full_response full response from the exchange (for logging, in case of errors)
 */
static void
link_cb (void *cls,
         unsigned int http_status,
	 enum TALER_ErrorCode ec,
         unsigned int num_coins,
         const struct TALER_CoinSpendPrivateKeyP *coin_privs,
         const struct TALER_DenominationSignature *sigs,
         const struct TALER_DenominationPublicKey *pubs,
         const json_t *full_response)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  const struct Command *ref;
  unsigned int i;
  unsigned int j;
  unsigned int found;
  cmd->details.refresh_link.rlh = NULL;
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s\n",
                http_status,
                cmd->label);
    json_dumpf (full_response, stderr, 0);
    fail (is);
    return;
  }
  ref = find_command (is,
                      cmd->details.refresh_link.reveal_ref);
  GNUNET_assert (NULL != ref);
  switch (http_status)
  {
  case MHD_HTTP_OK:
    /* check that number of coins returned matches */
    if (num_coins != ref->details.refresh_reveal.num_fresh_coins)
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    /* check that the coins match */
    for (i=0;idetails.refresh_reveal.fresh_coins[j];
	if ( (0 == memcmp (&coin_privs[i],
			   &fc->coin_priv,
			   sizeof (struct TALER_CoinSpendPrivateKeyP))) &&
	     (0 == GNUNET_CRYPTO_rsa_signature_cmp (fc->sig.rsa_signature,
						    sigs[i].rsa_signature)) &&
	     (0 == GNUNET_CRYPTO_rsa_public_key_cmp (fc->pk->key.rsa_public_key,
						     pubs[i].rsa_public_key)) )
	{
	  found++;
	  break;
	}
      }
    if (found != num_coins)
    {
      fprintf (stderr,
	       "Only %u/%u coins match expectations\n",
	       found,
	       num_coins);
      GNUNET_break (0);
      fail (is);
      return;
    }
    break;
  default:
    break;
  }
  next_command (is);
}
/**
 * Task triggered whenever we receive a SIGCHLD (child
 * process died).
 *
 * @param cls closure, NULL if we need to self-restart
 */
static void
maint_child_death (void *cls)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  const struct GNUNET_DISK_FileHandle *pr;
  char c[16];
  switch (cmd->oc) {
  case OC_RUN_AGGREGATOR:
    cmd->details.run_aggregator.child_death_task = NULL;
    pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ);
    GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c)));
    GNUNET_OS_process_wait (cmd->details.run_aggregator.aggregator_proc);
    GNUNET_OS_process_destroy (cmd->details.run_aggregator.aggregator_proc);
    cmd->details.run_aggregator.aggregator_proc = NULL;
    break;
  case OC_REVOKE:
    cmd->details.revoke.child_death_task = NULL;
    pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ);
    GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c)));
    GNUNET_OS_process_wait (cmd->details.revoke.revoke_proc);
    GNUNET_OS_process_destroy (cmd->details.revoke.revoke_proc);
    cmd->details.revoke.revoke_proc = NULL;
    /* trigger reload of denomination key information */
    GNUNET_break (0 ==
                  GNUNET_OS_process_kill (exchanged,
                                          SIGUSR1));
    sleep (5); /* make sure signal was received and processed */
    break;
  default:
    GNUNET_break (0);
    fail (is);
    return;
  }
  next_command (is);
}
/**
 * Find denomination key matching the given amount.
 *
 * @param keys array of keys to search
 * @param amount coin value to look for
 * @return NULL if no matching key was found
 */
static const struct TALER_EXCHANGE_DenomPublicKey *
find_pk (const struct TALER_EXCHANGE_Keys *keys,
         const struct TALER_Amount *amount)
{
  unsigned int i;
  struct GNUNET_TIME_Absolute now;
  struct TALER_EXCHANGE_DenomPublicKey *pk;
  char *str;
  now = GNUNET_TIME_absolute_get ();
  for (i=0;inum_denom_keys;i++)
  {
    pk = &keys->denom_keys[i];
    if ( (0 == TALER_amount_cmp (amount,
                                 &pk->value)) &&
         (now.abs_value_us >= pk->valid_from.abs_value_us) &&
         (now.abs_value_us < pk->withdraw_valid_until.abs_value_us) )
      return pk;
  }
  /* do 2nd pass to check if expiration times are to blame for failure */
  str = TALER_amount_to_string (amount);
  for (i=0;inum_denom_keys;i++)
  {
    pk = &keys->denom_keys[i];
    if ( (0 == TALER_amount_cmp (amount,
                                 &pk->value)) &&
         ( (now.abs_value_us < pk->valid_from.abs_value_us) ||
           (now.abs_value_us > pk->withdraw_valid_until.abs_value_us) ) )
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Have denomination key for `%s', but with wrong expiration range %llu vs [%llu,%llu)\n",
                  str,
                  (unsigned long long) now.abs_value_us,
                  (unsigned long long) pk->valid_from.abs_value_us,
                  (unsigned long long) pk->withdraw_valid_until.abs_value_us);
      GNUNET_free (str);
      return NULL;
    }
  }
  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
              "No denomination key for amount %s found\n",
              str);
  GNUNET_free (str);
  return NULL;
}
/**
 * Function called with information about the wire fees
 * for each wire method.
 *
 * @param cls closure
 * @param wire_method name of the wire method (i.e. "sepa")
 * @param fees fee structure for this method
 */
static void
check_fee_cb (void *cls,
              const char *wire_method,
              const struct TALER_EXCHANGE_WireAggregateFees *fees)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  struct TALER_Amount expected_amount;
  GNUNET_break ( (0 == strcasecmp ("test",
                                   wire_method)) ||
                 (0 == strcasecmp ("sepa",
                                   wire_method)) );
  if (GNUNET_OK !=
      TALER_string_to_amount (cmd->details.wire.expected_fee,
                              &expected_amount))
  {
    GNUNET_break (0);
    fail (is);
    return;
  }
  while (NULL != fees)
  {
    if (0 != TALER_amount_cmp (&fees->wire_fee,
                               &expected_amount))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Wire fee missmatch to command %s\n",
                  cmd->label);
      fail (is);
      return;
    }
    fees = fees->next;
  }
}
/**
 * Callbacks called with the result(s) of a
 * wire format inquiry request to the exchange.
 *
 * @param cls closure with the interpreter state
 * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful request;
 *                    0 if the exchange's reply is bogus (fails to follow the protocol)
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param obj the received JSON reply, if successful this should be the wire
 *            format details as provided by /wire.
 */
static void
wire_cb (void *cls,
         unsigned int http_status,
	 enum TALER_ErrorCode ec,
         const json_t *obj)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  cmd->details.wire.wh = NULL;
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s\n",
                http_status,
                cmd->label);
    json_dumpf (obj, stderr, 0);
    fail (is);
    return;
  }
  switch (http_status)
  {
  case MHD_HTTP_OK:
    {
      json_t *method;
      method = json_object_get (obj,
                                cmd->details.wire.format);
      if (NULL == method)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Expected method `%s' not included in response to command %s\n",
                    cmd->details.wire.format,
                    cmd->label);
        json_dumpf (obj, stderr, 0);
        fail (is);
        return;
      }
      if (GNUNET_OK !=
          TALER_EXCHANGE_wire_get_fees (&TALER_EXCHANGE_get_keys (exchange)->master_pub,
                                        obj,
                                        &check_fee_cb,
                                        is))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Wire fee extraction in command %s failed\n",
                    cmd->label);
        json_dumpf (obj, stderr, 0);
        fail (is);
        return;
      }
    }
    break;
  default:
    break;
  }
  next_command (is);
}
/**
 * Function called with detailed wire transfer data, including all
 * of the coin transactions that were combined into the wire transfer.
 *
 * @param cls closure
 * @param http_status HTTP status code we got, 0 on exchange protocol violation
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param exchange_pub public key the exchange used for signing
 * @param json original json reply (may include signatures, those have then been
 *        validated already)
 * @param h_wire hash of the wire transfer address the transfer went to, or NULL on error
 * @param execution_time time when the exchange claims to have performed the wire transfer
 * @param total_amount total amount of the wire transfer, or NULL if the exchange could
 *             not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK)
 * @param wire_fee wire fee that was charged by the exchange
 * @param details_length length of the @a details array
 * @param details array with details about the combined transactions
 */
static void
wire_deposits_cb (void *cls,
                  unsigned int http_status,
		  enum TALER_ErrorCode ec,
                  const struct TALER_ExchangePublicKeyP *exchange_pub,
                  const json_t *json,
                  const struct GNUNET_HashCode *h_wire,
                  struct GNUNET_TIME_Absolute execution_time,
                  const struct TALER_Amount *total_amount,
                  const struct TALER_Amount *wire_fee,
                  unsigned int details_length,
                  const struct TALER_TrackTransferDetails *details)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  const struct Command *ref;
  struct TALER_Amount expected_amount;
  cmd->details.wire_deposits.wdh = NULL;
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s\n",
                http_status,
                cmd->label);
    json_dumpf (json, stderr, 0);
    fail (is);
    return;
  }
  switch (http_status)
  {
  case MHD_HTTP_OK:
    if (GNUNET_OK !=
        TALER_string_to_amount (cmd->details.wire_deposits.total_amount_expected,
                                &expected_amount))
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    if (0 != TALER_amount_cmp (total_amount,
                               &expected_amount))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Total amount missmatch to command %s\n",
                  cmd->label);
      json_dumpf (json, stderr, 0);
      fail (is);
      return;
    }
    if (GNUNET_OK !=
        TALER_string_to_amount (cmd->details.wire_deposits.wire_fee_expected,
                                &expected_amount))
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    if (0 != TALER_amount_cmp (wire_fee,
                               &expected_amount))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Wire fee missmatch to command %s\n",
                  cmd->label);
      json_dumpf (json, stderr, 0);
      fail (is);
      return;
    }
    ref = find_command (is,
                        cmd->details.wire_deposits.wtid_ref);
    GNUNET_assert (NULL != ref);
    switch (ref->oc)
    {
    case OC_DEPOSIT_WTID:
      if (NULL != ref->details.deposit_wtid.deposit_ref)
      {
        const struct Command *dep;
        struct GNUNET_HashCode hw;
        json_t *wire;
        dep = find_command (is,
                            ref->details.deposit_wtid.deposit_ref);
        GNUNET_assert (NULL != dep);
        wire = json_loads (dep->details.deposit.wire_details,
                           JSON_REJECT_DUPLICATES,
                           NULL);
        TALER_JSON_hash (wire,
                         &hw);
        json_decref (wire);
        if (0 != memcmp (&hw,
                         h_wire,
                         sizeof (struct GNUNET_HashCode)))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      "Wire hash missmatch to command %s\n",
                      cmd->label);
          json_dumpf (json, stderr, 0);
          fail (is);
          return;
        }
      }
      break;
    case OC_CHECK_BANK_TRANSFER:
      if (GNUNET_OK !=
          TALER_string_to_amount (ref->details.check_bank_transfer.amount,
                                  &expected_amount))
      {
        GNUNET_break (0);
        fail (is);
        return;
      }
      if (0 != TALER_amount_cmp (total_amount,
                                 &expected_amount))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Total amount missmatch to command %s\n",
                    cmd->label);
        json_dumpf (json, stderr, 0);
        fail (is);
        return;
      }
      break;
    default:
      GNUNET_break (0);
      fail (is);
      return;
    }
    break;
  default:
    break;
  }
  next_command (is);
}
/**
 * Function called with detailed wire transfer data.
 *
 * @param cls closure
 * @param http_status HTTP status code we got, 0 on exchange protocol violation
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param exchange_pub public key the exchange used for signing
 * @param json original json reply (may include signatures, those have then been
 *        validated already)
 * @param wtid wire transfer identifier used by the exchange, NULL if exchange did not
 *                  yet execute the transaction
 * @param execution_time actual or planned execution time for the wire transfer
 * @param coin_contribution contribution to the @a total_amount of the deposited coin (may be NULL)
 * @param total_amount total amount of the wire transfer, or NULL if the exchange could
 *             not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK)
 */
static void
deposit_wtid_cb (void *cls,
                 unsigned int http_status,
		 enum TALER_ErrorCode ec,
                 const struct TALER_ExchangePublicKeyP *exchange_pub,
                 const json_t *json,
                 const struct TALER_WireTransferIdentifierRawP *wtid,
                 struct GNUNET_TIME_Absolute execution_time,
                 const struct TALER_Amount *coin_contribution)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  cmd->details.deposit_wtid.dwh = NULL;
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s\n",
                http_status,
                cmd->label);
    json_dumpf (json, stderr, 0);
    fail (is);
    return;
  }
  switch (http_status)
  {
  case MHD_HTTP_OK:
    cmd->details.deposit_wtid.wtid = *wtid;
    if (NULL != cmd->details.deposit_wtid.bank_transfer_ref)
    {
      const struct Command *ref;
      ref = find_command (is,
                          cmd->details.deposit_wtid.bank_transfer_ref);
      GNUNET_assert (NULL != ref);
      if (0 != memcmp (wtid,
                       &ref->details.check_bank_transfer.wtid,
                       sizeof (struct TALER_WireTransferIdentifierRawP)))
      {
        GNUNET_break (0);
        fail (is);
        return;
      }
    }
    break;
  case MHD_HTTP_ACCEPTED:
    /* allowed, nothing to check here */
    break;
  case MHD_HTTP_NOT_FOUND:
    /* allowed, nothing to check here */
    break;
  default:
    GNUNET_break (0);
    break;
  }
  next_command (is);
}
/**
 * Check the result for the refund request.
 *
 * @param cls closure
 * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit;
 *                    0 if the exchange's reply is bogus (fails to follow the protocol)
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param exchange_pub public key the exchange used for signing @a obj
 * @param obj the received JSON reply, should be kept as proof (and, in particular,
 *            be forwarded to the customer)
 */
static void
refund_cb (void *cls,
           unsigned int http_status,
	   enum TALER_ErrorCode ec,
           const struct TALER_ExchangePublicKeyP *exchange_pub,
           const json_t *obj)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  cmd->details.refund.rh = NULL;
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s\n",
                http_status,
                cmd->label);
    json_dumpf (obj, stderr, 0);
    fail (is);
    return;
  }
  switch (http_status)
  {
  case MHD_HTTP_OK:
    break;
  default:
    break;
  }
  next_command (is);
}
/**
 * Check the result of the payback request.
 *
 * @param cls closure
 * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
 *                    0 if the exchange's reply is bogus (fails to follow the protocol)
 * @param ec taler-specific error code, #TALER_EC_NONE on success
 * @param amount amount the exchange will wire back for this coin
 * @param timestamp what time did the exchange receive the /payback request
 * @param reserve_pub public key of the reserve receiving the payback
 * @param full_response full response from the exchange (for logging, in case of errors)
 */
static void
payback_cb (void *cls,
            unsigned int http_status,
            enum TALER_ErrorCode ec,
            const struct TALER_Amount *amount,
            struct GNUNET_TIME_Absolute timestamp,
            const struct TALER_ReservePublicKeyP *reserve_pub,
            const json_t *full_response)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  const struct Command *withdraw;
  const struct Command *reserve;
  struct TALER_Amount expected_amount;
  struct TALER_ReservePublicKeyP rp;
  cmd->details.payback.ph = NULL;
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s\n",
                http_status,
                cmd->label);
    json_dumpf (full_response, stderr, 0);
    fail (is);
    return;
  }
  withdraw = find_command (is,
                           cmd->details.payback.ref);
  reserve = find_command (is,
                          withdraw->details.reserve_withdraw.reserve_reference);
  GNUNET_CRYPTO_eddsa_key_get_public (&reserve->details.admin_add_incoming.reserve_priv.eddsa_priv,
                                      &rp.eddsa_pub);
  switch (http_status)
  {
  case MHD_HTTP_OK:
    if (GNUNET_OK !=
        TALER_string_to_amount (cmd->details.payback.amount,
                                &expected_amount))
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    if (0 != TALER_amount_cmp (amount,
                               &expected_amount))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Total amount missmatch to command %s\n",
                  cmd->label);
      json_dumpf (full_response, stderr, 0);
      fail (is);
      return;
    }
    if (0 != memcmp (reserve_pub,
                     &rp,
                     sizeof (rp)))
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    break;
  default:
    break;
  }
  next_command (is);
}
/**
 * Given a command that is used to withdraw coins,
 * extract the corresponding public key of the coin.
 *
 * @param coin command relating to coin withdrawal or refresh
 * @param idx index to use if we got multiple coins from the @a coin command
 * @param[out] coin_pub where to store the public key of the coin
 */
static void
get_public_key_from_coin_command (const struct Command *coin,
                                  unsigned int idx,
                                  struct TALER_CoinSpendPublicKeyP *coin_pub)
{
  switch (coin->oc)
  {
  case OC_WITHDRAW_SIGN:
    GNUNET_CRYPTO_eddsa_key_get_public (&coin->details.reserve_withdraw.coin_priv.eddsa_priv,
                                        &coin_pub->eddsa_pub);
    break;
  case OC_REFRESH_REVEAL:
    {
      const struct FreshCoin *fc;
      GNUNET_assert (idx < coin->details.refresh_reveal.num_fresh_coins);
      fc = &coin->details.refresh_reveal.fresh_coins[idx];
      GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv,
                                          &coin_pub->eddsa_pub);
    }
    break;
  default:
    GNUNET_assert (0);
  }
}
/**
 * Run the main interpreter loop that performs exchange operations.
 *
 * @param cls contains the `struct InterpreterState`
 */
static void
interpreter_run (void *cls)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  const struct Command *ref;
  struct TALER_ReservePublicKeyP reserve_pub;
  struct TALER_CoinSpendPublicKeyP coin_pub;
  struct TALER_Amount amount;
  struct GNUNET_TIME_Absolute execution_date;
  json_t *sender_details;
  json_t *transfer_details;
  const struct GNUNET_SCHEDULER_TaskContext *tc;
  is->task = NULL;
  tc = GNUNET_SCHEDULER_get_task_context ();
  if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
  {
    fprintf (stderr,
             "Test aborted by shutdown request\n");
    fail (is);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Running command `%s'\n",
              cmd->label);
  switch (cmd->oc)
  {
  case OC_END:
    result = GNUNET_OK;
    GNUNET_SCHEDULER_shutdown ();
    return;
  case OC_ADMIN_ADD_INCOMING:
    if (NULL !=
        cmd->details.admin_add_incoming.reserve_reference)
    {
      ref = find_command (is,
                          cmd->details.admin_add_incoming.reserve_reference);
      GNUNET_assert (NULL != ref);
      GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
      cmd->details.admin_add_incoming.reserve_priv
        = ref->details.admin_add_incoming.reserve_priv;
    }
    else
    {
      struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
      priv = GNUNET_CRYPTO_eddsa_key_create ();
      cmd->details.admin_add_incoming.reserve_priv.eddsa_priv = *priv;
      GNUNET_free (priv);
    }
    GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.admin_add_incoming.reserve_priv.eddsa_priv,
                                        &reserve_pub.eddsa_pub);
    if (GNUNET_OK !=
        TALER_string_to_amount (cmd->details.admin_add_incoming.amount,
                                &amount))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Failed to parse amount `%s' at %u\n",
                  cmd->details.admin_add_incoming.amount,
                  is->ip);
      fail (is);
      return;
    }
    sender_details = json_loads (cmd->details.admin_add_incoming.sender_details,
                                 JSON_REJECT_DUPLICATES,
                                 NULL);
    if (NULL == sender_details)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Failed to parse sender details `%s' at %u\n",
                  cmd->details.admin_add_incoming.sender_details,
                  is->ip);
      fail (is);
      return;
    }
    transfer_details = json_loads (cmd->details.admin_add_incoming.transfer_details,
                                   JSON_REJECT_DUPLICATES,
                                   NULL);
    if (NULL == transfer_details)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Failed to parse transfer details `%s' at %u\n",
                  cmd->details.admin_add_incoming.transfer_details,
                  is->ip);
      fail (is);
      return;
    }
    execution_date = GNUNET_TIME_absolute_get ();
    GNUNET_TIME_round_abs (&execution_date);
    cmd->details.admin_add_incoming.aih
      = TALER_EXCHANGE_admin_add_incoming (exchange,
                                           "http://localhost:18080/",
                                           &reserve_pub,
                                           &amount,
                                           execution_date,
                                           sender_details,
                                           transfer_details,
                                           &add_incoming_cb,
                                           is);
    json_decref (sender_details);
    json_decref (transfer_details);
    if (NULL == cmd->details.admin_add_incoming.aih)
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    return;
  case OC_WITHDRAW_STATUS:
    GNUNET_assert (NULL !=
                   cmd->details.reserve_status.reserve_reference);
    ref = find_command (is,
                        cmd->details.reserve_status.reserve_reference);
    GNUNET_assert (NULL != ref);
    GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
    GNUNET_CRYPTO_eddsa_key_get_public (&ref->details.admin_add_incoming.reserve_priv.eddsa_priv,
                                        &reserve_pub.eddsa_pub);
    cmd->details.reserve_status.wsh
      = TALER_EXCHANGE_reserve_status (exchange,
                                       &reserve_pub,
                                       &reserve_status_cb,
                                       is);
    return;
  case OC_WITHDRAW_SIGN:
    GNUNET_assert (NULL !=
                   cmd->details.reserve_withdraw.reserve_reference);
    ref = find_command (is,
                        cmd->details.reserve_withdraw.reserve_reference);
    GNUNET_assert (NULL != ref);
    GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
    if (NULL != cmd->details.reserve_withdraw.amount)
    {
      if (GNUNET_OK !=
          TALER_string_to_amount (cmd->details.reserve_withdraw.amount,
                                  &amount))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Failed to parse amount `%s' at %u\n",
                    cmd->details.reserve_withdraw.amount,
                    is->ip);
        fail (is);
        return;
      }
      cmd->details.reserve_withdraw.pk = find_pk (is->keys,
                                                  &amount);
    }
    if (NULL == cmd->details.reserve_withdraw.pk)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Failed to determine denomination key at %u\n",
                  is->ip);
      fail (is);
      return;
    }
    /* create coin's private key */
    {
      struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
      priv = GNUNET_CRYPTO_eddsa_key_create ();
      cmd->details.reserve_withdraw.coin_priv.eddsa_priv = *priv;
      GNUNET_free (priv);
    }
    GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.reserve_withdraw.coin_priv.eddsa_priv,
                                        &coin_pub.eddsa_pub);
    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
				&cmd->details.reserve_withdraw.blinding_key,
				sizeof (cmd->details.reserve_withdraw.blinding_key));
    cmd->details.reserve_withdraw.wsh
      = TALER_EXCHANGE_reserve_withdraw (exchange,
                                     cmd->details.reserve_withdraw.pk,
                                     &ref->details.admin_add_incoming.reserve_priv,
                                     &cmd->details.reserve_withdraw.coin_priv,
                                     &cmd->details.reserve_withdraw.blinding_key,
                                     &reserve_withdraw_cb,
                                     is);
    if (NULL == cmd->details.reserve_withdraw.wsh)
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    return;
  case OC_DEPOSIT:
    {
      struct GNUNET_HashCode h_proposal_data;
      const struct TALER_CoinSpendPrivateKeyP *coin_priv;
      const struct TALER_EXCHANGE_DenomPublicKey *coin_pk;
      const struct TALER_DenominationSignature *coin_pk_sig;
      struct TALER_CoinSpendPublicKeyP coin_pub;
      struct TALER_CoinSpendSignatureP coin_sig;
      struct GNUNET_TIME_Absolute refund_deadline;
      struct GNUNET_TIME_Absolute wire_deadline;
      struct GNUNET_TIME_Absolute timestamp;
      struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
      struct TALER_MerchantPublicKeyP merchant_pub;
      json_t *proposal_data;
      json_t *wire;
      GNUNET_assert (NULL !=
                     cmd->details.deposit.coin_ref);
      ref = find_command (is,
                          cmd->details.deposit.coin_ref);
      GNUNET_assert (NULL != ref);
      switch (ref->oc)
      {
      case OC_WITHDRAW_SIGN:
        coin_priv = &ref->details.reserve_withdraw.coin_priv;
        coin_pk = ref->details.reserve_withdraw.pk;
        coin_pk_sig = &ref->details.reserve_withdraw.sig;
        break;
      case OC_REFRESH_REVEAL:
        {
          const struct FreshCoin *fc;
          unsigned int idx;
          idx = cmd->details.deposit.coin_idx;
          GNUNET_assert (idx < ref->details.refresh_reveal.num_fresh_coins);
          fc = &ref->details.refresh_reveal.fresh_coins[idx];
          coin_priv = &fc->coin_priv;
          coin_pk = fc->pk;
          coin_pk_sig = &fc->sig;
        }
        break;
      default:
        GNUNET_assert (0);
      }
      if (GNUNET_OK !=
          TALER_string_to_amount (cmd->details.deposit.amount,
                                  &amount))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Failed to parse amount `%s' at %u\n",
                    cmd->details.deposit.amount,
                    is->ip);
        fail (is);
        return;
      }
      proposal_data = json_loads (cmd->details.deposit.proposal_data,
                             JSON_REJECT_DUPLICATES,
                             NULL);
      if (NULL == proposal_data)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Failed to parse proposal data `%s' at %u/%s\n",
                    cmd->details.deposit.proposal_data,
                    is->ip,
                    cmd->label);
        fail (is);
        return;
      }
      TALER_JSON_hash (proposal_data,
                       &h_proposal_data);
      json_decref (proposal_data);
      wire = json_loads (cmd->details.deposit.wire_details,
                         JSON_REJECT_DUPLICATES,
                         NULL);
      if (NULL == wire)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Failed to parse wire details `%s' at %u/%s\n",
                    cmd->details.deposit.wire_details,
                    is->ip,
                    cmd->label);
        fail (is);
        return;
      }
      GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
                                          &coin_pub.eddsa_pub);
      priv = GNUNET_CRYPTO_eddsa_key_create ();
      cmd->details.deposit.merchant_priv.eddsa_priv = *priv;
      GNUNET_free (priv);
      if (0 != cmd->details.deposit.refund_deadline.rel_value_us)
      {
        refund_deadline = GNUNET_TIME_relative_to_absolute (cmd->details.deposit.refund_deadline);
        wire_deadline = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (cmd->details.deposit.refund_deadline, 2));
      }
      else
      {
        refund_deadline = GNUNET_TIME_UNIT_ZERO_ABS;
        wire_deadline = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO);
      }
      GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.deposit.merchant_priv.eddsa_priv,
                                          &merchant_pub.eddsa_pub);
      timestamp = GNUNET_TIME_absolute_get ();
      GNUNET_TIME_round_abs (×tamp);
      GNUNET_TIME_round_abs (&refund_deadline);
      GNUNET_TIME_round_abs (&wire_deadline);
      {
        struct TALER_DepositRequestPS dr;
        memset (&dr, 0, sizeof (dr));
        dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS));
        dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
        dr.h_proposal_data = h_proposal_data;
        TALER_JSON_hash (wire,
                         &dr.h_wire);
        dr.timestamp = GNUNET_TIME_absolute_hton (timestamp);
        dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
        TALER_amount_hton (&dr.amount_with_fee,
                           &amount);
        TALER_amount_hton (&dr.deposit_fee,
                           &coin_pk->fee_deposit);
        dr.merchant = merchant_pub;
        dr.coin_pub = coin_pub;
        GNUNET_assert (GNUNET_OK ==
                       GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
                                                 &dr.purpose,
                                                 &coin_sig.eddsa_signature));
      }
      cmd->details.deposit.dh
        = TALER_EXCHANGE_deposit (exchange,
                                  &amount,
                                  wire_deadline,
                                  wire,
                                  &h_proposal_data,
                                  &coin_pub,
                                  coin_pk_sig,
                                  &coin_pk->key,
                                  timestamp,
                                  &merchant_pub,
                                  refund_deadline,
                                  &coin_sig,
                                  &deposit_cb,
                                  is);
      if (NULL == cmd->details.deposit.dh)
      {
        GNUNET_break (0);
        json_decref (wire);
        fail (is);
        return;
      }
      json_decref (wire);
      return;
    }
  case OC_REFRESH_MELT:
    {
      unsigned int num_fresh_coins;
      cmd->details.refresh_melt.noreveal_index = UINT16_MAX;
      for (num_fresh_coins=0;
           NULL != cmd->details.refresh_melt.fresh_amounts[num_fresh_coins];
           num_fresh_coins++) ;
      cmd->details.refresh_melt.fresh_pks
        = GNUNET_new_array (num_fresh_coins,
                            const struct TALER_EXCHANGE_DenomPublicKey *);
      {
        struct TALER_CoinSpendPrivateKeyP melt_priv;
        struct TALER_Amount melt_amount;
        struct TALER_DenominationSignature melt_sig;
        struct TALER_EXCHANGE_DenomPublicKey melt_pk;
        struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins];
        unsigned int i;
        const struct MeltDetails *md = &cmd->details.refresh_melt.melted_coin;
        ref = find_command (is,
                            md->coin_ref);
        GNUNET_assert (NULL != ref);
        GNUNET_assert (OC_WITHDRAW_SIGN == ref->oc);
        melt_priv = ref->details.reserve_withdraw.coin_priv;
        if (GNUNET_OK !=
            TALER_string_to_amount (md->amount,
                                    &melt_amount))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      "Failed to parse amount `%s' at %u\n",
                      md->amount,
                      is->ip);
          fail (is);
          return;
        }
        melt_sig = ref->details.reserve_withdraw.sig;
        melt_pk = *ref->details.reserve_withdraw.pk;
        for (i=0;idetails.refresh_melt.fresh_amounts[i],
                                      &amount))
          {
            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                        "Failed to parse amount `%s' at %u\n",
                        cmd->details.reserve_withdraw.amount,
                        is->ip);
            fail (is);
            return;
          }
          cmd->details.refresh_melt.fresh_pks[i]
            = find_pk (is->keys,
                       &amount);
          fresh_pks[i] = *cmd->details.refresh_melt.fresh_pks[i];
        }
        cmd->details.refresh_melt.refresh_data
          = TALER_EXCHANGE_refresh_prepare (&melt_priv,
                                            &melt_amount,
                                            &melt_sig,
                                            &melt_pk,
                                            GNUNET_YES,
                                            num_fresh_coins,
                                            fresh_pks,
                                            &cmd->details.refresh_melt.refresh_data_length);
        if (NULL == cmd->details.refresh_melt.refresh_data)
        {
          GNUNET_break (0);
          fail (is);
          return;
        }
        cmd->details.refresh_melt.rmh
          = TALER_EXCHANGE_refresh_melt (exchange,
                                     cmd->details.refresh_melt.refresh_data_length,
                                     cmd->details.refresh_melt.refresh_data,
                                     &melt_cb,
                                     is);
        if (NULL == cmd->details.refresh_melt.rmh)
        {
          GNUNET_break (0);
          fail (is);
          return;
        }
      }
    }
    return;
  case OC_REFRESH_REVEAL:
    ref = find_command (is,
                        cmd->details.refresh_reveal.melt_ref);
    GNUNET_assert (NULL != ref);
    cmd->details.refresh_reveal.rrh
      = TALER_EXCHANGE_refresh_reveal (exchange,
                                       ref->details.refresh_melt.refresh_data_length,
                                       ref->details.refresh_melt.refresh_data,
                                       ref->details.refresh_melt.noreveal_index,
                                       &reveal_cb,
                                       is);
    if (NULL == cmd->details.refresh_reveal.rrh)
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    return;
  case OC_REFRESH_LINK:
    /* find reveal command */
    ref = find_command (is,
                        cmd->details.refresh_link.reveal_ref);
    GNUNET_assert (NULL != ref);
    /* find melt command */
    ref = find_command (is,
                        ref->details.refresh_reveal.melt_ref);
    GNUNET_assert (NULL != ref);
    /* find reserve_withdraw command */
    {
      const struct MeltDetails *md;
      md = &ref->details.refresh_melt.melted_coin;
      ref = find_command (is,
                          md->coin_ref);
      GNUNET_assert (NULL != ref);
    }
    GNUNET_assert (OC_WITHDRAW_SIGN == ref->oc);
    /* finally, use private key from withdraw sign command */
    cmd->details.refresh_link.rlh
      = TALER_EXCHANGE_refresh_link (exchange,
                                     &ref->details.reserve_withdraw.coin_priv,
                                     &link_cb,
                                     is);
    if (NULL == cmd->details.refresh_link.rlh)
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    return;
  case OC_WIRE:
    cmd->details.wire.wh = TALER_EXCHANGE_wire (exchange,
                                                &wire_cb,
                                                is);
    return;
  case OC_WIRE_DEPOSITS:
    if (NULL != cmd->details.wire_deposits.wtid_ref)
    {
      ref = find_command (is,
                          cmd->details.wire_deposits.wtid_ref);
      GNUNET_assert (NULL != ref);
      switch (ref->oc)
      {
      case OC_DEPOSIT_WTID:
        cmd->details.wire_deposits.wtid = ref->details.deposit_wtid.wtid;
        break;
      case OC_CHECK_BANK_TRANSFER:
        cmd->details.wire_deposits.wtid = ref->details.check_bank_transfer.wtid;
        break;
      default:
        GNUNET_break (0);
        fail (is);
        return;
      }
    }
    cmd->details.wire_deposits.wdh
      = TALER_EXCHANGE_track_transfer (exchange,
                                      &cmd->details.wire_deposits.wtid,
                                      &wire_deposits_cb,
                                      is);
    return;
  case OC_DEPOSIT_WTID:
    {
      struct GNUNET_HashCode h_wire;
      struct GNUNET_HashCode h_proposal_data;
      json_t *wire;
      json_t *proposal_data;
      const struct Command *coin;
      struct TALER_CoinSpendPublicKeyP coin_pub;
      ref = find_command (is,
                          cmd->details.deposit_wtid.deposit_ref);
      GNUNET_assert (NULL != ref);
      coin = find_command (is,
                           ref->details.deposit.coin_ref);
      GNUNET_assert (NULL != coin);
      get_public_key_from_coin_command (coin,
                                        ref->details.deposit.coin_idx,
                                        &coin_pub);
      wire = json_loads (ref->details.deposit.wire_details,
                         JSON_REJECT_DUPLICATES,
                         NULL);
      GNUNET_assert (NULL != wire);
      TALER_JSON_hash (wire,
                       &h_wire);
      json_decref (wire);
      proposal_data = json_loads (ref->details.deposit.proposal_data,
                             JSON_REJECT_DUPLICATES,
                             NULL);
      GNUNET_assert (NULL != proposal_data);
      TALER_JSON_hash (proposal_data,
                       &h_proposal_data);
      json_decref (proposal_data);
      cmd->details.deposit_wtid.dwh
          = TALER_EXCHANGE_track_transaction (exchange,
                                              &ref->details.deposit.merchant_priv,
                                              &h_wire,
                                              &h_proposal_data,
                                              &coin_pub,
                                              &deposit_wtid_cb,
                                              is);
    }
    return;
  case OC_RUN_AGGREGATOR:
    {
      const struct GNUNET_DISK_FileHandle *pr;
      cmd->details.run_aggregator.aggregator_proc
        = GNUNET_OS_start_process (GNUNET_NO,
                                   GNUNET_OS_INHERIT_STD_ALL,
                                   NULL, NULL, NULL,
                                   "taler-exchange-aggregator",
                                   "taler-exchange-aggregator",
                                   "-c", "test_exchange_api.conf",
                                   "-t", /* exit when done */
                                   NULL);
      if (NULL == cmd->details.run_aggregator.aggregator_proc)
      {
        GNUNET_break (0);
        fail (is);
        return;
      }
      pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ);
      cmd->details.run_aggregator.child_death_task
        = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
                                          pr,
                                          &maint_child_death, is);
      return;
    }
  case OC_CHECK_BANK_TRANSFER:
    {
      if (GNUNET_OK !=
          TALER_string_to_amount (cmd->details.check_bank_transfer.amount,
                                  &amount))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Failed to parse amount `%s' at %u\n",
                    cmd->details.reserve_withdraw.amount,
                    is->ip);
        fail (is);
        return;
      }
      if (GNUNET_OK !=
          TALER_FAKEBANK_check (fakebank,
                                &amount,
                                cmd->details.check_bank_transfer.account_debit,
                                cmd->details.check_bank_transfer.account_credit,
                                cmd->details.check_bank_transfer.exchange_base_url,
                                &cmd->details.check_bank_transfer.wtid))
      {
        GNUNET_break (0);
        fail (is);
        return;
      }
      next_command (is);
      return;
    }
  case OC_CHECK_BANK_TRANSFERS_EMPTY:
    {
      if (GNUNET_OK !=
          TALER_FAKEBANK_check_empty (fakebank))
      {
        GNUNET_break (0);
        fail (is);
        return;
      }
      next_command (is);
      return;
    }
  case OC_REFUND:
    {
      const struct Command *coin;
      struct GNUNET_HashCode h_proposal_data;
      json_t *proposal_data;
      struct TALER_CoinSpendPublicKeyP coin_pub;
      struct TALER_Amount refund_fee;
      if (GNUNET_OK !=
          TALER_string_to_amount (cmd->details.refund.amount,
                                  &amount))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Failed to parse amount `%s' at %u\n",
                    cmd->details.refund.amount,
                    is->ip);
        fail (is);
        return;
      }
      if (GNUNET_OK !=
          TALER_string_to_amount (cmd->details.refund.fee,
                                  &refund_fee))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Failed to parse amount `%s' at %u\n",
                    cmd->details.refund.fee,
                    is->ip);
        fail (is);
        return;
      }
      ref = find_command (is,
                          cmd->details.refund.deposit_ref);
      GNUNET_assert (NULL != ref);
      proposal_data = json_loads (ref->details.deposit.proposal_data,
                             JSON_REJECT_DUPLICATES,
                             NULL);
      GNUNET_assert (NULL != proposal_data);
      TALER_JSON_hash (proposal_data,
                       &h_proposal_data);
      json_decref (proposal_data);
      coin = find_command (is,
                           ref->details.deposit.coin_ref);
      GNUNET_assert (NULL != coin);
      get_public_key_from_coin_command (coin,
                                        ref->details.deposit.coin_idx,
                                        &coin_pub);
      cmd->details.refund.rh
        = TALER_EXCHANGE_refund (exchange,
                                 &amount,
                                 &refund_fee,
                                 &h_proposal_data,
                                 &coin_pub,
                                 cmd->details.refund.rtransaction_id,
                                 &ref->details.deposit.merchant_priv,
                                 &refund_cb,
                                 is);
      if (NULL == cmd->details.refund.rh)
      {
        GNUNET_break (0);
        fail (is);
        return;
      }
      return;
    }
  case OC_REVOKE:
    {
      const struct GNUNET_DISK_FileHandle *pr;
      char *dhks;
      const struct Command *ref;
      ref = find_command (is,
                          cmd->details.revoke.ref);
      GNUNET_assert (NULL != ref);
      dhks = GNUNET_STRINGS_data_to_string_alloc (&ref->details.reserve_withdraw.pk->h_key,
                                                  sizeof (struct GNUNET_HashCode));
      cmd->details.revoke.revoke_proc
        = GNUNET_OS_start_process (GNUNET_NO,
                                   GNUNET_OS_INHERIT_STD_ALL,
                                   NULL, NULL, NULL,
                                   "taler-exchange-keyup",
                                   "taler-exchange-keyup",
                                   "-c", "test_exchange_api.conf",
                                   "-r", dhks,
                                   NULL);
      GNUNET_free (dhks);
      if (NULL == cmd->details.revoke.revoke_proc)
      {
        GNUNET_break (0);
        fail (is);
        return;
      }
      pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ);
      cmd->details.revoke.child_death_task
        = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
                                          pr,
                                          &maint_child_death,
                                          is);
      return;
    }
  case OC_PAYBACK:
    {
      const struct Command *ref;
      ref = find_command (is,
                          cmd->details.payback.ref);
      GNUNET_assert (NULL != ref);
      cmd->details.payback.ph
        = TALER_EXCHANGE_payback (exchange,
                                  ref->details.reserve_withdraw.pk,
                                  &ref->details.reserve_withdraw.sig,
                                  &ref->details.reserve_withdraw.coin_priv,
                                  &ref->details.reserve_withdraw.blinding_key,
                                  &payback_cb,
                                  is);
      return;
    }
  default:
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unknown instruction %d at %u (%s)\n",
                cmd->oc,
                is->ip,
                cmd->label);
    fail (is);
    return;
  }
}
/**
 * Signal handler called for SIGCHLD.  Triggers the
 * respective handler by writing to the trigger pipe.
 */
static void
sighandler_child_death ()
{
  static char c;
  int old_errno = errno;	/* back-up errno */
  GNUNET_break (1 ==
		GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle
					(sigpipe, GNUNET_DISK_PIPE_END_WRITE),
					&c, sizeof (c)));
  errno = old_errno;		/* restore errno */
}
/**
 * Function run when the test terminates (good or bad) with timeout.
 *
 * @param cls NULL
 */
static void
do_timeout (void *cls)
{
  timeout_task = NULL;
  GNUNET_SCHEDULER_shutdown ();
}
/**
 * Function run when the test terminates (good or bad).
 * Cleans up our state.
 *
 * @param cls the interpreter state.
 */
static void
do_shutdown (void *cls)
{
  struct InterpreterState *is = cls;
  struct Command *cmd;
  unsigned int i;
  fprintf (stderr,
           "Executing shutdown at `%s'\n",
           is->commands[is->ip].label);
  for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++)
  {
    switch (cmd->oc)
    {
    case OC_END:
      GNUNET_assert (0);
      break;
    case OC_ADMIN_ADD_INCOMING:
      if (NULL != cmd->details.admin_add_incoming.aih)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Command %u (%s) did not complete\n",
                    i,
                    cmd->label);
        TALER_EXCHANGE_admin_add_incoming_cancel (cmd->details.admin_add_incoming.aih);
        cmd->details.admin_add_incoming.aih = NULL;
      }
      break;
    case OC_WITHDRAW_STATUS:
      if (NULL != cmd->details.reserve_status.wsh)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Command %u (%s) did not complete\n",
                    i,
                    cmd->label);
        TALER_EXCHANGE_reserve_status_cancel (cmd->details.reserve_status.wsh);
        cmd->details.reserve_status.wsh = NULL;
      }
      break;
    case OC_WITHDRAW_SIGN:
      if (NULL != cmd->details.reserve_withdraw.wsh)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Command %u (%s) did not complete\n",
                    i,
                    cmd->label);
        TALER_EXCHANGE_reserve_withdraw_cancel (cmd->details.reserve_withdraw.wsh);
        cmd->details.reserve_withdraw.wsh = NULL;
      }
      if (NULL != cmd->details.reserve_withdraw.sig.rsa_signature)
      {
        GNUNET_CRYPTO_rsa_signature_free (cmd->details.reserve_withdraw.sig.rsa_signature);
        cmd->details.reserve_withdraw.sig.rsa_signature = NULL;
      }
      break;
    case OC_DEPOSIT:
      if (NULL != cmd->details.deposit.dh)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Command %u (%s) did not complete\n",
                    i,
                    cmd->label);
        TALER_EXCHANGE_deposit_cancel (cmd->details.deposit.dh);
        cmd->details.deposit.dh = NULL;
      }
      break;
    case OC_REFRESH_MELT:
      if (NULL != cmd->details.refresh_melt.rmh)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Command %u (%s) did not complete\n",
                    i,
                    cmd->label);
        TALER_EXCHANGE_refresh_melt_cancel (cmd->details.refresh_melt.rmh);
        cmd->details.refresh_melt.rmh = NULL;
      }
      GNUNET_free_non_null (cmd->details.refresh_melt.fresh_pks);
      cmd->details.refresh_melt.fresh_pks = NULL;
      GNUNET_free_non_null (cmd->details.refresh_melt.refresh_data);
      cmd->details.refresh_melt.refresh_data = NULL;
      cmd->details.refresh_melt.refresh_data_length = 0;
      break;
    case OC_REFRESH_REVEAL:
      if (NULL != cmd->details.refresh_reveal.rrh)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Command %u (%s) did not complete\n",
                    i,
                    cmd->label);
        TALER_EXCHANGE_refresh_reveal_cancel (cmd->details.refresh_reveal.rrh);
        cmd->details.refresh_reveal.rrh = NULL;
      }
      {
        unsigned int j;
        struct FreshCoin *fresh_coins;
        fresh_coins = cmd->details.refresh_reveal.fresh_coins;
        for (j=0;jdetails.refresh_reveal.num_fresh_coins;j++)
          GNUNET_CRYPTO_rsa_signature_free (fresh_coins[j].sig.rsa_signature);
      }
      GNUNET_free_non_null (cmd->details.refresh_reveal.fresh_coins);
      cmd->details.refresh_reveal.fresh_coins = NULL;
      cmd->details.refresh_reveal.num_fresh_coins = 0;
      break;
    case OC_REFRESH_LINK:
      if (NULL != cmd->details.refresh_link.rlh)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Command %u (%s) did not complete\n",
                    i,
                    cmd->label);
        TALER_EXCHANGE_refresh_link_cancel (cmd->details.refresh_link.rlh);
        cmd->details.refresh_link.rlh = NULL;
      }
      break;
    case OC_WIRE:
      if (NULL != cmd->details.wire.wh)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Command %u (%s) did not complete\n",
                    i,
                    cmd->label);
        TALER_EXCHANGE_wire_cancel (cmd->details.wire.wh);
        cmd->details.wire.wh = NULL;
      }
      break;
    case OC_WIRE_DEPOSITS:
      if (NULL != cmd->details.wire_deposits.wdh)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Command %u (%s) did not complete\n",
                    i,
                    cmd->label);
        TALER_EXCHANGE_track_transfer_cancel (cmd->details.wire_deposits.wdh);
        cmd->details.wire_deposits.wdh = NULL;
      }
      break;
    case OC_DEPOSIT_WTID:
      if (NULL != cmd->details.deposit_wtid.dwh)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Command %u (%s) did not complete\n",
                    i,
                    cmd->label);
        TALER_EXCHANGE_track_transaction_cancel (cmd->details.deposit_wtid.dwh);
        cmd->details.deposit_wtid.dwh = NULL;
      }
      break;
    case OC_RUN_AGGREGATOR:
      if (NULL != cmd->details.run_aggregator.aggregator_proc)
      {
        GNUNET_break (0 ==
                      GNUNET_OS_process_kill (cmd->details.run_aggregator.aggregator_proc,
                                              SIGKILL));
        GNUNET_OS_process_wait (cmd->details.run_aggregator.aggregator_proc);
        GNUNET_OS_process_destroy (cmd->details.run_aggregator.aggregator_proc);
        cmd->details.run_aggregator.aggregator_proc = NULL;
      }
      if (NULL != cmd->details.run_aggregator.child_death_task)
      {
        GNUNET_SCHEDULER_cancel (cmd->details.run_aggregator.child_death_task);
        cmd->details.run_aggregator.child_death_task = NULL;
      }
      break;
    case OC_CHECK_BANK_TRANSFER:
      break;
    case OC_CHECK_BANK_TRANSFERS_EMPTY:
      break;
    case OC_REFUND:
      if (NULL != cmd->details.refund.rh)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Command %u (%s) did not complete\n",
                    i,
                    cmd->label);
        TALER_EXCHANGE_refund_cancel (cmd->details.refund.rh);
        cmd->details.refund.rh = NULL;
      }
      break;
    case OC_REVOKE:
      if (NULL != cmd->details.revoke.revoke_proc)
      {
        GNUNET_break (0 ==
                      GNUNET_OS_process_kill (cmd->details.revoke.revoke_proc,
                                              SIGKILL));
        GNUNET_OS_process_wait (cmd->details.revoke.revoke_proc);
        GNUNET_OS_process_destroy (cmd->details.revoke.revoke_proc);
        cmd->details.revoke.revoke_proc = NULL;
      }
      if (NULL != cmd->details.revoke.child_death_task)
      {
        GNUNET_SCHEDULER_cancel (cmd->details.revoke.child_death_task);
        cmd->details.revoke.child_death_task = NULL;
      }
      break;
    case OC_PAYBACK:
      if (NULL != cmd->details.payback.ph)
      {
        TALER_EXCHANGE_payback_cancel (cmd->details.payback.ph);
        cmd->details.payback.ph = NULL;
      }
      break;
    default:
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Unknown instruction %d at %u (%s)\n",
                  cmd->oc,
                  i,
                  cmd->label);
      break;
    }
  }
  if (NULL != is->task)
  {
    GNUNET_SCHEDULER_cancel (is->task);
    is->task = NULL;
  }
  GNUNET_free (is);
  if (NULL != fakebank)
  {
    TALER_FAKEBANK_stop (fakebank);
    fakebank = NULL;
  }
  if (NULL != exchange)
  {
    TALER_EXCHANGE_disconnect (exchange);
    exchange = NULL;
  }
  if (NULL != ctx)
  {
    GNUNET_CURL_fini (ctx);
    ctx = NULL;
  }
  if (NULL != rc)
  {
    GNUNET_CURL_gnunet_rc_destroy (rc);
    rc = NULL;
  }
  if (NULL != timeout_task)
  {
    GNUNET_SCHEDULER_cancel (timeout_task);
    timeout_task = NULL;
  }
}
/**
 * Functions of this type are called to provide the retrieved signing and
 * denomination keys of the exchange.  No TALER_EXCHANGE_*() functions should be called
 * in this callback.
 *
 * @param cls closure
 * @param keys information about keys of the exchange
 */
static void
cert_cb (void *cls,
         const struct TALER_EXCHANGE_Keys *keys)
{
  struct InterpreterState *is = cls;
  /* check that keys is OK */
#define ERR(cond) do { if(!(cond)) break; GNUNET_break (0); GNUNET_SCHEDULER_shutdown(); return; } while (0)
  ERR (NULL == keys);
  ERR (0 == keys->num_sign_keys);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Read %u signing keys\n",
              keys->num_sign_keys);
  ERR (0 == keys->num_denom_keys);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Read %u denomination keys\n",
              keys->num_denom_keys);
#undef ERR
  /* run actual tests via interpreter-loop */
  is->keys = keys;
  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                       is);
}
/**
 * Main function that will be run by the scheduler.
 *
 * @param cls closure
 */
static void
run (void *cls)
{
  struct InterpreterState *is;
  static const char *melt_fresh_amounts_1[] = {
    "EUR:1",
    "EUR:1",
    "EUR:1",
    "EUR:0.1",
    "EUR:0.1",
    "EUR:0.1",
    "EUR:0.1",
    "EUR:0.1",
    "EUR:0.1",
    "EUR:0.1",
    "EUR:0.1",
    "EUR:0.01",
    "EUR:0.01",
    "EUR:0.01",
    "EUR:0.01",
    "EUR:0.01",
    "EUR:0.01",
    /* with 0.01 withdraw fees (except for 1ct coins),
       this totals up to exactly EUR:3.97, and with
       the 0.03 refresh fee, to EUR:4.0*/
    NULL
  };
  static struct Command commands[] =
  {
    /* *************** start of /wire testing ************** */
#if WIRE_TEST
    { .oc = OC_WIRE,
      .label = "wire-test",
      /* expecting 'test' method in response */
      .expected_response_code = MHD_HTTP_OK,
      .details.wire.format = "test",
      .details.wire.expected_fee = "EUR:0.01" },
#endif
#if WIRE_SEPA
    { .oc = OC_WIRE,
      .label = "wire-sepa",
      /* expecting 'sepa' method in response */
      .expected_response_code = MHD_HTTP_OK,
      .details.wire.format = "sepa",
      .details.wire.expected_fee = "EUR:0.01" },
#endif
    /* *************** end of /wire testing ************** */
#if WIRE_TEST
    /* None of this works if 'test' is not allowed as we do
       /admin/add/incoming with format 'test' */
    /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config */
    { .oc = OC_ADMIN_ADD_INCOMING,
      .label = "create-reserve-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42}",
      .details.admin_add_incoming.transfer_details = "{ \"uuid\":1  }",
      .details.admin_add_incoming.amount = "EUR:5.01" },
    /* Withdraw a 5 EUR coin, at fee of 1 ct */
    { .oc = OC_WITHDRAW_SIGN,
      .label = "withdraw-coin-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.reserve_withdraw.reserve_reference = "create-reserve-1",
      .details.reserve_withdraw.amount = "EUR:5" },
    /* Check that deposit and withdraw operation are in history, and
       that the balance is now at zero */
    { .oc = OC_WITHDRAW_STATUS,
      .label = "withdraw-status-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.reserve_status.reserve_reference = "create-reserve-1",
      .details.reserve_status.expected_balance = "EUR:0" },
    /* Try to deposit the 5 EUR coin (in full) */
    { .oc = OC_DEPOSIT,
      .label = "deposit-simple",
      .expected_response_code = MHD_HTTP_OK,
      .details.deposit.amount = "EUR:5",
      .details.deposit.coin_ref = "withdraw-coin-1",
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42  }",
      .details.deposit.proposal_data = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }" },
    /* Try to overdraw funds ... */
    { .oc = OC_WITHDRAW_SIGN,
      .label = "withdraw-coin-2",
      .expected_response_code = MHD_HTTP_FORBIDDEN,
      .details.reserve_withdraw.reserve_reference = "create-reserve-1",
      .details.reserve_withdraw.amount = "EUR:5" },
    /* Try to double-spend the 5 EUR coin with different wire details */
    { .oc = OC_DEPOSIT,
      .label = "deposit-double-1",
      .expected_response_code = MHD_HTTP_FORBIDDEN,
      .details.deposit.amount = "EUR:5",
      .details.deposit.coin_ref = "withdraw-coin-1",
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":43  }",
      .details.deposit.proposal_data = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }" },
    /* Try to double-spend the 5 EUR coin at the same merchant (but different
       transaction ID) */
    { .oc = OC_DEPOSIT,
      .label = "deposit-double-2",
      .expected_response_code = MHD_HTTP_FORBIDDEN,
      .details.deposit.amount = "EUR:5",
      .details.deposit.coin_ref = "withdraw-coin-1",
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42  }",
      .details.deposit.proposal_data = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }" },
    /* Try to double-spend the 5 EUR coin at the same merchant (but different
       proposal) */
    { .oc = OC_DEPOSIT,
      .label = "deposit-double-3",
      .expected_response_code = MHD_HTTP_FORBIDDEN,
      .details.deposit.amount = "EUR:5",
      .details.deposit.coin_ref = "withdraw-coin-1",
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42  }",
      .details.deposit.proposal_data = "{ \"items\":[{ \"name\":\"ice cream\", \"value\":2 } ] }" },
    /* ***************** /refresh testing ******************** */
    /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct */
    { .oc = OC_ADMIN_ADD_INCOMING,
      .label = "refresh-create-reserve-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":424  }",
      .details.admin_add_incoming.transfer_details = "{ \"uuid\":2  }",
      .details.admin_add_incoming.amount = "EUR:5.01" },
    /* Withdraw a 5 EUR coin, at fee of 1 ct */
    { .oc = OC_WITHDRAW_SIGN,
      .label = "refresh-withdraw-coin-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.reserve_withdraw.reserve_reference = "refresh-create-reserve-1",
      .details.reserve_withdraw.amount = "EUR:5" },
    /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in full)
       (merchant would receive EUR:0.99 due to 1 ct deposit fee) */
    { .oc = OC_DEPOSIT,
      .label = "refresh-deposit-partial",
      .expected_response_code = MHD_HTTP_OK,
      .details.deposit.amount = "EUR:1",
      .details.deposit.coin_ref = "refresh-withdraw-coin-1",
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42  }",
      .details.deposit.proposal_data = "{ \"items\" : [ { \"name\":\"ice cream\", \"value\":\"EUR:1\" } ] }" },
    /* Melt the rest of the coin's value (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */
    { .oc = OC_REFRESH_MELT,
      .label = "refresh-melt-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.refresh_melt.melted_coin = {
        .amount = "EUR:4",
        .coin_ref = "refresh-withdraw-coin-1" },
      .details.refresh_melt.fresh_amounts = melt_fresh_amounts_1 },
    /* Complete (successful) melt operation, and withdraw the coins */
    { .oc = OC_REFRESH_REVEAL,
      .label = "refresh-reveal-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.refresh_reveal.melt_ref = "refresh-melt-1" },
    /* do it again to check idempotency */
    { .oc = OC_REFRESH_REVEAL,
      .label = "refresh-reveal-1-idempotency",
      .expected_response_code = MHD_HTTP_OK,
      .details.refresh_reveal.melt_ref = "refresh-melt-1" },
    /* Test that /refresh/link works */
    { .oc = OC_REFRESH_LINK,
      .label = "refresh-link-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.refresh_link.reveal_ref = "refresh-reveal-1" },
    /* Test successfully spending coins from the refresh operation:
       first EUR:1 */
    { .oc = OC_DEPOSIT,
      .label = "refresh-deposit-refreshed-1a",
      .expected_response_code = MHD_HTTP_OK,
      .details.deposit.amount = "EUR:1",
      .details.deposit.coin_ref = "refresh-reveal-1-idempotency",
      .details.deposit.coin_idx = 0,
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42  }",
      .details.deposit.proposal_data = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }" },
    /* Test successfully spending coins from the refresh operation:
       finally EUR:0.1 */
    { .oc = OC_DEPOSIT,
      .label = "refresh-deposit-refreshed-1b",
      .expected_response_code = MHD_HTTP_OK,
      .details.deposit.amount = "EUR:0.1",
      .details.deposit.coin_ref = "refresh-reveal-1",
      .details.deposit.coin_idx = 4,
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":43  }",
      .details.deposit.proposal_data = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }" },
    /* Test running a failing melt operation (same operation again must fail) */
    { .oc = OC_REFRESH_MELT,
      .label = "refresh-melt-failing",
      .expected_response_code = MHD_HTTP_FORBIDDEN,
      .details.refresh_melt.melted_coin = {
        .amount = "EUR:4",
        .coin_ref = "refresh-withdraw-coin-1" },
      .details.refresh_melt.fresh_amounts = melt_fresh_amounts_1 },
    // FIXME: also test with coin that was already melted
    // (signature differs from coin that was deposited...)
    /* *************** end of /refresh testing ************** */
    /* ************** Test tracking API ******************** */
    /* Try resolving a deposit's WTID, as we never triggered
       execution of transactions, the answer should be that
       the exchange knows about the deposit, but has no WTID yet. */
    { .oc = OC_DEPOSIT_WTID,
      .label = "deposit-wtid-found",
      .expected_response_code = MHD_HTTP_ACCEPTED,
      .details.deposit_wtid.deposit_ref = "deposit-simple" },
    /* Try resolving a deposit's WTID for a failed deposit.
       As the deposit failed, the answer should be that
       the exchange does NOT know about the deposit. */
    { .oc = OC_DEPOSIT_WTID,
      .label = "deposit-wtid-failing",
      .expected_response_code = MHD_HTTP_NOT_FOUND,
      .details.deposit_wtid.deposit_ref = "deposit-double-2" },
    /* Try resolving an undefined (all zeros) WTID; this
       should fail as obviously the exchange didn't use that
       WTID value for any transaction. */
    { .oc = OC_WIRE_DEPOSITS,
      .label = "wire-deposit-failing",
      .expected_response_code = MHD_HTTP_NOT_FOUND },
    /* Run transfers. Note that _actual_ aggregation will NOT
       happen here, as each deposit operation is run with a
       fresh merchant public key! */
    { .oc = OC_RUN_AGGREGATOR,
      .label = "run-aggregator" },
    { .oc = OC_CHECK_BANK_TRANSFER,
      .label = "check_bank_transfer-499c",
      .details.check_bank_transfer.exchange_base_url = "https://exchange.com/",
      .details.check_bank_transfer.amount = "EUR:4.98",
      .details.check_bank_transfer.account_debit = 2,
      .details.check_bank_transfer.account_credit = 42
    },
    { .oc = OC_CHECK_BANK_TRANSFER,
      .label = "check_bank_transfer-99c1",
      .details.check_bank_transfer.exchange_base_url = "https://exchange.com/",
      .details.check_bank_transfer.amount = "EUR:0.98",
      .details.check_bank_transfer.account_debit = 2,
      .details.check_bank_transfer.account_credit = 42
    },
    { .oc = OC_CHECK_BANK_TRANSFER,
      .label = "check_bank_transfer-99c2",
      .details.check_bank_transfer.exchange_base_url = "https://exchange.com/",
      .details.check_bank_transfer.amount = "EUR:0.98",
      .details.check_bank_transfer.account_debit = 2,
      .details.check_bank_transfer.account_credit = 42
    },
    { .oc = OC_CHECK_BANK_TRANSFER,
      .label = "check_bank_transfer-9c",
      .details.check_bank_transfer.exchange_base_url = "https://exchange.com/",
      .details.check_bank_transfer.amount = "EUR:0.08",
      .details.check_bank_transfer.account_debit = 2,
      .details.check_bank_transfer.account_credit = 43
    },
    { .oc = OC_CHECK_BANK_TRANSFERS_EMPTY,
      .label = "check_bank_empty" },
    { .oc = OC_DEPOSIT_WTID,
      .label = "deposit-wtid-ok",
      .expected_response_code = MHD_HTTP_OK,
      .details.deposit_wtid.deposit_ref = "deposit-simple",
      .details.deposit_wtid.bank_transfer_ref = "check_bank_transfer-499c" },
    { .oc = OC_WIRE_DEPOSITS,
      .label = "wire-deposits-success-bank",
      .expected_response_code = MHD_HTTP_OK,
      .details.wire_deposits.wtid_ref = "check_bank_transfer-99c1",
      .details.wire_deposits.total_amount_expected = "EUR:0.98",
      .details.wire_deposits.wire_fee_expected = "EUR:0.01"  },
    { .oc = OC_WIRE_DEPOSITS,
      .label = "wire-deposits-success-wtid",
      .expected_response_code = MHD_HTTP_OK,
      .details.wire_deposits.wtid_ref = "deposit-wtid-ok",
      .details.wire_deposits.total_amount_expected = "EUR:4.98",
      .details.wire_deposits.wire_fee_expected = "EUR:0.01"  },
    /* ************** End of tracking API testing************* */
    /* ************** Test /refund API  ************* */
    /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config */
    { .oc = OC_ADMIN_ADD_INCOMING,
      .label = "create-reserve-r1",
      .expected_response_code = MHD_HTTP_OK,
      .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42 }",
      .details.admin_add_incoming.transfer_details = "{ \"uuid\":3  }",
      .details.admin_add_incoming.amount = "EUR:5.01" },
    /* Withdraw a 5 EUR coin, at fee of 1 ct */
    { .oc = OC_WITHDRAW_SIGN,
      .label = "withdraw-coin-r1",
      .expected_response_code = MHD_HTTP_OK,
      .details.reserve_withdraw.reserve_reference = "create-reserve-r1",
      .details.reserve_withdraw.amount = "EUR:5" },
    /* Spend 5 EUR of the 5 EUR coin (in full)
       (merchant would receive EUR:4.99 due to 1 ct deposit fee) */
    { .oc = OC_DEPOSIT,
      .label = "deposit-refund-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.deposit.amount = "EUR:5",
      .details.deposit.coin_ref = "withdraw-coin-r1",
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42  }",
      .details.deposit.proposal_data = "{ \"items\" : [ { \"name\":\"ice cream\", \"value\":\"EUR:5\" } ] }",
      .details.deposit.refund_deadline = { 60LL * 1000 * 1000 } /* 60 s */,
    },
    /* Run transfers. Should do nothing as refund deadline blocks it */
    { .oc = OC_RUN_AGGREGATOR,
      .label = "run-aggregator-refund" },
    /* check that aggregator didn't do anything, as expected */
    { .oc = OC_CHECK_BANK_TRANSFERS_EMPTY,
      .label = "check-refund-not-run" },
    /* Trigger refund */
    { .oc = OC_REFUND,
      .label = "refund-ok",
      .expected_response_code = MHD_HTTP_OK,
      .details.refund.amount = "EUR:5",
      .details.refund.fee = "EUR:0.01",
      .details.refund.deposit_ref = "deposit-refund-1",
    },
    /* Spend 4.99 EUR of the refunded 4.99 EUR coin (1ct gone due to refund)
       (merchant would receive EUR:4.98 due to 1 ct deposit fee) */
    { .oc = OC_DEPOSIT,
      .label = "deposit-refund-2",
      .expected_response_code = MHD_HTTP_OK,
      .details.deposit.amount = "EUR:4.99",
      .details.deposit.coin_ref = "withdraw-coin-r1",
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42  }",
      .details.deposit.proposal_data = "{ \"items\" : [ { \"name\":\"more ice cream\", \"value\":\"EUR:5\" } ] }",
    },
    /* Run transfers. This will do the transfer as refund deadline was 0 */
    { .oc = OC_RUN_AGGREGATOR,
      .label = "run-aggregator-3" },
    /* Check that deposit did run */
    { .oc = OC_CHECK_BANK_TRANSFER,
      .label = "check_bank_transfer-pre-refund",
      .details.check_bank_transfer.exchange_base_url = "https://exchange.com/",
      .details.check_bank_transfer.amount = "EUR:4.97",
      .details.check_bank_transfer.account_debit = 2,
      .details.check_bank_transfer.account_credit = 42
    },
    /* Run failing refund, as past deadline & aggregation */
    { .oc = OC_REFUND,
      .label = "refund-fail",
      .expected_response_code = MHD_HTTP_GONE,
      .details.refund.amount = "EUR:4.99",
      .details.refund.fee = "EUR:0.01",
      .details.refund.deposit_ref = "deposit-refund-2",
    },
    /* ************** End of refund API testing************* */
    /* ************** Test /payback API  ************* */
    /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config,
       then withdraw a coin and then have it be paid back. */
    { .oc = OC_ADMIN_ADD_INCOMING,
      .label = "payback-create-reserve-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42}",
      .details.admin_add_incoming.transfer_details = "{ \"uuid\":4  }",
      .details.admin_add_incoming.amount = "EUR:5.01" },
    /* Withdraw a 5 EUR coin, at fee of 1 ct */
    { .oc = OC_WITHDRAW_SIGN,
      .label = "payback-withdraw-coin-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.reserve_withdraw.reserve_reference = "payback-create-reserve-1",
      .details.reserve_withdraw.amount = "EUR:5" },
    { .oc = OC_REVOKE,
      .label = "revoke-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.revoke.ref = "payback-withdraw-coin-1" },
    { .oc = OC_PAYBACK,
      .label = "payback-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.payback.ref = "payback-withdraw-coin-1",
      .details.payback.amount = "EUR:5" },
    /* Check the money is back with the reserve */
    { .oc = OC_WITHDRAW_STATUS,
      .label = "payback-reserve-status-1",
      .expected_response_code = MHD_HTTP_OK,
      .details.reserve_status.reserve_reference = "payback-create-reserve-1",
      .details.reserve_status.expected_balance = "EUR:5.00" },
    /* Fill reserve with EUR:2.02, as withdraw fee is 1 ct per config,
       then withdraw two coin, partially spend one, and then have the rest paid back.
       Check deposit of other coin fails.
       (Do not use EUR:5 here as the EUR:5 coin was revoked and we did not
       bother to create a new one...) */
    { .oc = OC_ADMIN_ADD_INCOMING,
      .label = "payback-create-reserve-2",
      .expected_response_code = MHD_HTTP_OK,
      .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42}",
      .details.admin_add_incoming.transfer_details = "{ \"uuid\":5  }",
      .details.admin_add_incoming.amount = "EUR:2.02" },
    /* Withdraw a 1 EUR coin, at fee of 1 ct */
    { .oc = OC_WITHDRAW_SIGN,
      .label = "payback-withdraw-coin-2a",
      .expected_response_code = MHD_HTTP_OK,
      .details.reserve_withdraw.reserve_reference = "payback-create-reserve-2",
      .details.reserve_withdraw.amount = "EUR:1" },
    /* Withdraw a 1 EUR coin, at fee of 1 ct */
    { .oc = OC_WITHDRAW_SIGN,
      .label = "payback-withdraw-coin-2b",
      .expected_response_code = MHD_HTTP_OK,
      .details.reserve_withdraw.reserve_reference = "payback-create-reserve-2",
      .details.reserve_withdraw.amount = "EUR:1" },
    { .oc = OC_DEPOSIT,
      .label = "payback-deposit-partial",
      .expected_response_code = MHD_HTTP_OK,
      .details.deposit.amount = "EUR:0.5",
      .details.deposit.coin_ref = "payback-withdraw-coin-2a",
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42  }",
      .details.deposit.proposal_data = "{ \"items\": [ { \"name\":\"more ice cream\", \"value\":1 } ] }" },
    { .oc = OC_REVOKE,
      .label = "revoke-2",
      .expected_response_code = MHD_HTTP_OK,
      .details.revoke.ref = "payback-withdraw-coin-2a" },
    { .oc = OC_PAYBACK,
      .label = "payback-2",
      .expected_response_code = MHD_HTTP_OK,
      .details.payback.ref = "payback-withdraw-coin-2a",
      .details.payback.amount = "EUR:0.5" },
    { .oc = OC_PAYBACK,
      .label = "payback-2b",
      .expected_response_code = MHD_HTTP_FORBIDDEN,
      .details.payback.ref = "payback-withdraw-coin-2a",
      .details.payback.amount = "EUR:0.5" },
    { .oc = OC_DEPOSIT,
      .label = "payback-deposit-revoked",
      .expected_response_code = MHD_HTTP_NOT_FOUND,
      .details.deposit.amount = "EUR:1",
      .details.deposit.coin_ref = "payback-withdraw-coin-2b",
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42  }",
      .details.deposit.proposal_data = "{ \"items\": [ { \"name\":\"more ice cream\", \"value\":1 } ] }" },
    /* Test deposit fails after payback, with proof in payback */
    /* FIXME: #3887: right now, the exchange will never return the
       coin's transaction history with payback data, as we get a 404 on the DK! */
    { .oc = OC_DEPOSIT,
      .label = "payback-deposit-partial-after-payback",
      .expected_response_code = MHD_HTTP_NOT_FOUND,
      .details.deposit.amount = "EUR:0.5",
      .details.deposit.coin_ref = "payback-withdraw-coin-2a",
      .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42  }",
      .details.deposit.proposal_data = "{ \"items\": [ { \"name\":\"extra ice cream\", \"value\":1 } ] }" },
    /* Test that revoked coins cannot be withdrawn */
    { .oc = OC_ADMIN_ADD_INCOMING,
      .label = "payback-create-reserve-3",
      .expected_response_code = MHD_HTTP_OK,
      .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42}",
      .details.admin_add_incoming.transfer_details = "{ \"uuid\":6  }",
      .details.admin_add_incoming.amount = "EUR:1.01" },
    { .oc = OC_WITHDRAW_SIGN,
      .label = "payback-withdraw-coin-3-revoked",
      .expected_response_code = MHD_HTTP_NOT_FOUND,
      .details.reserve_withdraw.reserve_reference = "payback-create-reserve-3",
      .details.reserve_withdraw.amount = "EUR:1" },
    /* ************** End of payback API testing************* */
#endif
    { .oc = OC_END }
  };
  is = GNUNET_new (struct InterpreterState);
  is->commands = commands;
  ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
                          &rc);
  GNUNET_assert (NULL != ctx);
  rc = GNUNET_CURL_gnunet_rc_create (ctx);
  fakebank = TALER_FAKEBANK_start (8082);
  exchange = TALER_EXCHANGE_connect (ctx,
                                     "http://localhost:8081",
                                     &cert_cb, is,
                                     TALER_EXCHANGE_OPTION_END);
  GNUNET_assert (NULL != exchange);
  timeout_task
    = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
                                    (GNUNET_TIME_UNIT_SECONDS, 150),
                                    &do_timeout, NULL);
  GNUNET_SCHEDULER_add_shutdown (&do_shutdown, is);
}
/**
 * Remove files from previous runs
 */
static void
cleanup_files ()
{
  struct GNUNET_CONFIGURATION_Handle *cfg;
  char *dir;
  cfg = GNUNET_CONFIGURATION_create ();
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_load (cfg,
                                 "test_exchange_api.conf"))
  {
    GNUNET_break (0);
    GNUNET_CONFIGURATION_destroy (cfg);
    exit (77);
  }
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CONFIGURATION_get_value_filename (cfg,
                                                          "exchange",
                                                          "keydir",
                                                          &dir));
  if (GNUNET_YES ==
      GNUNET_DISK_directory_test (dir,
                                  GNUNET_NO))
    GNUNET_break (GNUNET_OK ==
                  GNUNET_DISK_directory_remove (dir));
  GNUNET_free (dir);
  GNUNET_CONFIGURATION_destroy (cfg);
}
/**
 * Main function for the testcase for the exchange API.
 *
 * @param argc expected to be 1
 * @param argv expected to only contain the program name
 */
int
main (int argc,
      char * const *argv)
{
  struct GNUNET_OS_Process *proc;
  struct GNUNET_SIGNAL_Context *shc_chld;
  enum GNUNET_OS_ProcessStatusType type;
  unsigned long code;
  unsigned int iter;
  /* These might get in the way... */
  unsetenv ("XDG_DATA_HOME");
  unsetenv ("XDG_CONFIG_HOME");
  GNUNET_log_setup ("test-exchange-api",
                    "INFO",
                    NULL);
  if (GNUNET_OK !=
      GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
				     8081))
  {
    fprintf (stderr,
             "Required port %u not available, skipping.\n",
	     8081);
    return 77;
  }
  if (GNUNET_OK !=
      GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
				     8082))
  {
    fprintf (stderr,
             "Required port %u not available, skipping.\n",
	     8082);
    return 77;
  }
  cleanup_files ();
  proc = GNUNET_OS_start_process (GNUNET_NO,
                                  GNUNET_OS_INHERIT_STD_ALL,
                                  NULL, NULL, NULL,
                                  "taler-exchange-keyup",
                                  "taler-exchange-keyup",
                                  "-c", "test_exchange_api.conf",
                                  NULL);
  if (NULL == proc)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
		"Failed to run `taler-exchange-keyup`, is your PATH correct?\n");
    return 77;
  }
  GNUNET_OS_process_wait (proc);
  GNUNET_OS_process_destroy (proc);
  proc = GNUNET_OS_start_process (GNUNET_NO,
                                  GNUNET_OS_INHERIT_STD_ALL,
                                  NULL, NULL, NULL,
                                  "taler-exchange-dbinit",
                                  "taler-exchange-dbinit",
                                  "-c", "test_exchange_api.conf",
                                  "-r",
                                  NULL);
  if (NULL == proc)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
		"Failed to run `taler-exchange-dbinit`, is your PATH correct?\n");
    return 77;
  }
  if (GNUNET_SYSERR ==
      GNUNET_OS_process_wait_status (proc,
                                     &type,
                                     &code))
  {
    GNUNET_break (0);
    GNUNET_OS_process_destroy (proc);
    return 1;
  }
  GNUNET_OS_process_destroy (proc);
  if ( (type == GNUNET_OS_PROCESS_EXITED) &&
       (0 != code) )
  {
    fprintf (stderr,
             "Failed to setup database\n");
    return 77;
  }
  if ( (type != GNUNET_OS_PROCESS_EXITED) ||
       (0 != code) )
  {
    fprintf (stderr,
             "Unexpected error running `taler-exchange-dbinit'!\n");
    return 1;
  }
  exchanged = GNUNET_OS_start_process (GNUNET_NO,
                                       GNUNET_OS_INHERIT_STD_ALL,
                                       NULL, NULL, NULL,
                                       "taler-exchange-httpd",
                                       "taler-exchange-httpd",
                                       "-c", "test_exchange_api.conf",
                                       "-i",
                                       NULL);
  /* give child time to start and bind against the socket */
  fprintf (stderr,
           "Waiting for `taler-exchange-httpd' to be ready");
  iter = 0;
  do
    {
      if (10 == iter)
      {
	fprintf (stderr,
		 "Failed to launch `taler-exchange-httpd' (or `wget')\n");
	GNUNET_OS_process_kill (exchanged,
				SIGTERM);
	GNUNET_OS_process_wait (exchanged);
	GNUNET_OS_process_destroy (exchanged);
	return 77;
      }
      fprintf (stderr, ".");
      sleep (1);
      iter++;
    }
  while (0 != system ("wget -q -t 1 -T 1 http://127.0.0.1:8081/keys -o /dev/null -O /dev/null"));
  fprintf (stderr, "\n");
  result = GNUNET_NO;
  sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_NO, GNUNET_NO);
  GNUNET_assert (NULL != sigpipe);
  shc_chld = GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD,
                                            &sighandler_child_death);
  GNUNET_SCHEDULER_run (&run, NULL);
  GNUNET_SIGNAL_handler_uninstall (shc_chld);
  shc_chld = NULL;
  GNUNET_DISK_pipe_close (sigpipe);
  GNUNET_OS_process_kill (exchanged,
                          SIGTERM);
  GNUNET_OS_process_wait (exchanged);
  GNUNET_OS_process_destroy (exchanged);
  return (GNUNET_OK == result) ? 0 : 1;
}
/* end of test_exchange_api.c */