From ae45b7ac9579cc9fcb9c3008162e07b694eb52f8 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 29 Jan 2016 14:24:18 +0100 Subject: fix testcase --- src/bank-lib/Makefile.am | 12 + src/bank-lib/bank_api_context.c | 2 +- src/bank-lib/test_bank_api.c | 2184 ++------------------------------------- 3 files changed, 71 insertions(+), 2127 deletions(-) (limited to 'src/bank-lib') diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am index 326fdd5af..2f44adada 100644 --- a/src/bank-lib/Makefile.am +++ b/src/bank-lib/Makefile.am @@ -31,3 +31,15 @@ libtalerbank_la_LIBADD += -lgnurl endif endif +check_PROGRAMS = \ + test_bank_api + +TESTS = \ + $(check_PROGRAMS) + +test_bank_api_SOURCES = \ + test_bank_api.c +test_bank_api_LDADD = \ + libtalerbank.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil diff --git a/src/bank-lib/bank_api_context.c b/src/bank-lib/bank_api_context.c index f54e9e703..a47b4072a 100644 --- a/src/bank-lib/bank_api_context.c +++ b/src/bank-lib/bank_api_context.c @@ -395,7 +395,7 @@ TALER_BANK_fini (struct TALER_BANK_Context *ctx) * @return the full URI to use with cURL */ char * -MAH_path_to_url (struct TALER_BANK_Context *h, +BAC_path_to_url (struct TALER_BANK_Context *h, const char *path) { char *url; diff --git a/src/bank-lib/test_bank_api.c b/src/bank-lib/test_bank_api.c index 3c1747a1f..b14f523ba 100644 --- a/src/bank-lib/test_bank_api.c +++ b/src/bank-lib/test_bank_api.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015, 2016 GNUnet e.V. + Copyright (C) 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 @@ -16,7 +16,6 @@ /** * @file bank/test_bank_api.c * @brief testcase to test bank's HTTP API interface - * @author Sree Harsha Totakura * @author Christian Grothoff */ #include "platform.h" @@ -26,26 +25,12 @@ #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_BANK_Context *ctx; -/** - * Handle to access the bank. - */ -static struct TALER_BANK_Handle *bank; - /** * Task run on shutdown. */ @@ -75,102 +60,7 @@ enum OpCode /** * 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 bank's /refresh/link by linking original private key to - * results from #OC_REFRESH_REVEAL step. - */ - OC_REFRESH_LINK, - - /** - * Verify the bank's /wire-method. - */ - OC_WIRE, - - /** - * Verify bank's /wire/deposits method. - */ - OC_WIRE_DEPOSITS, - - /** - * Verify bank'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_BANK_DenomPublicKey *pk; - - /** - * Set (by the interpreter) to the bank'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; + OC_ADMIN_ADD_INCOMING }; @@ -207,28 +97,21 @@ struct 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). + * Account number. */ - const char *wire; + uint64_t account_no; /** - * Set (by the interpreter) to the reserve's private key - * we used to fill the reserve. + * Wire transfer identifier to use. Initialized to + * a random value. */ - struct TALER_ReservePrivateKeyP reserve_priv; + struct TALER_WireTransferIdentifierRawP wtid; /** * Set to the API's handle during the operation. @@ -237,303 +120,6 @@ struct Command } 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_BANK_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 bank'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_BANK_DenomPublicKey *pk; - - /** - * Set (by the interpreter) to the bank'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_BANK_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_BANK_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_BANK_DenomPublicKey **fresh_pks; - - /** - * Melt handle while operation is running. - */ - struct TALER_BANK_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 bank. - */ - 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_BANK_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_BANK_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_BANK_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_BANK_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_BANK_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; }; @@ -604,6 +190,7 @@ fail (struct InterpreterState *is) } +#if 0 /** * Find a command by label. * @@ -622,813 +209,53 @@ find_command (const struct InterpreterState *is, { 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 bank 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 bank's reply is bogus (fails to follow the protocol) - * @param full_response full response from the bank (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_BANK_ReserveHistory *h, - const struct Command *cmd) -{ - struct TALER_Amount amount; - - if (TALER_BANK_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_BANK_ReserveHistory *h, - const struct Command *cmd) -{ - struct TALER_Amount amount; - struct TALER_Amount amount_with_fee; - - if (TALER_BANK_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 bank'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_BANK_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 bank'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 bank (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 bank'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 bank's reply is bogus (fails to follow the protocol) - * @param noreveal_index choice by the bank in the cut-and-choose protocol, - * UINT16_MAX on error - * @param full_response full response from the bank (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 bank'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 bank (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 bank'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 bank (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_BANK_DenomPublicKey * -find_pk (const struct TALER_BANK_Keys *keys, - const struct TALER_Amount *amount) -{ - unsigned int i; - struct GNUNET_TIME_Absolute now; - struct TALER_BANK_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 bank. - * - * 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 bank, @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 bank 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 bank'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 bank 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 bank 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 bank 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; + return NULL; } - - /* move to next command */ - is->ip++; - is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, - is); + 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; } +#endif + + +/** + * Run the main interpreter loop that performs bank 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 with detailed wire transfer data. + * Function called upon completion of our /admin/add/incoming request. * - * @param cls closure - * @param http_status HTTP status code we got, 0 on bank protocol violation - * @param json original json reply (may include signatures, those have then been - * validated already) - * @param wtid wire transfer identifier used by the bank, NULL if bank 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 bank could - * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + * 0 if the bank's reply is bogus (fails to follow the protocol) */ 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) +add_incoming_cb (void *cls, + unsigned int http_status) { struct InterpreterState *is = cls; struct Command *cmd = &is->commands[is->ip]; - cmd->details.deposit_wtid.dwh = NULL; + cmd->details.admin_add_incoming.aih = 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); + GNUNET_break (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); @@ -1447,12 +274,7 @@ interpreter_run (void *cls, { struct InterpreterState *is = cls; struct Command *cmd = &is->commands[is->ip]; - const struct Command *ref; - struct TALER_ReservePublicKeyP reserve_pub; - struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_Amount amount; - struct GNUNET_TIME_Absolute execution_date; - json_t *wire; is->task = NULL; if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) @@ -1469,26 +291,7 @@ interpreter_run (void *cls, 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)) @@ -1500,26 +303,14 @@ interpreter_run (void *cls, 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); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &cmd->details.admin_add_incoming.wtid, + sizeof (cmd->details.admin_add_incoming.wtid)); cmd->details.admin_add_incoming.aih - = TALER_BANK_admin_add_incoming (bank, - &reserve_pub, + = TALER_BANK_admin_add_incoming (ctx, + &cmd->details.admin_add_incoming.wtid, &amount, - execution_date, - wire, + cmd->details.admin_add_incoming.account_no, &add_incoming_cb, is); if (NULL == cmd->details.admin_add_incoming.aih) @@ -1530,473 +321,6 @@ interpreter_run (void *cls, } 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_BANK_reserve_status (bank, - &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_BANK_reserve_withdraw (bank, - 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_BANK_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_BANK_deposit (bank, - &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_BANK_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_BANK_DenomPublicKey melt_pks[num_melted_coins]; - struct TALER_BANK_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_BANK_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_BANK_refresh_melt (bank, - 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_BANK_refresh_reveal (bank, - 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_BANK_refresh_link (bank, - &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_BANK_wire (bank, - &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_BANK_wire_deposits (bank, - &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_BANK_deposit_wtid (bank, - &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", @@ -2043,131 +367,6 @@ do_shutdown (void *cls, 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_BANK_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_BANK_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_BANK_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_BANK_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_BANK_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_BANK_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_BANK_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_BANK_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_BANK_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", @@ -2188,11 +387,6 @@ do_shutdown (void *cls, GNUNET_SCHEDULER_cancel (ctx_task); ctx_task = NULL; } - if (NULL != bank) - { - TALER_BANK_disconnect (bank); - bank = NULL; - } if (NULL != ctx) { TALER_BANK_fini (ctx); @@ -2201,40 +395,6 @@ do_shutdown (void *cls, } -/** - * Functions of this type are called to provide the retrieved signing and - * denomination keys of the bank. No TALER_BANK_*() functions should be called - * in this callback. - * - * @param cls closure - * @param keys information about keys of the bank - */ -static void -cert_cb (void *cls, - const struct TALER_BANK_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. * @@ -2304,235 +464,14 @@ 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 */ + /* Add EUR:5.01 to account 42 */ { .oc = OC_ADMIN_ADD_INCOMING, - .label = "refresh-create-reserve-1", + .label = "deposit-1", .expected_response_code = MHD_HTTP_OK, - .details.admin_add_incoming.wire = "{ \"type\":\"TEST\", \"bank\":\"source bank\", \"account\":424 }", + .details.admin_add_incoming.account_no = 42, .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 bank 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 bank 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 bank 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 } }; @@ -2540,15 +479,12 @@ run (void *cls, is = GNUNET_new (struct InterpreterState); is->commands = commands; - ctx = TALER_BANK_init (); + ctx = TALER_BANK_init ("http://localhost:8081"); GNUNET_assert (NULL != ctx); ctx_task = GNUNET_SCHEDULER_add_now (&context_task, ctx); - bank = TALER_BANK_connect (ctx, - "http://localhost:8081", - &cert_cb, is, - TALER_BANK_OPTION_END); - GNUNET_assert (NULL != bank); + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); shutdown_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 150), @@ -2566,22 +502,11 @@ int main (int argc, char * const *argv) { - struct GNUNET_OS_Process *proc; struct GNUNET_OS_Process *bankd; GNUNET_log_setup ("test-bank-api", "WARNING", NULL); - proc = GNUNET_OS_start_process (GNUNET_NO, - GNUNET_OS_INHERIT_STD_ALL, - NULL, NULL, NULL, - "taler-bank-keyup", - "taler-bank-keyup", - "-d", "test-bank-home", - "-m", "test-bank-home/master.priv", - NULL); - GNUNET_OS_process_wait (proc); - GNUNET_OS_process_destroy (proc); bankd = GNUNET_OS_start_process (GNUNET_NO, GNUNET_OS_INHERIT_STD_ALL, NULL, NULL, NULL, @@ -2589,8 +514,15 @@ main (int argc, "taler-bank-httpd", "-d", "test-bank-home", NULL); + if (NULL == bankd) + { + fprintf (stderr, + "taler-bank-httpd not found, skipping test\n"); + return 77; /* report 'skip' */ + } /* give child time to start and bind against the socket */ - fprintf (stderr, "Waiting for taler-bank-httpd to be ready"); + fprintf (stderr, + "Waiting for taler-bank-httpd to be ready"); do { fprintf (stderr, "."); -- cgit v1.2.3