/*
This file is part of TALER
Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
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'?
* Needs #3972 to be solved first.
*/
#define WIRE_SEPA 0
/**
* 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
};
/**
* 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 withdraw_sign 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_WithdrawStatusHandle *wsh;
/**
* Expected reserve balance.
*/
const char *expected_balance;
} withdraw_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_WithdrawSignHandle *wsh;
} withdraw_sign;
/**
* Information for a #OC_DEPOSIT command.
*/
struct
{
/**
* Amount to deposit.
*/
const char *amount;
/**
* Reference to a withdraw_sign 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;
} wire;
} 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_withdraw_sign_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.withdraw_sign.amount,
&amount));
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&amount_with_fee,
&amount,
&cmd->details.withdraw_sign.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 /withdraw/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
withdraw_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.withdraw_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.withdraw_status.reserve_reference,
rel->label) ) ) ||
( (NULL != rel->details.admin_add_incoming.reserve_reference) &&
(0 == strcmp (cmd->details.withdraw_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.withdraw_status.reserve_reference,
rel->details.withdraw_sign.reserve_reference))
{
if (GNUNET_OK !=
compare_withdraw_sign_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.withdraw_status.expected_balance)
{
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (cmd->details.withdraw_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 /withdraw/sign 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
withdraw_sign_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.withdraw_sign.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.withdraw_sign.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 */
fprintf (stderr,
"Got %u coins\n",
num_coins);
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;
}
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;
}
/**
* 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.withdraw_status.reserve_reference);
ref = find_command (is,
cmd->details.withdraw_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.withdraw_status.wsh
= TALER_MINT_withdraw_status (mint,
&reserve_pub,
&withdraw_status_cb,
is);
trigger_context_task ();
return;
case OC_WITHDRAW_SIGN:
GNUNET_assert (NULL !=
cmd->details.withdraw_sign.reserve_reference);
ref = find_command (is,
cmd->details.withdraw_sign.reserve_reference);
GNUNET_assert (NULL != ref);
GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
if (NULL != cmd->details.withdraw_sign.amount)
{
if (GNUNET_OK !=
TALER_string_to_amount (cmd->details.withdraw_sign.amount,
&amount))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to parse amount `%s' at %u\n",
cmd->details.withdraw_sign.amount,
is->ip);
fail (is);
return;
}
cmd->details.withdraw_sign.pk = find_pk (is->keys,
&amount);
}
if (NULL == cmd->details.withdraw_sign.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.withdraw_sign.coin_priv.eddsa_priv = *priv;
GNUNET_free (priv);
}
GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.withdraw_sign.coin_priv.eddsa_priv,
&coin_pub.eddsa_pub);
cmd->details.withdraw_sign.blinding_key.rsa_blinding_key
= GNUNET_CRYPTO_rsa_blinding_key_create (GNUNET_CRYPTO_rsa_public_key_len (cmd->details.withdraw_sign.pk->key.rsa_public_key));
cmd->details.withdraw_sign.wsh
= TALER_MINT_withdraw_sign (mint,
cmd->details.withdraw_sign.pk,
&ref->details.admin_add_incoming.reserve_priv,
&cmd->details.withdraw_sign.coin_priv,
&cmd->details.withdraw_sign.blinding_key,
&withdraw_sign_cb,
is);
if (NULL == cmd->details.withdraw_sign.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 timestamp;
struct TALER_MerchantPublicKeyP merchant_pub;
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.withdraw_sign.coin_priv;
coin_pk = ref->details.withdraw_sign.pk;
coin_pk_sig = &ref->details.withdraw_sign.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;
}
GNUNET_CRYPTO_hash (cmd->details.deposit.contract,
strlen (cmd->details.deposit.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\n",
cmd->details.deposit.wire_details,
is->ip);
fail (is);
return;
}
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
&coin_pub.eddsa_pub);
if (0 != cmd->details.deposit.refund_deadline.rel_value_us)
{
struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
priv = GNUNET_CRYPTO_eddsa_key_create ();
cmd->details.deposit.merchant_priv.eddsa_priv = *priv;
GNUNET_free (priv);
refund_deadline = GNUNET_TIME_relative_to_absolute (cmd->details.deposit.refund_deadline);
}
else
{
refund_deadline = GNUNET_TIME_UNIT_ZERO_ABS;
}
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,
&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;
}
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.withdraw_sign.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.withdraw_sign.sig;
melt_pks[i] = *ref->details.withdraw_sign.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.withdraw_sign.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 withdraw_sign 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.withdraw_sign.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;
default:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unknown instruction %d at %u (%s)\n",
cmd->oc,
is->ip,
cmd->label);
fail (is);
return;
}
is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
is);
}
/**
* 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.withdraw_status.wsh)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Command %u (%s) did not complete\n",
i,
cmd->label);
TALER_MINT_withdraw_status_cancel (cmd->details.withdraw_status.wsh);
cmd->details.withdraw_status.wsh = NULL;
}
break;
case OC_WITHDRAW_SIGN:
if (NULL != cmd->details.withdraw_sign.wsh)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Command %u (%s) did not complete\n",
i,
cmd->label);
TALER_MINT_withdraw_sign_cancel (cmd->details.withdraw_sign.wsh);
cmd->details.withdraw_sign.wsh = NULL;
}
if (NULL != cmd->details.withdraw_sign.sig.rsa_signature)
{
GNUNET_CRYPTO_rsa_signature_free (cmd->details.withdraw_sign.sig.rsa_signature);
cmd->details.withdraw_sign.sig.rsa_signature = NULL;
}
if (NULL != cmd->details.withdraw_sign.blinding_key.rsa_blinding_key)
{
GNUNET_CRYPTO_rsa_blinding_key_free (cmd->details.withdraw_sign.blinding_key.rsa_blinding_key);
cmd->details.withdraw_sign.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)
{
TALER_MINT_wire_cancel (cmd->details.wire.wh);
cmd->details.wire.wh = 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",
/* /wire/test replies with a 302 redirect */
.expected_response_code = MHD_HTTP_FOUND },
#endif
#if WIRE_SEPA
{ .oc = OC_WIRE,
.label = "wire",
/* /wire/sepa replies with a 200 redirect */
.expected_response_code = MHD_HTTP_OK },
#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.withdraw_sign.reserve_reference = "create-reserve-1",
.details.withdraw_sign.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.withdraw_status.reserve_reference = "create-reserve-1",
.details.withdraw_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.withdraw_sign.reserve_reference = "create-reserve-1",
.details.withdraw_sign.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.withdraw_sign.reserve_reference = "refresh-create-reserve-1",
.details.withdraw_sign.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 ************** */
#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 */