/*
  This file is part of TALER
  Copyright (C) 2014, 2015, 2016 GNUnet e.V.
  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, If not, see 
*/
/**
 * @file mint/test_mint_api.c
 * @brief testcase to test mint's HTTP API interface
 * @author Sree Harsha Totakura 
 * @author Christian Grothoff
 */
#include "platform.h"
#include "taler_util.h"
#include "taler_signatures.h"
#include "taler_mint_service.h"
#include 
#include 
/**
 * 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 TALER_MINT_Context *ctx;
/**
 * Handle to access the mint.
 */
static struct TALER_MINT_Handle *mint;
/**
 * Task run on shutdown.
 */
static struct GNUNET_SCHEDULER_Task *shutdown_task;
/**
 * Task that runs the main event loop.
 */
static struct GNUNET_SCHEDULER_Task *ctx_task;
/**
 * 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 mint's /refresh/link by linking original private key to
   * results from #OC_REFRESH_REVEAL step.
   */
  OC_REFRESH_LINK,
  /**
   * Verify the mint's /wire-method.
   */
  OC_WIRE,
  /**
   * Verify mint's /wire/deposits method.
   */
  OC_WIRE_DEPOSITS,
  /**
   * Verify mint's /deposit/wtid method.
   */
  OC_DEPOSIT_WTID
};
/**
 * 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_MINT_DenomPublicKey *pk;
  /**
   * Set (by the interpreter) to the mint'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 mint 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;
      /**
       * Wire details (JSON).
       */
      const char *wire;
      /**
       * 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_MINT_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_MINT_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 mint'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_MINT_DenomPublicKey *pk;
      /**
       * Set (by the interpreter) to the mint'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_DenominationBlindingKey blinding_key;
      /**
       * Withdraw handle (while operation is running).
       */
      struct TALER_MINT_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 the contract between the two parties.
       */
      const char *contract;
      /**
       * Transaction ID to use.
       */
      uint64_t transaction_id;
      /**
       * 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_MINT_DepositHandle *dh;
    } deposit;
    /**
     * Information for a #OC_REFRESH_MELT command.
     */
    struct
    {
      /**
       * Information about coins to be melted.
       */
      struct MeltDetails *melted_coins;
      /**
       * 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_MINT_DenomPublicKey **fresh_pks;
      /**
       * Melt handle while operation is running.
       */
      struct TALER_MINT_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 mint.
       */
      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_MINT_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_MINT_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_MINT_WireHandle *wh;
      /**
       * Format we expect to see, others will be *ignored*.
       */
      const char *format;
    } wire;
    /**
     * Information for the /wire/deposits's command.
     */
    struct {
      /**
       * Handle to the wire deposits request.
       */
      struct TALER_MINT_WireDepositsHandle *wdh;
      /**
       * Reference to a /deposit/wtid command. If set, we use the
       * WTID from that command.
       */
      const char *wtid_ref;
      /**
       * WTID to use (used if @e wtid_ref is NULL).
       */
      struct TALER_WireTransferIdentifierRawP wtid;
      /* TODO: may want to add list of deposits we expected
         to see aggregated here in the future. */
    } wire_deposits;
    /**
     * Information for the /deposit/wtid command.
     */
    struct {
      /**
       * Handle to the deposit wtid request.
       */
      struct TALER_MINT_DepositWtidHandle *dwh;
      /**
       * Which /deposit operation should we obtain WTID data for?
       */
      const char *deposit_ref;
      /**
       * What is the expected total amount? Only used if
       * @e expected_response_code was #MHD_HTTP_OK.
       */
      struct TALER_Amount total_amount_expected;
      /**
       * Wire transfer identifier, set if #MHD_HTTP_OK was the response code.
       */
      struct TALER_WireTransferIdentifierRawP wtid;
    } deposit_wtid;
  } details;
};
/**
 * State of the interpreter loop.
 */
struct InterpreterState
{
  /**
   * Keys from the mint.
   */
  const struct TALER_MINT_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;
};
/**
 * Task that runs the context's event loop with the GNUnet scheduler.
 *
 * @param cls unused
 * @param tc scheduler context (unused)
 */
static void
context_task (void *cls,
              const struct GNUNET_SCHEDULER_TaskContext *tc);
/**
 * Run the context task, the working set has changed.
 */
static void
trigger_context_task ()
{
  GNUNET_SCHEDULER_cancel (ctx_task);
  ctx_task = GNUNET_SCHEDULER_add_now (&context_task,
                                       NULL);
}
/**
 * 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 mint operations.
 *
 * @param cls contains the `struct InterpreterState`
 * @param tc scheduler context
 */
static void
interpreter_run (void *cls,
                 const struct GNUNET_SCHEDULER_TaskContext *tc);
/**
 * 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 mint's reply is bogus (fails to follow the protocol)
 * @param full_response full response from the mint (for logging, in case of errors)
 */
static void
add_incoming_cb (void *cls,
                 unsigned int http_status,
                 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;
  }
  is->ip++;
  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                       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_MINT_ReserveHistory *h,
                                    const struct Command *cmd)
{
  struct TALER_Amount amount;
  if (TALER_MINT_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_MINT_ReserveHistory *h,
                                  const struct Command *cmd)
{
  struct TALER_Amount amount;
  struct TALER_Amount amount_with_fee;
  if (TALER_MINT_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;
}
/**
 * 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 mint's reply is bogus (fails to follow the protocol)
 * @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,
                   json_t *json,
                   const struct TALER_Amount *balance,
                   unsigned int history_length,
                   const struct TALER_MINT_ReserveHistory *history)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  struct Command *rel;
  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 (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 (GNUNET_OK !=
              compare_reserve_withdraw_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;
  }
  is->ip++;
  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                       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 mint's reply is bogus (fails to follow the protocol)
 * @param sig signature over the coin, NULL on error
 * @param full_response full response from the mint (for logging, in case of errors)
 */
static void
reserve_withdraw_cb (void *cls,
                     unsigned int http_status,
                     const struct TALER_DenominationSignature *sig,
                     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_PAYMENT_REQUIRED:
    /* nothing to check */
    break;
  default:
    /* Unsupported status code (by test harness) */
    GNUNET_break (0);
    break;
  }
  is->ip++;
  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                       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 mint's reply is bogus (fails to follow the protocol)
 * @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,
            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;
  }
  is->ip++;
  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                       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 mint's reply is bogus (fails to follow the protocol)
 * @param noreveal_index choice by the mint in the cut-and-choose protocol,
 *                    UINT16_MAX on error
 * @param full_response full response from the mint (for logging, in case of errors)
 */
static void
melt_cb (void *cls,
         unsigned int http_status,
         uint16_t noreveal_index,
         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;
  is->ip++;
  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                       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 mint's reply is bogus (fails to follow the protocol)
 * @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 mint (for logging, in case of errors)
 */
static void
reveal_cb (void *cls,
           unsigned int http_status,
           unsigned int num_coins,
           const struct TALER_CoinSpendPrivateKeyP *coin_privs,
           const struct TALER_DenominationSignature *sigs,
           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);
  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;
  }
  is->ip++;
  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                       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 mint's reply is bogus (fails to follow the protocol)
 * @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 mint (for logging, in case of errors)
 */
static void
link_cb (void *cls,
         unsigned int http_status,
         unsigned int num_coins,
         const struct TALER_CoinSpendPrivateKeyP *coin_privs,
         const struct TALER_DenominationSignature *sigs,
         const struct TALER_DenominationPublicKey *pubs,
         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);
  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;
  }
  is->ip++;
  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                       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_MINT_DenomPublicKey *
find_pk (const struct TALER_MINT_Keys *keys,
         const struct TALER_Amount *amount)
{
  unsigned int i;
  struct GNUNET_TIME_Absolute now;
  struct TALER_MINT_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,
                  now.abs_value_us,
                  pk->valid_from.abs_value_us,
                  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;
}
/**
 * Callbacks called with the result(s) of a
 * wire format inquiry request to the mint.
 *
 * The callback is invoked multiple times, once for each supported @a
 * method.  Finally, it is invoked one more time with cls/0/NULL/NULL
 * to indicate the end of the iteration.  If any request fails to
 * generate a valid response from the mint, @a http_status will also
 * be zero and the iteration will also end.  Thus, the iteration
 * always ends with a final call with an @a http_status of 0. If the
 * @a http_status is already 0 on the first call, then the response to
 * the /wire request was invalid.  Later, clients can tell the
 * difference between @a http_status of 0 indicating a failed
 * /wire/method request and a regular end of the iteration by @a
 * method being non-NULL.  If the mint simply correctly asserts that
 * it does not support any methods, @a method will be NULL but the @a
 * http_status will be #MHD_HTTP_OK for the first call (followed by a
 * cls/0/NULL/NULL call to signal the end of the iteration).
 *
 * @param cls closure with the interpreter state
 * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful request;
 *                    0 if the mint's reply is bogus (fails to follow the protocol)
 * @param method wire format method supported, i.e. "test" or "sepa", or NULL
 *            if already the /wire request failed.
 * @param obj the received JSON reply, if successful this should be the wire
 *            format details as provided by /wire/METHOD/, or NULL if the
 *            reply was not in JSON format (in this case, the client might
 *            want to do an HTTP request to /wire/METHOD/ with a browser to
 *            provide more information to the user about the @a method).
 */
static void
wire_cb (void *cls,
         unsigned int http_status,
         const char *method,
         json_t *obj)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  if (0 == http_status)
  {
    /* 0 always signals the end of the iteration */
    cmd->details.wire.wh = NULL;
  }
  else if ( (NULL != method) &&
            (0 != strcasecmp (method,
                              cmd->details.wire.format)) )
  {
    /* not the method we care about, skip */
    return;
  }
  if (cmd->expected_response_code != http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u to command %s/%s\n",
                http_status,
                cmd->label,
                method);
    json_dumpf (obj, stderr, 0);
    fail (is);
    return;
  }
  if (0 == http_status)
  {
    /* end of iteration, move to next command */
    is->ip++;
    is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                         is);
    return;
  }
  /* For now, we only support to be called only once
     with a "positive" result; so we switch to an
     expected value of 0 for the 2nd iteration */
  cmd->expected_response_code = 0;
}
/**
 * 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 mint protocol violation
 * @param json original json reply (may include signatures, those have then been
 *        validated already)
 * @param wtid extracted wire transfer identifier, or NULL if the mint could
 *             not provide any (set only if @a http_status is #MHD_HTTP_OK)
 * @param total_amount total amount of the wire transfer, or NULL if the mint could
 *             not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK)
 * @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,
                  json_t *json,
                  const struct GNUNET_HashCode *h_wire,
                  const struct TALER_Amount *total_amount,
                  unsigned int details_length,
                  const struct TALER_WireDepositDetails *details)
{
  struct InterpreterState *is = cls;
  struct Command *cmd = &is->commands[is->ip];
  const struct Command *ref;
  cmd->details.wire_deposits.wdh = NULL;
  ref = find_command (is,
                      cmd->details.wire_deposits.wtid_ref);
  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 (0 != TALER_amount_cmp (total_amount,
                               &ref->details.deposit_wtid.total_amount_expected))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Total amount missmatch to command %s\n",
                  http_status,
                  cmd->label);
      json_dumpf (json, stderr, 0);
      fail (is);
      return;
    }
    if (NULL != ref->details.deposit_wtid.deposit_ref)
    {
      const struct Command *dep;
      struct GNUNET_HashCode hw;
      dep = find_command (is,
                          ref->details.deposit_wtid.deposit_ref);
      GNUNET_CRYPTO_hash (dep->details.deposit.wire_details,
                          strlen (dep->details.deposit.wire_details),
                          &hw);
      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;
  default:
    break;
  }
  /* move to next command */
  is->ip++;
  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                       is);
}
/**
 * Function called with detailed wire transfer data.
 *
 * @param cls closure
 * @param http_status HTTP status code we got, 0 on mint protocol violation
 * @param json original json reply (may include signatures, those have then been
 *        validated already)
 * @param wtid wire transfer identifier used by the mint, NULL if mint 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 mint 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,
                 json_t *json,
                 const struct TALER_WireTransferIdentifierRawP *wtid,
                 struct GNUNET_TIME_Absolute execution_time,
                 const struct TALER_Amount *coin_contribution,
                 const struct TALER_Amount *total_amount)
{
  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 (0 != TALER_amount_cmp (total_amount,
                               &cmd->details.deposit_wtid.total_amount_expected))
    {
      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:
    break;
  }
  /* move to next command */
  is->ip++;
  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
                                       is);
}
/**
 * Run the main interpreter loop that performs mint operations.
 *
 * @param cls contains the `struct InterpreterState`
 * @param tc scheduler context
 */
static void
interpreter_run (void *cls,
                 const struct GNUNET_SCHEDULER_TaskContext *tc)
{
  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 *wire;
  is->task = NULL;
  if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
  {
    fprintf (stderr,
             "Test aborted by shutdown request\n");
    fail (is);
    return;
  }
  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;
    }
    wire = json_loads (cmd->details.admin_add_incoming.wire,
                       JSON_REJECT_DUPLICATES,
                       NULL);
    if (NULL == wire)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Failed to parse wire details `%s' at %u\n",
                  cmd->details.admin_add_incoming.wire,
                  is->ip);
      fail (is);
      return;
    }
    execution_date = GNUNET_TIME_absolute_get ();
    TALER_round_abs_time (&execution_date);
    cmd->details.admin_add_incoming.aih
      = TALER_MINT_admin_add_incoming (mint,
                                       &reserve_pub,
                                       &amount,
                                       execution_date,
                                       wire,
                                       &add_incoming_cb,
                                       is);
    if (NULL == cmd->details.admin_add_incoming.aih)
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    trigger_context_task ();
    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_MINT_reserve_status (mint,
                                   &reserve_pub,
                                   &reserve_status_cb,
                                   is);
    trigger_context_task ();
    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);
    cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key
      = GNUNET_CRYPTO_rsa_blinding_key_create (GNUNET_CRYPTO_rsa_public_key_len (cmd->details.reserve_withdraw.pk->key.rsa_public_key));
    cmd->details.reserve_withdraw.wsh
      = TALER_MINT_reserve_withdraw (mint,
                                     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;
    }
    trigger_context_task ();
    return;
  case OC_DEPOSIT:
    {
      struct GNUNET_HashCode h_contract;
      const struct TALER_CoinSpendPrivateKeyP *coin_priv;
      const struct TALER_MINT_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 *contract;
      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;
      }
      contract = json_loads (cmd->details.deposit.contract,
                             JSON_REJECT_DUPLICATES,
                             NULL);
      if (NULL == contract)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Failed to parse contract details `%s' at %u/%s\n",
                    cmd->details.deposit.contract,
                    is->ip,
                    cmd->label);
        fail (is);
        return;
      }
      TALER_hash_json (contract,
                       &h_contract);
      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);
      }
      else
      {
        refund_deadline = GNUNET_TIME_UNIT_ZERO_ABS;
      }
      GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.deposit.merchant_priv.eddsa_priv,
                                          &merchant_pub.eddsa_pub);
      wire_deadline = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS);
      timestamp = GNUNET_TIME_absolute_get ();
      TALER_round_abs_time (×tamp);
      {
        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_contract = h_contract;
        TALER_hash_json (wire,
                         &dr.h_wire);
        dr.timestamp = GNUNET_TIME_absolute_hton (timestamp);
        dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
        dr.transaction_id = GNUNET_htonll (cmd->details.deposit.transaction_id);
        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_MINT_deposit (mint,
                              &amount,
                              wire_deadline,
                              wire,
                              &h_contract,
                              &coin_pub,
                              coin_pk_sig,
                              &coin_pk->key,
                              timestamp,
                              cmd->details.deposit.transaction_id,
                              &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);
      trigger_context_task ();
      return;
    }
  case OC_REFRESH_MELT:
    {
      unsigned int num_melted_coins;
      unsigned int num_fresh_coins;
      cmd->details.refresh_melt.noreveal_index = UINT16_MAX;
      for (num_melted_coins=0;
           NULL != cmd->details.refresh_melt.melted_coins[num_melted_coins].amount;
           num_melted_coins++) ;
      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_MINT_DenomPublicKey *);
      {
        struct TALER_CoinSpendPrivateKeyP melt_privs[num_melted_coins];
        struct TALER_Amount melt_amounts[num_melted_coins];
        struct TALER_DenominationSignature melt_sigs[num_melted_coins];
        struct TALER_MINT_DenomPublicKey melt_pks[num_melted_coins];
        struct TALER_MINT_DenomPublicKey fresh_pks[num_fresh_coins];
        unsigned int i;
        for (i=0;idetails.refresh_melt.melted_coins[i];
          ref = find_command (is,
                              md->coin_ref);
          GNUNET_assert (NULL != ref);
          GNUNET_assert (OC_WITHDRAW_SIGN == ref->oc);
          melt_privs[i] = ref->details.reserve_withdraw.coin_priv;
          if (GNUNET_OK !=
              TALER_string_to_amount (md->amount,
                                      &melt_amounts[i]))
          {
            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                        "Failed to parse amount `%s' at %u\n",
                        md->amount,
                        is->ip);
            fail (is);
            return;
          }
          melt_sigs[i] = ref->details.reserve_withdraw.sig;
          melt_pks[i] = *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_MINT_refresh_prepare (num_melted_coins,
                                        melt_privs,
                                        melt_amounts,
                                        melt_sigs,
                                        melt_pks,
                                        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_MINT_refresh_melt (mint,
                                     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;
        }
      }
    }
    trigger_context_task ();
    return;
  case OC_REFRESH_REVEAL:
    ref = find_command (is,
                        cmd->details.refresh_reveal.melt_ref);
    cmd->details.refresh_reveal.rrh
      = TALER_MINT_refresh_reveal (mint,
                                   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;
    }
    trigger_context_task ();
    return;
  case OC_REFRESH_LINK:
    /* find reveal command */
    ref = find_command (is,
                        cmd->details.refresh_link.reveal_ref);
    /* find melt command */
    ref = find_command (is,
                        ref->details.refresh_reveal.melt_ref);
    /* find reserve_withdraw command */
    {
      unsigned int idx;
      const struct MeltDetails *md;
      unsigned int num_melted_coins;
      for (num_melted_coins=0;
           NULL != ref->details.refresh_melt.melted_coins[num_melted_coins].amount;
           num_melted_coins++) ;
      idx = cmd->details.refresh_link.coin_idx;
      GNUNET_assert (idx < num_melted_coins);
      md = &ref->details.refresh_melt.melted_coins[idx];
      ref = find_command (is,
                          md->coin_ref);
    }
    GNUNET_assert (OC_WITHDRAW_SIGN == ref->oc);
    /* finally, use private key from withdraw sign command */
    cmd->details.refresh_link.rlh
      = TALER_MINT_refresh_link (mint,
                                 &ref->details.reserve_withdraw.coin_priv,
                                 &link_cb,
                                 is);
    if (NULL == cmd->details.refresh_link.rlh)
    {
      GNUNET_break (0);
      fail (is);
      return;
    }
    trigger_context_task ();
    return;
  case OC_WIRE:
    cmd->details.wire.wh = TALER_MINT_wire (mint,
                                            &wire_cb,
                                            is);
    trigger_context_task ();
    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);
      cmd->details.wire_deposits.wtid = ref->details.deposit_wtid.wtid;
    }
    cmd->details.wire_deposits.wdh
      = TALER_MINT_wire_deposits (mint,
                                  &cmd->details.wire_deposits.wtid,
                                  &wire_deposits_cb,
                                  is);
    trigger_context_task ();
    return;
  case OC_DEPOSIT_WTID:
    {
      struct GNUNET_HashCode h_wire;
      struct GNUNET_HashCode h_contract;
      json_t *wire;
      json_t *contract;
      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);
      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;
          unsigned int idx;
          idx = ref->details.deposit.coin_idx;
          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);
      }
      wire = json_loads (ref->details.deposit.wire_details,
                         JSON_REJECT_DUPLICATES,
                         NULL);
      GNUNET_assert (NULL != wire);
      TALER_hash_json (wire,
                       &h_wire);
      json_decref (wire);
      contract = json_loads (ref->details.deposit.contract,
                             JSON_REJECT_DUPLICATES,
                             NULL);
      GNUNET_assert (NULL != contract);
      TALER_hash_json (contract,
                       &h_contract);
      json_decref (contract);
      cmd->details.deposit_wtid.dwh
        = TALER_MINT_deposit_wtid (mint,
                                   &ref->details.deposit.merchant_priv,
                                   &h_wire,
                                   &h_contract,
                                   &coin_pub,
                                   ref->details.deposit.transaction_id,
                                   &deposit_wtid_cb,
                                   is);
      trigger_context_task ();
    }
    return;
  default:
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unknown instruction %d at %u (%s)\n",
                cmd->oc,
                is->ip,
                cmd->label);
    fail (is);
    return;
  }
}
/**
 * Function run when the test terminates (good or bad).
 * Cleans up our state.
 *
 * @param cls the interpreter state.
 * @param tc unused
 */
static void
do_shutdown (void *cls,
             const struct GNUNET_SCHEDULER_TaskContext *tc)
{
  struct InterpreterState *is = cls;
  struct Command *cmd;
  unsigned int i;
  shutdown_task = NULL;
  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_MINT_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_MINT_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_MINT_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;
      }
      if (NULL != cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key)
      {
        GNUNET_CRYPTO_rsa_blinding_key_free (cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key);
        cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key = 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_MINT_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_MINT_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_MINT_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_MINT_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_MINT_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_MINT_wire_deposits_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_MINT_deposit_wtid_cancel (cmd->details.deposit_wtid.dwh);
        cmd->details.deposit_wtid.dwh = 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 != ctx_task)
  {
    GNUNET_SCHEDULER_cancel (ctx_task);
    ctx_task = NULL;
  }
  if (NULL != mint)
  {
    TALER_MINT_disconnect (mint);
    mint = NULL;
  }
  if (NULL != ctx)
  {
    TALER_MINT_fini (ctx);
    ctx = NULL;
  }
}
/**
 * Functions of this type are called to provide the retrieved signing and
 * denomination keys of the mint.  No TALER_MINT_*() functions should be called
 * in this callback.
 *
 * @param cls closure
 * @param keys information about keys of the mint
 */
static void
cert_cb (void *cls,
         const struct TALER_MINT_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);
}
/**
 * Task that runs the context's event loop with the GNUnet scheduler.
 *
 * @param cls unused
 * @param tc scheduler context (unused)
 */
static void
context_task (void *cls,
              const struct GNUNET_SCHEDULER_TaskContext *tc)
{
  long timeout;
  int max_fd;
  fd_set read_fd_set;
  fd_set write_fd_set;
  fd_set except_fd_set;
  struct GNUNET_NETWORK_FDSet *rs;
  struct GNUNET_NETWORK_FDSet *ws;
  struct GNUNET_TIME_Relative delay;
  ctx_task = NULL;
  TALER_MINT_perform (ctx);
  max_fd = -1;
  timeout = -1;
  FD_ZERO (&read_fd_set);
  FD_ZERO (&write_fd_set);
  FD_ZERO (&except_fd_set);
  TALER_MINT_get_select_info (ctx,
                              &read_fd_set,
                              &write_fd_set,
                              &except_fd_set,
                              &max_fd,
                              &timeout);
  if (timeout >= 0)
    delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
                                           timeout);
  else
    delay = GNUNET_TIME_UNIT_FOREVER_REL;
  rs = GNUNET_NETWORK_fdset_create ();
  GNUNET_NETWORK_fdset_copy_native (rs,
                                    &read_fd_set,
                                    max_fd + 1);
  ws = GNUNET_NETWORK_fdset_create ();
  GNUNET_NETWORK_fdset_copy_native (ws,
                                    &write_fd_set,
                                    max_fd + 1);
  ctx_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
                                          delay,
                                          rs,
                                          ws,
                                          &context_task,
                                          cls);
  GNUNET_NETWORK_fdset_destroy (rs);
  GNUNET_NETWORK_fdset_destroy (ws);
}
/**
 * Main function that will be run by the scheduler.
 *
 * @param cls closure
 * @param args remaining command-line arguments
 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
 * @param config configuration
 */
static void
run (void *cls,
     const struct GNUNET_SCHEDULER_TaskContext *tc)
{
  struct InterpreterState *is;
  static struct MeltDetails melt_coins_1[] = {
    { .amount = "EUR:4",
      .coin_ref = "refresh-withdraw-coin-1" },
    { NULL, NULL }
  };
  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",
      /* /wire/test replies with a 302 redirect */
      .expected_response_code = MHD_HTTP_FOUND,
      .details.wire.format = "test" },
#endif
#if WIRE_SEPA
    { .oc = OC_WIRE,
      .label = "wire-sepa",
      /* /wire/sepa replies with a 200 redirect */
      .expected_response_code = MHD_HTTP_OK,
      .details.wire.format = "sepa" },
#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.wire = "{ \"type\":\"TEST\", \"bank\":\"source bank\", \"account\":42 }",
      .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\":\"dest bank\", \"account\":42 }",
      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }",
      .details.deposit.transaction_id = 1 },
    /* Try to overdraw funds ... */
    { .oc = OC_WITHDRAW_SIGN,
      .label = "withdraw-coin-2",
      .expected_response_code = MHD_HTTP_PAYMENT_REQUIRED,
      .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\":\"dest bank\", \"account\":43 }",
      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }",
      .details.deposit.transaction_id = 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\":\"dest bank\", \"account\":42 }",
      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }",
      .details.deposit.transaction_id = 2 },
    /* Try to double-spend the 5 EUR coin at the same merchant (but different
       contract) */
    { .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\":\"dest bank\", \"account\":42 }",
      .details.deposit.contract = "{ \"items\":[{ \"name\":\"ice cream\", \"value\":2 } ] }",
      .details.deposit.transaction_id = 1 },
    /* ***************** /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.wire = "{ \"type\":\"TEST\", \"bank\":\"source bank\", \"account\":424 }",
      .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\":\"dest bank\", \"account\":42 }",
      .details.deposit.contract = "{ \"items\" : [ { \"name\":\"ice cream\", \"value\":\"EUR:1\" } ] }",
      .details.deposit.transaction_id = 42421 },
    /* 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_coins = melt_coins_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" },
    /* 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",
      .details.deposit.coin_idx = 0,
      .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }",
      .details.deposit.transaction_id = 2 },
    /* 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\":\"dest bank\", \"account\":42 }",
      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }",
      .details.deposit.transaction_id = 2 },
    /* 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_coins = melt_coins_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 mint 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 mint 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 mint didn't use that
       WTID value for any transaction. */
    { .oc = OC_WIRE_DEPOSITS,
      .label = "wire-deposit-failing",
      .expected_response_code = MHD_HTTP_NOT_FOUND },
    /* TODO: trigger aggregation logic and then check the
       cases where tracking succeeds! */
    /* ************** End of tracking API testing************* */
#endif
    { .oc = OC_END }
  };
  is = GNUNET_new (struct InterpreterState);
  is->commands = commands;
  ctx = TALER_MINT_init ();
  GNUNET_assert (NULL != ctx);
  ctx_task = GNUNET_SCHEDULER_add_now (&context_task,
                                       ctx);
  mint = TALER_MINT_connect (ctx,
                             "http://localhost:8081",
                             &cert_cb, is,
                             TALER_MINT_OPTION_END);
  GNUNET_assert (NULL != mint);
  shutdown_task
    = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
                                    (GNUNET_TIME_UNIT_SECONDS, 150),
                                    &do_shutdown, is);
}
/**
 * Main function for the testcase for the mint 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_OS_Process *mintd;
  GNUNET_log_setup ("test-mint-api",
                    "WARNING",
                    NULL);
  proc = GNUNET_OS_start_process (GNUNET_NO,
                                  GNUNET_OS_INHERIT_STD_ALL,
                                  NULL, NULL, NULL,
                                  "taler-mint-keyup",
                                  "taler-mint-keyup",
                                  "-d", "test-mint-home",
                                  "-m", "test-mint-home/master.priv",
                                  NULL);
  GNUNET_OS_process_wait (proc);
  GNUNET_OS_process_destroy (proc);
  mintd = GNUNET_OS_start_process (GNUNET_NO,
                                   GNUNET_OS_INHERIT_STD_ALL,
                                   NULL, NULL, NULL,
                                   "taler-mint-httpd",
                                   "taler-mint-httpd",
                                   "-d", "test-mint-home",
                                   NULL);
  /* give child time to start and bind against the socket */
  fprintf (stderr, "Waiting for taler-mint-httpd to be ready");
  do
    {
      fprintf (stderr, ".");
      sleep (1);
    }
  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_SYSERR;
  GNUNET_SCHEDULER_run (&run, NULL);
  GNUNET_OS_process_kill (mintd,
                          SIGTERM);
  GNUNET_OS_process_wait (mintd);
  GNUNET_OS_process_destroy (mintd);
  return (GNUNET_OK == result) ? 0 : 1;
}
/* end of test_mint_api.c */