diff options
Diffstat (limited to 'src/lib')
97 files changed, 25416 insertions, 0 deletions
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am new file mode 100644 index 000000000..59b1f3165 --- /dev/null +++ b/src/lib/Makefile.am @@ -0,0 +1,255 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/bank-lib + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libtalerexchange.la \ + libtalertesting.la \ + libtalerauditor.la \ + libtalerauditortesting.la + +libtalerexchange_la_LDFLAGS = \ + -version-info 4:0:0 \ + -no-undefined +libtalerexchange_la_SOURCES = \ + exchange_api_curl_defaults.c exchange_api_curl_defaults.h \ + exchange_api_common.c \ + exchange_api_handle.c exchange_api_handle.h \ + exchange_api_deposit.c \ + exchange_api_payback.c \ + exchange_api_refresh.c \ + exchange_api_refresh_link.c \ + exchange_api_refund.c \ + exchange_api_reserve.c \ + exchange_api_track_transaction.c \ + exchange_api_track_transfer.c \ + exchange_api_wire.c +libtalerexchange_la_LIBADD = \ + libtalerauditor.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + + +libtalerauditor_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined +libtalerauditor_la_SOURCES = \ + auditor_api_curl_defaults.c auditor_api_curl_defaults.h \ + auditor_api_handle.c auditor_api_handle.h \ + auditor_api_deposit_confirmation.c \ + auditor_api_exchanges.c +libtalerauditor_la_LIBADD = \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + +if HAVE_LIBCURL +libtalerauditor_la_LIBADD += -lcurl +else +if HAVE_LIBGNURL +libtalerauditor_la_LIBADD += -lgnurl +endif +endif + + +libtalertesting_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined +libtalertesting_la_SOURCES = \ + exchange_api_curl_defaults.c \ + testing_api_cmd_exec_aggregator.c \ + testing_api_cmd_exec_wirewatch.c \ + testing_api_cmd_exec_keyup.c \ + testing_api_cmd_exec_auditor-sign.c \ + testing_api_cmd_fakebank_transfer.c \ + testing_api_cmd_withdraw.c \ + testing_api_cmd_wire.c \ + testing_api_cmd_refund.c \ + testing_api_cmd_status.c \ + testing_api_cmd_deposit.c \ + testing_api_cmd_sleep.c \ + testing_api_cmd_refresh.c \ + testing_api_cmd_track.c \ + testing_api_cmd_bank_check.c \ + testing_api_cmd_payback.c \ + testing_api_cmd_signal.c \ + testing_api_cmd_check_keys.c \ + testing_api_cmd_batch.c \ + testing_api_cmd_serialize_keys.c \ + testing_api_helpers.c \ + testing_api_loop.c \ + testing_api_traits.c \ + testing_api_trait_blinding_key.c \ + testing_api_trait_coin_priv.c \ + testing_api_trait_denom_pub.c \ + testing_api_trait_denom_sig.c \ + testing_api_trait_exchange_pub.c \ + testing_api_trait_exchange_sig.c \ + testing_api_trait_json.c \ + testing_api_trait_process.c \ + testing_api_trait_reserve_priv.c \ + testing_api_trait_number.c \ + testing_api_trait_fresh_coin.c \ + testing_api_trait_string.c \ + testing_api_trait_key_peer.c \ + testing_api_trait_wtid.c \ + testing_api_trait_amount.c \ + testing_api_trait_cmd.c +libtalertesting_la_LIBADD = \ + libtalerexchange.la \ + $(top_builddir)/src/wire/libtalerwire.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + +libtalerauditortesting_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined +libtalerauditortesting_la_SOURCES = \ + testing_auditor_api_helpers.c \ + testing_auditor_api_cmd_deposit_confirmation.c \ + testing_auditor_api_cmd_exchanges.c \ + testing_auditor_api_cmd_exec_auditor.c \ + testing_auditor_api_cmd_exec_auditor_dbinit.c \ + testing_auditor_api_cmd_exec_wire_auditor.c +libtalerauditortesting_la_LIBADD = \ + libtalerauditor.la \ + libtalerexchange.la \ + libtalertesting.la \ + $(top_builddir)/src/wire/libtalerwire.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + + +if HAVE_LIBCURL +libtalerexchange_la_LIBADD += -lcurl +else +if HAVE_LIBGNURL +libtalerexchange_la_LIBADD += -lgnurl +endif +endif + +check_PROGRAMS = \ + test_exchange_api_keys_cherry_picking_new \ + test_exchange_api_overlapping_keys_bug \ + test_exchange_api_new \ + test_auditor_api + +if HAVE_TWISTER + check_PROGRAMS += \ + test_exchange_api_twisted + +test_exchange_api_twisted_SOURCES = \ + test_exchange_api_twisted.c +test_exchange_api_twisted_LDADD = \ + $(LIBGCRYPT_LIBS) \ + libtalertesting.la \ + libtalerexchange.la \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -ltalertwistertesting \ + -lgnunetjson \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + +endif + +AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; + +TESTS = \ + $(check_PROGRAMS) + +test_exchange_api_new_SOURCES = \ + test_exchange_api_new.c +test_exchange_api_new_LDADD = \ + libtalertesting.la \ + libtalerexchange.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + +test_exchange_api_overlapping_keys_bug_SOURCES = \ + test_exchange_api_overlapping_keys_bug.c +test_exchange_api_overlapping_keys_bug_LDADD = \ + libtalertesting.la \ + libtalerexchange.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + +test_exchange_api_keys_cherry_picking_new_SOURCES = \ + test_exchange_api_keys_cherry_picking_new.c +test_exchange_api_keys_cherry_picking_new_LDADD = \ + libtalertesting.la \ + libtalerexchange.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + +test_auditor_api_SOURCES = \ + test_auditor_api.c +test_auditor_api_LDADD = \ + libtalerauditortesting.la \ + libtalerauditor.la \ + libtalertesting.la \ + libtalerexchange.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + + + +EXTRA_DIST = \ + test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv \ + test_exchange_api_home/.config/taler/test.json \ + test_exchange_api_home/.config/taler/sepa.json \ + test_exchange_api.conf \ + test_exchange_api_keys_cherry_picking.conf \ + test_exchange_api_keys_cherry_picking_extended.conf \ + test_auditor_api.conf \ + test_auditor_api_expire_reserve_now.conf diff --git a/src/lib/afl-generate.sh b/src/lib/afl-generate.sh new file mode 100644 index 000000000..b0afcab35 --- /dev/null +++ b/src/lib/afl-generate.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# +# This file is part of TALER +# Copyright (C) 2015 GNUnet e.V. +# +# TALER is free software; you can redistribute it and/or modify it under the +# terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License along with +# TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +# +# +# This will generate testcases in a directory 'afl-tests', which can then +# be moved into src/exchange/afl-tests/ to be run during exchange-testing. +# +# This script uses American Fuzzy Loop (AFL) to fuzz the exchange to +# automatically create tests with good coverage. You must install +# AFL and set AFL_HOME to the directory where AFL is installed +# before running. Also, a directory "baseline/" should exist with +# templates for inputs for AFL to fuzz. These can be generated +# by running wireshark on loopback while running 'make check' in +# this directory. Save each HTTP request to a new file. +# +# Note that you want to switch 'TESTRUN = NO' and pre-init the +# database before running this, otherwise it will be awfully slow. +# +# Must be run from this directory. +# +$AFL_HOME/afl-fuzz -i baseline/ -m 250 -o afl-tests/ -f /tmp/afl-input taler-exchange-httpd -i -f /tmp/afl-input -d test-exchange-home/ -C diff --git a/src/lib/auditor_api_curl_defaults.c b/src/lib/auditor_api_curl_defaults.c new file mode 100644 index 000000000..d5b924008 --- /dev/null +++ b/src/lib/auditor_api_curl_defaults.c @@ -0,0 +1,75 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file auditor-lib/curl_defaults.c + * @brief curl easy handle defaults + * @author Florian Dold + */ + +#include "auditor_api_curl_defaults.h" + + +/** + * Get a curl handle with the right defaults + * for the exchange lib. In the future, we might manage a pool of connections here. + * + * @param url URL to query + */ +CURL * +TAL_curl_easy_get (const char *url) +{ + CURL *eh; + + eh = curl_easy_init (); + + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_ENCODING, + "deflate")); +#ifdef CURLOPT_TCP_FASTOPEN + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TCP_FASTOPEN, + 1L)); +#endif + { + /* Unfortunately libcurl needs chunk to be alive until after + curl_easy_perform. To avoid manual cleanup, we keep + one static list here. */ + static struct curl_slist *chunk = NULL; + if (NULL == chunk) + { + /* With POST requests, we do not want to wait for the + "100 Continue" response, as our request bodies are usually + small and directy sending them saves us a round trip. + + Clearing the expect header like this disables libcurl's + default processing of the header. + + Disabling this header is safe for other HTTP methods, thus + we don't distinguish further before setting it. */ + chunk = curl_slist_append (chunk, "Expect:"); + } + GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HTTPHEADER, chunk)); + } + + return eh; +} diff --git a/src/lib/auditor_api_curl_defaults.h b/src/lib/auditor_api_curl_defaults.h new file mode 100644 index 000000000..3be5816bf --- /dev/null +++ b/src/lib/auditor_api_curl_defaults.h @@ -0,0 +1,41 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file auditor-lib/curl_defaults.h + * @brief curl easy handle defaults + * @author Florian Dold + */ + +#ifndef _TALER_CURL_DEFAULTS_H +#define _TALER_CURL_DEFAULTS_H + + +#include "platform.h" +#include <gnunet/gnunet_curl_lib.h> + + +/** + * Get a curl handle with the right defaults + * for the auditor lib. In the future, we might manage a pool of connections here. + * + * @param url URL to query + */ +CURL * +TAL_curl_easy_get (const char *url); + +#endif /* _TALER_CURL_DEFAULTS_H */ diff --git a/src/lib/auditor_api_deposit_confirmation.c b/src/lib/auditor_api_deposit_confirmation.c new file mode 100644 index 000000000..99c855fb4 --- /dev/null +++ b/src/lib/auditor_api_deposit_confirmation.c @@ -0,0 +1,384 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file auditor-lib/auditor_api_deposit_confirmation.c + * @brief Implementation of the /deposit request of the auditor's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_auditor_service.h" +#include "auditor_api_handle.h" +#include "taler_signatures.h" +#include "auditor_api_curl_defaults.h" + + +/** + * @brief A DepositConfirmation Handle + */ +struct TALER_AUDITOR_DepositConfirmationHandle +{ + + /** + * The connection to auditor this request handle will use + */ + struct TALER_AUDITOR_Handle *auditor; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_AUDITOR_DepositConfirmationResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * Function called when we're done processing the + * HTTP /deposit-confirmation request. + * + * @param cls the `struct TALER_AUDITOR_DepositConfirmationHandle` + * @param response_code HTTP response code, 0 on error + * @param djson parsed JSON result, NULL on error + */ +static void +handle_deposit_confirmation_finished (void *cls, + long response_code, + const void *djson) +{ + const json_t *json = djson; + struct TALER_AUDITOR_DepositConfirmationHandle *dh = cls; + + dh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the auditor is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, auditor says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + dh->cb (dh->cb_cls, + response_code, + TALER_JSON_get_error_code (json), + json); + TALER_AUDITOR_deposit_confirmation_cancel (dh); +} + + +/** + * Verify signature information about the deposit-confirmation. + * + * @param h_wire hash of merchant wire details + * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor) + * @param timestamp timestamp when the contract was finalized, must not be too far in the future + * @param refund_deadline date until which the merchant can issue a refund to the customer via the auditor (can be zero if refunds are not allowed); must not be after the @a wire_deadline + * @param amount_without_fee the amount confirmed to be wired by the exchange to the merchant + * @param coin_pub coin’s public key + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT + * @param exchange_pub the public key of the exchange that matches @a exchange_sig + * @param master_pub master public key of the exchange + * @param ep_start when does @a exchange_pub validity start + * @param ep_expire when does @a exchange_pub usage end + * @param ep_end when does @a exchange_pub legal validity end + * @param master_sig master signature affirming validity of @a exchange_pub + * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not + */ +static int +verify_signatures (const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract_terms, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund_deadline, + const struct TALER_Amount *amount_without_fee, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig, + const struct TALER_MasterPublicKeyP *master_pub, + struct GNUNET_TIME_Absolute ep_start, + struct GNUNET_TIME_Absolute ep_expire, + struct GNUNET_TIME_Absolute ep_end, + const struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_DepositConfirmationPS dc; + struct TALER_ExchangeSigningKeyValidityPS sv; + + dc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT); + dc.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); + dc.h_contract_terms = *h_contract_terms; + dc.h_wire = *h_wire; + dc.timestamp = GNUNET_TIME_absolute_hton (timestamp); + dc.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); + TALER_amount_hton (&dc.amount_without_fee, + amount_without_fee); + dc.coin_pub = *coin_pub; + dc.merchant = *merchant_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT, + &dc.purpose, + &exchange_sig->eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("Invalid signature on /deposit-confirmation request!\n"); + { + TALER_LOG_DEBUG ("... amount_without_fee was %s\n", + TALER_amount2s (amount_without_fee)); + } + + return GNUNET_SYSERR; + } + sv.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); + sv.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)); + sv.master_public_key = *master_pub; + sv.start = GNUNET_TIME_absolute_hton (ep_start); + sv.expire = GNUNET_TIME_absolute_hton (ep_expire); + sv.end = GNUNET_TIME_absolute_hton (ep_end); + sv.signkey_pub = *exchange_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, + &sv.purpose, + &master_sig->eddsa_signature, + &master_pub->eddsa_pub)) + { + GNUNET_break (0); + TALER_LOG_WARNING ("Invalid signature on exchange signing key!\n"); + return GNUNET_SYSERR; + } + if (0 == GNUNET_TIME_absolute_get_remaining (ep_end).rel_value_us) + { + GNUNET_break (0); + TALER_LOG_WARNING ("Exchange signing key is no longer valid!\n"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Submit a deposit-confirmation permission to the auditor and get the + * auditor's response. Note that while we return the response + * verbatim to the caller for further processing, we do already verify + * that the response is well-formed. If the auditor's reply is not + * well-formed, we return an HTTP status code of zero to @a cb. + * + * We also verify that the @a exchange_sig is valid for this deposit-confirmation + * request, and that the @a master_sig is a valid signature for @a + * exchange_pub. Also, the @a auditor must be ready to operate (i.e. have + * finished processing the /version reply). If either check fails, we do + * NOT initiate the transaction with the auditor and instead return NULL. + * + * @param auditor the auditor handle; the auditor must be ready to operate + * @param h_wire hash of merchant wire details + * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor) + * @param timestamp timestamp when the contract was finalized, must not be too far in the future + * @param refund_deadline date until which the merchant can issue a refund to the customer via the auditor (can be zero if refunds are not allowed); must not be after the @a wire_deadline + * @param amount_without_fee the amount confirmed to be wired by the exchange to the merchant + * @param coin_pub coin’s public key + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT + * @param exchange_pub the public key of the exchange that matches @a exchange_sig + * @param master_pub master public key of the exchange + * @param ep_start when does @a exchange_pub validity start + * @param ep_expire when does @a exchange_pub usage end + * @param ep_end when does @a exchange_pub legal validity end + * @param master_sig master signature affirming validity of @a exchange_pub + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_AUDITOR_DepositConfirmationHandle * +TALER_AUDITOR_deposit_confirmation (struct TALER_AUDITOR_Handle *auditor, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract_terms, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund_deadline, + const struct TALER_Amount *amount_without_fee, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig, + const struct TALER_MasterPublicKeyP *master_pub, + struct GNUNET_TIME_Absolute ep_start, + struct GNUNET_TIME_Absolute ep_expire, + struct GNUNET_TIME_Absolute ep_end, + const struct TALER_MasterSignatureP *master_sig, + TALER_AUDITOR_DepositConfirmationResultCallback cb, + void *cb_cls) +{ + struct TALER_AUDITOR_DepositConfirmationHandle *dh; + struct GNUNET_CURL_Context *ctx; + json_t *deposit_confirmation_obj; + CURL *eh; + + (void) GNUNET_TIME_round_abs (×tamp); + (void) GNUNET_TIME_round_abs (&refund_deadline); + (void) GNUNET_TIME_round_abs (&ep_start); + (void) GNUNET_TIME_round_abs (&ep_expire); + (void) GNUNET_TIME_round_abs (&ep_end); + GNUNET_assert (GNUNET_YES == + MAH_handle_is_ready (auditor)); + if (GNUNET_OK != + verify_signatures (h_wire, + h_contract_terms, + timestamp, + refund_deadline, + amount_without_fee, + coin_pub, + merchant_pub, + exchange_pub, + exchange_sig, + master_pub, + ep_start, + ep_expire, + ep_end, + master_sig)) + { + GNUNET_break_op (0); + return NULL; + } + + deposit_confirmation_obj + = json_pack ("{s:o, s:o," /* H_wire, h_contract_terms */ + " s:o, s:o," /* timestamp, refund_deadline */ + " s:o, s:o," /* amount_without_fees, coin_pub */ + " s:o, s:o," /* merchant_pub, exchange_sig */ + " s:o, s:o," /* master_pub, ep_start */ + " s:o, s:o," /* ep_expire, ep_end */ + " s:o}", /* master_sig */ + "H_wire", GNUNET_JSON_from_data_auto (&h_wire), + "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), + "timestamp", GNUNET_JSON_from_time_abs (timestamp), + "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline), + "amount_without_fee", TALER_JSON_from_amount (amount_without_fee), + "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), + "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub), + "exchange_sig", GNUNET_JSON_from_data_auto (exchange_sig), + "master_pub", GNUNET_JSON_from_data_auto (master_pub), + "ep_start", GNUNET_JSON_from_time_abs (ep_start), + "ep_expire", GNUNET_JSON_from_time_abs (ep_expire), + "ep_end", GNUNET_JSON_from_time_abs (ep_end), + "master_sig", GNUNET_JSON_from_data_auto (master_sig)); + if (NULL == deposit_confirmation_obj) + { + GNUNET_break (0); + return NULL; + } + + dh = GNUNET_new (struct TALER_AUDITOR_DepositConfirmationHandle); + dh->auditor = auditor; + dh->cb = cb; + dh->cb_cls = cb_cls; + dh->url = MAH_path_to_url (auditor, "/deposit-confirmation"); + + eh = TAL_curl_easy_get (dh->url); + GNUNET_assert (NULL != (dh->json_enc = + json_dumps (deposit_confirmation_obj, + JSON_COMPACT))); + json_decref (deposit_confirmation_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for deposit-confirmation: `%s'\n", + dh->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + dh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (dh->json_enc))); + ctx = MAH_handle_to_context (auditor); + dh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_deposit_confirmation_finished, + dh); + return dh; +} + + +/** + * Cancel a deposit-confirmation permission request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param deposit-confirmation the deposit-confirmation permission request handle + */ +void +TALER_AUDITOR_deposit_confirmation_cancel (struct TALER_AUDITOR_DepositConfirmationHandle *deposit_confirmation) +{ + if (NULL != deposit_confirmation->job) + { + GNUNET_CURL_job_cancel (deposit_confirmation->job); + deposit_confirmation->job = NULL; + } + GNUNET_free (deposit_confirmation->url); + GNUNET_free (deposit_confirmation->json_enc); + GNUNET_free (deposit_confirmation); +} + + +/* end of auditor_api_deposit_confirmation.c */ diff --git a/src/lib/auditor_api_exchanges.c b/src/lib/auditor_api_exchanges.c new file mode 100644 index 000000000..770eedda3 --- /dev/null +++ b/src/lib/auditor_api_exchanges.c @@ -0,0 +1,245 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file auditor-lib/auditor_api_exchanges.c + * @brief Implementation of the /exchanges request of the auditor's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_auditor_service.h" +#include "auditor_api_handle.h" +#include "taler_signatures.h" +#include "auditor_api_curl_defaults.h" + +/** + * How many exchanges do we allow a single auditor to + * audit at most? + */ +#define MAX_EXCHANGES 1024 + + +/** + * @brief A ListExchanges Handle + */ +struct TALER_AUDITOR_ListExchangesHandle +{ + + /** + * The connection to auditor this request handle will use + */ + struct TALER_AUDITOR_Handle *auditor; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_AUDITOR_ListExchangesResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * Function called when we're done processing the + * HTTP /deposit-confirmation request. + * + * @param cls the `struct TALER_AUDITOR_ListExchangesHandle` + * @param response_code HTTP response code, 0 on error + * @param djson parsed JSON result, NULL on error + */ +static void +handle_exchanges_finished (void *cls, + long response_code, + const void *djson) +{ + const json_t *json = djson; + const json_t *ja; + unsigned int ja_len; + struct TALER_AUDITOR_ListExchangesHandle *leh = cls; + + leh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + ja = json_object_get (json, + "exchanges"); + if ( (NULL == ja) || + (! json_is_array (ja)) ) + { + GNUNET_break (0); + response_code = 0; + break; + } + + ja_len = json_array_size (ja); + if (ja_len > MAX_EXCHANGES) + { + GNUNET_break (0); + response_code = 0; + break; + } + { + struct TALER_AUDITOR_ExchangeInfo ei[ja_len]; + int ok; + + ok = GNUNET_YES; + for (unsigned int i=0;i<ja_len;i++) + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("master_pub", &ei[i].master_pub), + GNUNET_JSON_spec_string ("exchange_url", &ei[i].exchange_url), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (ja, + i), + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ok = GNUNET_NO; + break; + } + } + if (GNUNET_YES != ok) + break; + leh->cb (leh->cb_cls, + response_code, + TALER_EC_NONE, + ja_len, + ei, + json); + leh->cb = NULL; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the auditor is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != leh->cb) + leh->cb (leh->cb_cls, + response_code, + TALER_JSON_get_error_code (json), + 0, + NULL, + json); + TALER_AUDITOR_list_exchanges_cancel (leh); +} + + +/** + * Submit an /exchanges request to the auditor and get the + * auditor's response. If the auditor's reply is not + * well-formed, we return an HTTP status code of zero to @a cb. + * + * @param auditor the auditor handle; the auditor must be ready to operate + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_AUDITOR_ListExchangesHandle * +TALER_AUDITOR_list_exchanges (struct TALER_AUDITOR_Handle *auditor, + TALER_AUDITOR_ListExchangesResultCallback cb, + void *cb_cls) +{ + struct TALER_AUDITOR_ListExchangesHandle *leh; + struct GNUNET_CURL_Context *ctx; + CURL *eh; + + GNUNET_assert (GNUNET_YES == + MAH_handle_is_ready (auditor)); + + leh = GNUNET_new (struct TALER_AUDITOR_ListExchangesHandle); + leh->auditor = auditor; + leh->cb = cb; + leh->cb_cls = cb_cls; + leh->url = MAH_path_to_url (auditor, "/exchanges"); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for list-exchanges: `%s'\n", + leh->url); + eh = TAL_curl_easy_get (leh->url); + ctx = MAH_handle_to_context (auditor); + leh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_NO, + &handle_exchanges_finished, + leh); + return leh; +} + + +/** + * Cancel a deposit-confirmation permission request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param deposit-confirmation the deposit-confirmation permission request handle + */ +void +TALER_AUDITOR_list_exchanges_cancel (struct TALER_AUDITOR_ListExchangesHandle *leh) +{ + if (NULL != leh->job) + { + GNUNET_CURL_job_cancel (leh->job); + leh->job = NULL; + } + GNUNET_free (leh->url); + GNUNET_free (leh); +} + + +/* end of auditor_api_exchanges.c */ diff --git a/src/lib/auditor_api_handle.c b/src/lib/auditor_api_handle.c new file mode 100644 index 000000000..aca591dc7 --- /dev/null +++ b/src/lib/auditor_api_handle.c @@ -0,0 +1,527 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file auditor-lib/auditor_api_handle.c + * @brief Implementation of the "handle" component of the auditor's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <microhttpd.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_auditor_service.h" +#include "taler_signatures.h" +#include "auditor_api_handle.h" +#include "auditor_api_curl_defaults.h" +#include "backoff.h" + +/** + * Which revision of the Taler auditor protocol is implemented + * by this library? Used to determine compatibility. + */ +#define TALER_PROTOCOL_CURRENT 0 + +/** + * How many revisions back are we compatible to? + */ +#define TALER_PROTOCOL_AGE 0 + + +/** + * Log error related to CURL operations. + * + * @param type log level + * @param function which function failed to run + * @param code what was the curl error code + */ +#define CURL_STRERROR(type, function, code) \ + GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \ + function, __FILE__, __LINE__, curl_easy_strerror (code)); + +/** + * Stages of initialization for the `struct TALER_AUDITOR_Handle` + */ +enum AuditorHandleState +{ + /** + * Just allocated. + */ + MHS_INIT = 0, + + /** + * Obtained the auditor's versioning data and version. + */ + MHS_VERSION = 1, + + /** + * Failed to initialize (fatal). + */ + MHS_FAILED = 2 +}; + + +/** + * Data for the request to get the /version of a auditor. + */ +struct VersionRequest; + + +/** + * Handle to the auditor + */ +struct TALER_AUDITOR_Handle +{ + /** + * The context of this handle + */ + struct GNUNET_CURL_Context *ctx; + + /** + * The URL of the auditor (i.e. "http://auditor.taler.net/") + */ + char *url; + + /** + * Function to call with the auditor's certification data, + * NULL if this has already been done. + */ + TALER_AUDITOR_VersionCallback version_cb; + + /** + * Closure to pass to @e version_cb. + */ + void *version_cb_cls; + + /** + * Data for the request to get the /version of a auditor, + * NULL once we are past stage #MHS_INIT. + */ + struct VersionRequest *vr; + + /** + * Task for retrying /version request. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * /version data of the auditor, only valid if + * @e handshake_complete is past stage #MHS_VERSION. + */ + struct TALER_AUDITOR_VersionInformation vi; + + /** + * Retry /version frequency. + */ + struct GNUNET_TIME_Relative retry_delay; + + /** + * Stage of the auditor's initialization routines. + */ + enum AuditorHandleState state; + +}; + + +/* ***************** Internal /version fetching ************* */ + +/** + * Data for the request to get the /version of a auditor. + */ +struct VersionRequest +{ + /** + * The connection to auditor this request handle will use + */ + struct TALER_AUDITOR_Handle *auditor; + + /** + * The url for this handle + */ + char *url; + + /** + * Entry for this request with the `struct GNUNET_CURL_Context`. + */ + struct GNUNET_CURL_Job *job; + +}; + + +/** + * Release memory occupied by a version request. + * Note that this does not cancel the request + * itself. + * + * @param vr request to free + */ +static void +free_version_request (struct VersionRequest *vr) +{ + GNUNET_free (vr->url); + GNUNET_free (vr); +} + + +/** + * Free version data object. + * + * @param vi data to free (pointer itself excluded) + */ +static void +free_version_info (struct TALER_AUDITOR_VersionInformation *vi) +{ + GNUNET_free_non_null (vi->version); + vi->version = NULL; +} + + +/** + * Decode the JSON in @a resp_obj from the /version response and store the data + * in the @a key_data. + * + * @param[in] resp_obj JSON object to parse + * @param check_sig #GNUNET_YES if we should check the signature + * @param[out] vi where to store the results we decoded + * @param[out] vc where to store version compatibility data + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error (malformed JSON) + */ +static int +decode_version_json (const json_t *resp_obj, + int check_sig, + struct TALER_AUDITOR_VersionInformation *vi, + enum TALER_AUDITOR_VersionCompatibility *vc) +{ + unsigned int age; + unsigned int revision; + unsigned int current; + const char *ver; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("version", + &ver), + GNUNET_JSON_spec_fixed_auto ("master_public_key", + &vi->auditor_pub), + GNUNET_JSON_spec_end() + }; + + if (JSON_OBJECT != json_typeof (resp_obj)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* check the version */ + if (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (3 != sscanf (vi->version, + "%u:%u:%u", + ¤t, + &revision, + &age)) + { + GNUNET_break_op (0); + free_version_info (vi); + return GNUNET_SYSERR; + } + vi->version = GNUNET_strdup (ver); + *vc = TALER_AUDITOR_VC_MATCH; + if (TALER_PROTOCOL_CURRENT < current) + { + *vc |= TALER_AUDITOR_VC_NEWER; + if (TALER_PROTOCOL_CURRENT < current - age) + *vc |= TALER_AUDITOR_VC_INCOMPATIBLE; + } + if (TALER_PROTOCOL_CURRENT > current) + { + *vc |= TALER_AUDITOR_VC_OLDER; + if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current) + *vc |= TALER_AUDITOR_VC_INCOMPATIBLE; + } + return GNUNET_OK; +} + + +/** + * Initiate download of /version from the auditor. + * + * @param cls auditor where to download /version from + */ +static void +request_version (void *cls); + + +/** + * Callback used when downloading the reply to a /version request + * is complete. + * + * @param cls the `struct VersionRequest` + * @param response_code HTTP response code, 0 on error + * @param resp_obj parsed JSON result, NULL on error + */ +static void +version_completed_cb (void *cls, + long response_code, + const void *gresp_obj) +{ + const json_t *resp_obj = gresp_obj; + struct VersionRequest *vr = cls; + struct TALER_AUDITOR_Handle *auditor = vr->auditor; + enum TALER_AUDITOR_VersionCompatibility vc; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received version from URL `%s' with status %ld.\n", + vr->url, + response_code); + vc = TALER_AUDITOR_VC_PROTOCOL_ERROR; + switch (response_code) + { + case 0: + case MHD_HTTP_INTERNAL_SERVER_ERROR: + free_version_request (vr); + auditor->vr = NULL; + GNUNET_assert (NULL == auditor->retry_task); + auditor->retry_delay = EXCHANGE_LIB_BACKOFF (auditor->retry_delay); + auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay, + &request_version, + auditor); + return; + case MHD_HTTP_OK: + if (NULL == resp_obj) + { + response_code = 0; + break; + } + if (GNUNET_OK != + decode_version_json (resp_obj, + GNUNET_YES, + &auditor->vi, + &vc)) + { + response_code = 0; + break; + } + auditor->retry_delay = GNUNET_TIME_UNIT_ZERO; + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + break; + } + if (MHD_HTTP_OK != response_code) + { + auditor->vr = NULL; + free_version_request (vr); + auditor->state = MHS_FAILED; + free_version_info (&auditor->vi); + /* notify application that we failed */ + auditor->version_cb (auditor->version_cb_cls, + NULL, + vc); + return; + } + + auditor->vr = NULL; + free_version_request (vr); + auditor->state = MHS_VERSION; + /* notify application about the key information */ + auditor->version_cb (auditor->version_cb_cls, + &auditor->vi, + vc); +} + + +/* ********************* library internal API ********* */ + + +/** + * Get the context of a auditor. + * + * @param h the auditor handle to query + * @return ctx context to execute jobs in + */ +struct GNUNET_CURL_Context * +MAH_handle_to_context (struct TALER_AUDITOR_Handle *h) +{ + return h->ctx; +} + + +/** + * Check if the handle is ready to process requests. + * + * @param h the auditor handle to query + * @return #GNUNET_YES if we are ready, #GNUNET_NO if not + */ +int +MAH_handle_is_ready (struct TALER_AUDITOR_Handle *h) +{ + return (MHS_VERSION == h->state) ? GNUNET_YES : GNUNET_NO; +} + + +/** + * Obtain the URL to use for an API request. + * + * @param h handle for the auditor + * @param path Taler API path (i.e. "/deposit-confirmation") + * @return the full URL to use with cURL + */ +char * +MAH_path_to_url (struct TALER_AUDITOR_Handle *h, + const char *path) +{ + return MAH_path_to_url2 (h->url, + path); +} + + +/** + * Obtain the URL to use for an API request. + * + * @param base_url base URL of the auditor (i.e. "http://auditor/") + * @param path Taler API path (i.e. "/deposit-confirmation") + * @return the full URL to use with cURL + */ +char * +MAH_path_to_url2 (const char *base_url, + const char *path) +{ + char *url; + + if ( ('/' == path[0]) && + (0 < strlen (base_url)) && + ('/' == base_url[strlen (base_url) - 1]) ) + path++; /* avoid generating URL with "//" from concat */ + GNUNET_asprintf (&url, + "%s%s", + base_url, + path); + return url; +} + + +/* ********************* public API ******************* */ + + +/** + * Initialise a connection to the auditor. Will connect to the + * auditor and obtain information about the auditor's master public + * key and the auditor's auditor. The respective information will + * be passed to the @a version_cb once available, and all future + * interactions with the auditor will be checked to be signed + * (where appropriate) by the respective master key. + * + * @param ctx the context + * @param url HTTP base URL for the auditor + * @param version_cb function to call with the auditor's version information + * @param version_cb_cls closure for @a version_cb + * @return the auditor handle; NULL upon error + */ +struct TALER_AUDITOR_Handle * +TALER_AUDITOR_connect (struct GNUNET_CURL_Context *ctx, + const char *url, + TALER_AUDITOR_VersionCallback version_cb, + void *version_cb_cls) +{ + struct TALER_AUDITOR_Handle *auditor; + + auditor = GNUNET_new (struct TALER_AUDITOR_Handle); + auditor->ctx = ctx; + auditor->url = GNUNET_strdup (url); + auditor->version_cb = version_cb; + auditor->version_cb_cls = version_cb_cls; + auditor->retry_task = GNUNET_SCHEDULER_add_now (&request_version, + auditor); + return auditor; +} + + +/** + * Initiate download of /version from the auditor. + * + * @param cls auditor where to download /version from + */ +static void +request_version (void *cls) +{ + struct TALER_AUDITOR_Handle *auditor = cls; + struct VersionRequest *vr; + CURL *eh; + + auditor->retry_task = NULL; + GNUNET_assert (NULL == auditor->vr); + vr = GNUNET_new (struct VersionRequest); + vr->auditor = auditor; + vr->url = MAH_path_to_url (auditor, + "/version"); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting version with URL `%s'.\n", + vr->url); + eh = TAL_curl_easy_get (vr->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_VERBOSE, + 0)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT, + (long) 300)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + vr)); + vr->job = GNUNET_CURL_job_add (auditor->ctx, + eh, + GNUNET_NO, + &version_completed_cb, + vr); + auditor->vr = vr; +} + + +/** + * Disconnect from the auditor + * + * @param auditor the auditor handle + */ +void +TALER_AUDITOR_disconnect (struct TALER_AUDITOR_Handle *auditor) +{ + if (NULL != auditor->vr) + { + GNUNET_CURL_job_cancel (auditor->vr->job); + free_version_request (auditor->vr); + auditor->vr = NULL; + } + free_version_info (&auditor->vi); + if (NULL != auditor->retry_task) + { + GNUNET_SCHEDULER_cancel (auditor->retry_task); + auditor->retry_task = NULL; + } + GNUNET_free (auditor->url); + GNUNET_free (auditor); +} + + +/* end of auditor_api_handle.c */ diff --git a/src/lib/auditor_api_handle.h b/src/lib/auditor_api_handle.h new file mode 100644 index 000000000..c3c73f5c3 --- /dev/null +++ b/src/lib/auditor_api_handle.h @@ -0,0 +1,71 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file auditor-lib/auditor_api_handle.h + * @brief Internal interface to the handle part of the auditor's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_auditor_service.h" + + +/** + * Get the context of a auditor. + * + * @param h the auditor handle to query + * @return ctx context to execute jobs in + */ +struct GNUNET_CURL_Context * +MAH_handle_to_context (struct TALER_AUDITOR_Handle *h); + + +/** + * Check if the handle is ready to process requests. + * + * @param h the auditor handle to query + * @return #GNUNET_YES if we are ready, #GNUNET_NO if not + */ +int +MAH_handle_is_ready (struct TALER_AUDITOR_Handle *h); + + +/** + * Obtain the URL to use for an API request. + * + * @param h the auditor handle to query + * @param path Taler API path (i.e. "/deposit-confirmation") + * @return the full URL to use with cURL + */ +char * +MAH_path_to_url (struct TALER_AUDITOR_Handle *h, + const char *path); + + +/** + * Obtain the URL to use for an API request. + * + * @param base_url base URL of the auditor (i.e. "http://auditor/") + * @param path Taler API path (i.e. "/deposit-confirmation") + * @return the full URL to use with cURL + */ +char * +MAH_path_to_url2 (const char *base_url, + const char *path); + + +/* end of auditor_api_handle.h */ diff --git a/src/lib/backoff.h b/src/lib/backoff.h new file mode 100644 index 000000000..2af41e7c7 --- /dev/null +++ b/src/lib/backoff.h @@ -0,0 +1,38 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/backoff.h + * @brief backoff computation for the exchange lib + * @author Florian Dold + */ + + +#ifndef _TALER_BACKOFF_H +#define _TALER_BACKOFF_H + +#include "platform.h" +#include <gnunet/gnunet_time_lib.h> + +/** + * Random exponential backoff used in the exchange lib. + */ +#define EXCHANGE_LIB_BACKOFF(r) GNUNET_TIME_randomized_backoff ( \ + (r), \ + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 2)); + +#endif diff --git a/src/lib/baseline/admin_add_incoming.req b/src/lib/baseline/admin_add_incoming.req new file mode 100644 index 000000000..677678b5d --- /dev/null +++ b/src/lib/baseline/admin_add_incoming.req @@ -0,0 +1,7 @@ +POST /admin/add/incoming HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 220 + +{"reserve_pub":"TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0","amount":{"currency":"EUR","value":5,"fraction":10000},"execution_date":"/Date(1442821651)/","wire":{"type":"TEST","bank":"source bank","account":42}}
\ No newline at end of file diff --git a/src/lib/baseline/deposit.req b/src/lib/baseline/deposit.req new file mode 100644 index 000000000..a400796f1 --- /dev/null +++ b/src/lib/baseline/deposit.req @@ -0,0 +1,8 @@ +POST /deposit HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 1658 +Expect: 100-continue + +{"ub_sig":"51SPJSSDESGPR80A40M74WV140520818ECG26E9M8S0M6CSH6X334GSN8RW30D9G8MT46CA660W34GSG6MT4AD9K8GT3ECSH6MVK0E2374V38H1M8MR4CDJ66MWK4E1S6MR3GCT28CV32H1Q8N23GCHG70S36C1K8MS3GCSN8RV36D9S710KGD9K6GWKEGJ28GRM4CJ56X1K6DJ18D2KGHA46D13GDA66GVK4GHJ8N13AE9J8RVK6GT184S48E1K6X336G9Q8N142CJ4692M6EA16GRKJD9N6523ADA36X13GG9G70TK6DHN68R36CT18GR4CDSJ6CW3GCT364W46CSR8RV42GJ474SMADSH851K4H9Q8GS42CHS8RV3GCSJ64V46DSN8RSM6HHN6N246D9S6934AH9P6X23JGSH652K0DJ5612KJGA26N242CH35452081918G2J2G0","timestamp":"/Date(1442821652)/","f":{"currency":"EUR","value":5,"fraction":0},"wire":{"type":"TEST","bank":"dest bank","account":42},"coin_pub":"JXWK4NS0H2W4V4BETQ90CCEDADP6QQ3MV3YZ7RV2KXEM8PWXE8Q0","H_wire":"YQED9FDYPKK2QQYB3FS19Y15ZMKBAXJP2C73CXASAF1KM6ZYY723TEJ3HBR6D864A7X5W58G92QJ0A9PFMZNB81ZP9NJAQQCCABM4RG","h_contract_terms":"1CMEEFQ5S4QJGGAMVYFV07XQRHQA311CR2MTRNC5M9KZV6ETDV1SY00WJFEV2CG9BXQTEQPZAF8A54C2HX32TZCN20VBGPFPS2Z16B0","merchant_pub":"C36TEXQXFW00170C2EJ66ZR0000CX9VPZNZG00109NX020000000","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","refund_deadline":"/Date(0)/","coin_sig":"X16E0DP8C2BJNVNX09G24FFC5GA4W7RN2YXZP9WJTAN9BY6B4GMA39QNYR51XNNEZ3H1J7TP0K9G55JZ8V7WS7CZMD7E64HWYBFWM00"} diff --git a/src/lib/baseline/keys.req b/src/lib/baseline/keys.req new file mode 100644 index 000000000..a9503a864 --- /dev/null +++ b/src/lib/baseline/keys.req @@ -0,0 +1,7 @@ +GET /keys HTTP/1.1
+User-Agent: Wget/1.16.3 (linux-gnu)
+Accept: */*
+Accept-Encoding: identity
+Host: 127.0.0.1:8081
+Connection: Keep-Alive
+
diff --git a/src/lib/baseline/refresh_link.req b/src/lib/baseline/refresh_link.req new file mode 100644 index 000000000..acf3dff51 --- /dev/null +++ b/src/lib/baseline/refresh_link.req @@ -0,0 +1,5 @@ +GET /refresh/link?coin_pub=WQHES0X5XK43VBG1Y8FXR2YEJM04HQVMDTCS07MH691XWADG8QCG HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/lib/baseline/refresh_melt.req b/src/lib/baseline/refresh_melt.req new file mode 100644 index 000000000..98b5b6389 --- /dev/null +++ b/src/lib/baseline/refresh_melt.req @@ -0,0 +1,8 @@ +POST /refresh/melt HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 34136 +Expect: 100-continue + +{"new_denoms":["51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0"],"transfer_pubs":[["J65E5480S6ZVPBA5HV9D4APYXFS527DAJGN5HKZ5EWP89EFSKGK0"],["TYZZG5HSXTYJMA9XD3EMTX2S36V7F4VPR1RHQPKJSTWXJD2KPDAG"],["2MN2X2X7P6GJCN09Z6ZDF2R9W1W3BSYJER7FTBSDDSWMRJ7DG0DG"]],"melt_coins":[{"value_with_fee":{"currency":"EUR","value":4,"fraction":0},"coin_pub":"WQHES0X5XK43VBG1Y8FXR2YEJM04HQVMDTCS07MH691XWADG8QCG","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","denom_sig":"51SPJSSDESGPR80A40M74WV140520818ECG26DJ384R4CCHP8RTK6DJ46X344C9M6S1KEDSQ8923EE9P84SKAH9R84SMAGT670SM4GT66123EDJ46RVK0E1N65336H226H2K8CA38CVKCH9N711K2H1S6X134GHJ6924CGJ68RRMAH9P6MT30H23651KCD1N6X0M8GA58CS34E9K70V32E1S8MTK8GJ270WK0HA368V3GDHM68RK2CHS610MAH1H8RR42CT58H342DSG8D1MCC1J6N0KEHHS6WVM2GHS60WM8G9G6GTMAC258D338HHP8MWM2GSS8MW3GCA270TK6G9G6WRM2GA56RSMCC9R88T3JEA270SK0HJ284R3CC9Q6WVK0DT570TKJE9P68SK0CSG88T3CCHJ60R48C9N611KAD9N6WVMAC9N70T46GH35452081918G2J2G0","confirm_sig":"ZKYCWKVTVQCWMWTAQRNRSKGQP6AC14VF7W525ZQYFZ6J2T1GM7PE7945W3HNW32S0MZBX8K65VQPXH83NMQR2SGTY7DX2T60RKHFP00"}],"secret_encs":[["DCZPFTHF1GZRXFVSYJ4AK1HH4QE46T8F60X1AQ6T6BF1WHBNTJ3FEGH7Y1TH5A6YT10GFYGV12S8V9MB4HEKHRYBBMR7JJ9KB264TNR"],["ANBQS74BACGAWEC0F0654HYGZB3NBSNXVD4KZY4TRP4JMRADJE2Y5BYV513Y37PPMR6V5P200FEAHTZ5TS3R9G9MBYM6Z144N853PQG"],["1X6P2JR301MPR5JYMKPV1HXAFXKH9M09HWVQ4PS6JNK1K6E8ZSMW1C6NS79CPFP443QBVEW9SRWXXJQT91EHFKJ8007AB4S64SPWN3G"]],"coin_evs":[["1XB4A97X8ATPH1KG5NAMJGJY258AGK0MKVJRHN4NGKPRD17DQM0CMTT351FB7T56NTVA9E5VTH9Q12A10C3YVDEYJB4B66EC1NPXNPMNSVTBJ7HGM4DXFB03K1BXK247QK581CGE54286BTK6B5VW0FTS4AZ3NQP43V9E6VV1HTKG1CM5WQJEY14TAC8P2YSV2XH61RG4E84R","JVNGX2AR684RF4VRYFSS69WNMJBDDS9Y4AXFRX5JRFES5QBTMM51SB84BN61S5W0R7PZGK09NQ8QMX7Z90AE0JJH76J7DCM94DKWMV88K966NEA2CNMS50PG6D0K73619K67S9HJ0PQTQH8YD91P4CQQZTRPV7Y2RHEGMBB2G67ZBVVH1S9Q0HYX69Y2B3J2WYFMH3BJESVGE","6MZ6ZDF26MSAZN3VK3QDY62MD3AXZTK8K46A7ZFNBVCR33AE6H3A309F822EKPHT2YTJB6PMSPSJ3ZAE99Z5Q2J8F87X5N723J713A0QR87KYJ2NZ3PFCS5CC8FXJYK6YXZ4ND6FHNJRJZZZZRXE05VP14XW1BZFYZGQQZ8J05FYKY8AYFNCZ50WT7W8M83XS50GTYJGQTNPT","Q0JWR4WRPYT7TCNCV47RJ1FW3HEFQABN9EE27DBMDB4FYAVK8X9KM38HH9KN8QW28RXFXAKT3MGHXPGRWCY9P313CPR5Z2XSR3ZGKFHQHTP1A02SSD0DHH12RCV5TJ7M86WXWT7F00CQ8HV5H90MFEDX6J2X9JNX26NC0PRH0YXHGW7XVWXF00ZEJCGJDEZ1EVXA5BNW3SAEY","B20PSGCM77EVSXEC6T41N1T55KX76B5K3X38VBQ8NSAN2BKNH0W4DQGVZQTBA2588YCRW1TN9G721GP7ZCP9H32BBC9HM3XFTNJ4QBV2FMM7KSG6T0H418EBNBGKZXMTD9ZDVV6A2TEBMXSEKYVK9HCZT8EEQ7E44DWBDHN274N9D4ZDEFCZXNF0MKBM3TYZGHJ5T71T88CE4","F95WT8Y2WRXGK07MY3FMV1Y6PS44CMP59KK8G82EKW3Z0Y6C6QZ2CVC4A6DF9N3E6HW8SQ3CF4FSD1QC2DN5E9DM5G762DW491BAE5RC2A3YPGJTZ3XBFJ3R3NSRDRZMHR6Y755QDRZZEVFTEPN7MZ90SE6KTQS80CHNMTX15NMZPNFJSCQSEJ9RQJKZBH9ZZFMNXT8BXM7K4","1B8GKDJYJYCZ8Y2HCETV6EWA8FRVR0AAD7TRXFXBHNF7G10CZYDFTKBJ8X0J0M7VR1KQV67SF5VMM7VVG5ZQ4F9QEKRV3GSBB4M75CAXSEXSHDT36RJ6A98EKP3EZXT65HX2K788ZE4SVE5N2NJ8Q26P318PK9X69YWB4W7R1F1F9WHZ8WKJ69G5YZ1T9WR1N8CXTJASXW3A8","FV0Y6B21F01D5EH31R80BHG7GKA08C7HN3FYDMGQE6HJ2Y9ZSVJ17HR8Y22WGPN98T08E6XP4PZNMJ3RHAFK8DAVW3AYM5VNPHV3JSB5B64F0H60EMQNA59A2QAFRV3FCHJQATY87SGT9E67HRVE5QDP9HJC7A351WM55VV5MW3T251VVZ8N5V2GVX6MD14MR9ZX9TBS4N30J","K8QSPQTEV9YZC7MC6AT9R8Y0QGG4PT1V45KSSZR2YY9SBA3QEQG7R6YR4RKEKR5DGDM2K4DN9K128TQMMPFWZR8N95F29Q8E2WBD22F5SRFPJ2KJA6SEZ6RMPCHG6C4BJEV7PZAEVTNJDK8T79PJCB5B97D5WQ52TT2DB6Q4S599D61AA54FCVM9R3AB03PDTYZ0PCKBMJ23G","1655VA0GT3ASG2JVEZ93BWE01YDEA70ZW2FD1W3AZTD4B4WK9GYPMM9Y6KXDGDDB2TMDF5CWXNAX0B7E396E1NH3GCK8KFHMGXNPG0CTD2KNW9X3QB4HKK68JM2WGMF05VJ6K4G76R4A10JWPEZTD554TSFYVEM0355C9E5P9W5GFFFYQMTAXG43V8GP7P4BDBD81Q9G87MXG","J3T1MSDB9ZHCCNAYDXEQAMETNSCX5DJYQBCXGREMYAWAQ8AJCY5JV00HHX31DVT7X5S25CEX88707E81X4KNCN7RBRFMFNP8QZHFZHXNS6MN7BD4MGRVQEC50EWH6N6EFY1GA039KYJKX24XMGZXKB27H5R0RKN7YC00EKN99SGKF3YN7DPR8880MG457438G1YJR7BGRCG7J","DX73NQ14FVC978B1HP03MWBD83YT6NVP607WB126RAJPKPB11GSWB7H9W0QJSH9WD4SN9JJE6FDMQZTMJXCN1AKDBKKVPWW9VBEAE0YVCG64S16TXMSF5FTWNXYG7RDTHK03BXDGVXSPZFE7A2F6FPK9WCBBZSMKB8EJFGJK1PKKBHVA1Q2MX91NK32XPF00WFWZCC3N72JY2","PKSMQXMCT9H53FHGKVVPPTTHJ0M5X4SSR0XARN34VV7DX9ZA9N8379NVQKN4E5V6JXAPEVGPREWD720RZCVB90DDHRFW8GG6G434GR1A77SVPG9GKP2DEKBVP3CZ9WDMQD9RFZZ2Z3ZQ34YVD2J1Y4AZ52ADY5FXPB06W9NQ9NH7AQVHTPMSTP181MZM507M777SAPSCR9J8M","1XV578XYBQYYSV9V8ZRACRG3JTN103BZFPF5JXHF8YDCCCJM9XS343JYM8N6Q3J8D0NF2T8TPZKCB32MC1GXWZZRRWQW77JSSYXGA8136DNFYTCKB8EP277KBXAYXBW0ZX7S13J5NDGR7ZJ7QTAAPTEAG9MX6QBT9Z7ZZ1MECV7Z0D7GE7Z6KWASE5T4SW0MFHKVD1EAFNG0P","86PDN3KMT8AW8TVXEBGG3199YSEDMQX91QW82QVZ9TPYCNS3YKK3NCK5J64VKPKGJGQJP8E3RX97NZ4RDQ0PR21QS93MTWXSHKCTK2KTH7TQPKVQSWPGVC8CZDEGD9X484HJKMQG56RP1MZE7AWKK9C5ZK7MSN21GTG1KNF591Q8DVX0TM06BD8VGF6QNC745CFRMRSYHXK7P","KGXXZAECQ6DETR97HRYKFC4A1WAKC3S0FYNHS16J342WMPPP78NHWZXSEPN04XEQY6QNR9ECWZNXY6BZHXWAW2EV42295GBVA4R2J2KAK9EF7MR2789EHY0MVQJME1ADGEPGCQ02WHWT3C173SPDKW1DA5C8HBF847KV00A3F3ZM2HE18N5MFFQXBB014HK0MC4NW6G19AESR","92G786WTFN4BP3Z2RX1Q79ZPHGMPYC21K646XA8AB97V103DSJY0S4Y1FHHSHYQ8WZPWF0W07VY6CDG66438K8E8DWN7CCSB6R8KSC2VNAQBVC1ZD2H9VG33309B6QEKK5PYAGJZN5NG0CJXW9RFGPCNJJZW6KHM4TCMDJVKV9P953Q0TWVM2YZWK37504QJ41MZ4KXN52M30"],["NG0RYVSNPXP4B2NEYCTVC5A0EW5PFFW8V3PH9X5FFQ2VHCETMWDPZ75HBSC8QDBCWZ6EV9SEN1SMEMXABK3YTXBDDXYTN0JNKX1ME9T71JJTZZQ7XA1890DZR3ZAMD0XW8GZENDNAP4R5B80DQYFDEENSB141DZ0HG3A3FZA7JMCSBY0Y0PB2AXNS25Q6CQ5WA7GWCWA09PD2","R70NVRHFT6HCP69K8QQM9HQX0CZQYG09VV2VA7RKCVFDRTNH44XZHRBEJTBW1ASDS7WYA47PBVPB9T77B32ER6V2745Y8B1P22CZ5T70WTE6Y4HSS60CM5SFB6QT1VXTVW9G0DAC3XY2QDX6TKKQA1HX3RB0GPJH47D08KB1Y4EP1SXW851CFNEE373YJHBNYXMSE3MJBCQFP","JGZK4NS2E8D1CTR8ZAR5CHGN7T09Q41P545ZNNB7RMYV0CZ9CQWJWZZWRQZDRDCHV5PQS9G6WZB37CHQMZ7Y207D23FQAPMN6CP30CXYEQ449R3V7B8BYGJ84VYW60201B4343GG75TTTN947VVEYA3BWKRY4M5XCXSPAGW1TTAH9VAE8CBXBKAKJESX8XWS4W5PYBG8793TR","CEVHPNNSWR438ZWEK27YRYBKZXVBT8AM9QP2KXWC59ZSM8Q7J0GZNXZYBES04RB7BJY4D5YCPT3VRT7EPK0576809CG98X5SPTQPSR4TZ0R6DWMXH9893FCTVJX2A4H2EGSGP3Q6H53FD4CSDBMZFZVACXPMW1CR07A1GG7XFXSK1BP1BTGMBQD0818G6BC6SBFTXS1Z68PD8","72BFMSPJ57Q1B88JS6NX8YETQP0GSSRHXM25ZBGNW06YGYJ7FT1D7KVGF4PRKKQK8KN4MGHK5DXRQDTNEJSRZYGAN6DV5QAW9PACT7FQQXG6G4JS7J129VXFQP3DYTV0455Q37H5YPVGMWXZ4GNBQJ86HWWD670XY8JAR4WTYHX8FWJXZW57Q94KK79SD51HBJH1SJ4Q5C04W","2MDED6PYFV264D0SETN9MD4F0SADGVD75J8Y16DTSABNPW09AKE7A5F4QNB2124WDQ0HW4SGHCX26NAG46Q960Y01VYSX0GQHVKESSMEAZ2426GJ8KR0E6KTAXDZY6HZ6DRSPXXGZ07YZT2XF696CP8215HE92RGT7A50H5XXV2FDGFWJXYNCB7QND27ZWYWTK0KTCRYT104Y","267QZPCB0QGC63V9E75Y41J5285Q3FX6KQHM08MAKZ248X9WJK5H6H903C0T02W4G3EA22E0C1MHCWP5VRAEC8D0J0VYW9JNCH9YGPQJ3P941AADABVXG4M2K724C8VCFZQNXYJAMY4Q65WYHW6VYPN9H1K7ZABPAZX82SSSBPYKJZBREJ15MSS50R9HJ6FWW7T9P8ES015PY","7W8HPWAGBVFK0KTWG02RRTWKAE4W0PJH76GH9JWD7H48BS7CVD1T38QBX7HJVC6PJEAJ5NGP7MSJ9D5H1FD2T9Q5X4YQMQ1F80H4G8GRGXFKS863G48HN15D72EZSM9V8M75FXCH3639PHENNV35GA6TWPRS70HEJSBAX9A52DWTB63ZCSZXFGACZEQFGR0EKEE0AZB3HKE2J","JMGGS2DPDKNQKTM593M69Z1R331654KR6N12Q7EZPX20BMHV1P9JZZ8HNN6VHQ76YKWNMW9HR3173C2J66R8EVVWZAR001Z18F9HJ9YBVSM1Y94PJSM57GNVRNN0GH11A3ZRCA4WBKFC64A4RE6KTYMMJKJNHTR9A75QC7JWG5YY9D0J8TM9KXQW8495V702VS0E40121HNHY","REH6B17HSBJGMMPYKG7EA1J4G1D7H95N20P647AWHJKY3VA5BDZKN84FEG0G0FK6TKNN8D0TDFQ41610SK9CBR27SFD16PKEZ7AP97NB9ENPQRQ9PEKR76KB6VFWP666MJBTQJ5R7WGKTTE3ZN5Z2VWY2CPB437746CYBN52G2P2Q7V6SDGXV5HPYJBKXESS1JMVH3EHDC988","MRE9Q877Z18VMFNH1PWNH9N3Z5YE2SDZCVVFBZAA41SSRC2F81D6F9TCZP2AGYAEXWM3977KWJ6SV215ZJ24RAWA94NCKEFQ1CMW8MYJ063EP68NVVPH51SFVYQVHYC45MGNEXK61R9KFK6RWYV5Y6CHMAYZTW5M17WBSHBZ79F4DSWHVVY7774FX3WEEVM8EZ0WMJWY99A1P","2CM59NJVJ68NYX5ZJM2ZX7JK8EW7JXAVE0H2D1ZTX9X6X72P9768WK5GG3ED7MGEYGFQBK5YSSJ0DM68DRPDPTAHQGWTGJ7E79GAW96BXHYV64BQXXEV9YQ0XR84G8VZWQ8Z7T9VX7VQ0HHX1SAS3NGNZAH1H03KPFNAZMNMGV3H4Q3PC96JHGAP5JNDWA1FAFJ410REB33D0","2KEXQVJHACQX7V1XGF8PA6DV0EVAKFNSA037YJ3BP91NNQB60ZBAEFT9DKVW2M19R51B8HGQ2RCBSWBRHD0RFC95TYGJVKNJSCMQPXV7SM6A1JH18DFPWB1AJBS4SSVJXZ9V72TY2P8Q8ZTBMGCYERXCT4QJMASTQM1T22C9AXMMPZ1BHKNB2FBJ3TY9AQP4ZGHRSRBKR75E4","5Q9ZFEFF6C6XPGM78EXAWJF2CKJVJ0SPA113XT7R5JTRW7MKE70TETKK1MBRSTF566AEZB2CBX6CR10J6DPAQ92GC6XYK27A52KQGQHR5W346RGF28WE2PPC8S3XWTGCGVP318AKKTC8GZQ62FRYZVPTVF07HWYHDESQFKYNSDRNV8Q5KPGRQM27SK3CX14RA2K9KZ0NJJ0FE","7ZK7H90WBS080M9TH8XH9DNENKQ78YHYSXWF16VRM76YDKP4RG97DEM1DBX84VXH98M64CJS1GYTDKG4XA8XCNSZ4KQ12GAXCJC168DBR8Q4M29X2Q70WNR7BZDNH0DQJMR154M0C1NH2EHJBKA76PNZT2369PT9Q4961EBCY5FE85EKCX7584GXBMHQF1GRDVCCDVY7TH210","CFSSPFE27VKZHPS7D48AAHE0C2XKXS774CDPFE530P5C4EFZVZEP1PTS3NYB7KBNHNAGMZX8YZJVTNJAB1DE7GCRG43Q2JVS15RGJC4K9PDA0Q2MG4DGRX1CM28HE8CB1XWVW7MCDJHPNE28NWN0CYHDES8GHAZ6Y50NNZRN31FV8C28V0PRNZK4WFAPJ4EGV5NQPHGSZAWNG","9XJB29QY06J1DYBXQRV9F7M3T11JTGSZHX8D0Y80Q07M646VTHNFKNA19CG84BA2DKRE1D5DS3M72QH29S9EZEADW6PCS1FXGCMGZ0RQXT5ASSX7VBQ40NGPYY6PFE6CYV45YYEGPGJ2DCQCNYP2Q9E78SBD53QTQVYZG7W3QE8EASEPSZXP1VVB1TYYHN0B1BZGY2JKGWWPY"],["CSATAT8AQRB91XKHJD1B7MK1DPVPV991WZM4Y2NF0G8SNQSR391KBDGHR0AWGZ8V6EYXKNGTACT9GRBWWAPYTDPWFZ5QPY9GY8Z4YDXRK1QW30YT6JMAYQM2TH36XP81G2X55785ZJWCRXZ9QQDGGMMM8QZTWN9TPW8JXH4XM8A0DPG5354PF3782GVC8J8DXD7KY88KT0FZY","4MG8W9X1M5361HRRG6W9QF13EQDRFWVAQGMWJKEESZ3FBCWP7QQGQBBDT3ZYE3XARBVGJAVFQRQPAYA1PC4CQYRX0CZTS0E4QZB3ACMXG9XJ91VPP7X0ZG08WXWMT02M9V4HCHJ7MJQZ7EWCRJAG7E14BQ465R123P6QFGY770GN0BBNT040S3XPC88CD8X3D8497DE2YA1MT","DCSBAHV955VGACX5WE2N02QZSPQ9N375DCS0W3298N8KF59YHVTS7FYJ1NE011VWF8ET7F6SQZHW1NWJZW3XYWN2ZMWXCRGH80CD6N8YA6EE8Z2N2PW4T65M0DDX4YFXZPG61E7JJGK03VTDW5J8WS2F2R88GNNJMWBQCYBWWTZ34H7J5XAPBBY3AF4M6A8C0RAHPX0FS044W","5CKW4V17Q2XAV54F07X0C2NZCRZ21B9M79JM37F10DZJ3YMY9WPEWSYEDC6DP1QZ2DRRAXDCFG1WZH3C0Z58Y26XGSC28R9Q3ZFSKZHS0F0DC08JWY01W9GPECCN0CP0Q19N7GWX7HAFVR0PR89P0DTBPJ02NKCG14HZK933C6C62AFS8GFWNWN7FJ042YVSH69BAM9FQX4JT","G3J5ZS034N9TKESCEBQJQVXJMYB7HX48QT7QTEZA688019KSSCFVDG2TD963JA9NQF2G80Q3J1853PRB8QD2WFWDSMPP9X4FXKAEH79Q2X8K2KZJ120848KY421H81WAA1AMPZ6APD67RDRBKM0H5GN974TBQ8S5ENFRJSNTJRJEDY57GRHAN8V2R2PCNCY1VWH92EN1MJA4P","BFTC88T9TMD6EQPMBN6M7ZES14AGZMK8CYF0BQ7JAPPAPKXRTW83Q2ZF5270PG9WN0SET02R2HHFEM4J66GDWK2GYCF6GBZWF0GAH9ZC0AHVT3JW2XWDZ2X1DH2WB9Y10A7QB83S3E1S5DQEJXD6AS4J6HCBJBZJPC1EJZP3E5R5ANY1EYZ7P1692WSNABA0KWZS5T05DK51M","HFBFB0NVHM82461FBYT7SKWR1R38QD6MPMTSZXQAAH7CKVSKE21M4KZMWE3EF2C0FFJ6QGZPFWYXM58NANQMHP293WNJD5NYM31ZGV3ZS8SDP9DK1ZVSF5WXW3PEKYCAR3C7J6QZKAT1CVQF86P6M3S7XPVN8EV212SG4ENANH650EF7H5RDBN29PRREWB5JA4PSX9TQR1Y92","1JT0M6407RNBHQFZ0SKEPHQW2ZTZTQ5X7BZ1Q1FBQVR9QBH3EP20CH1KJSP71PC8Z4WFKJ97442BWG991RJC0XE7Y71PW9SXMPX4MG1SVN371ZG3GNCBX3Z9AZAAR9PGHS46AVG1RH71X1WQZ7W9HGQPRYDPCMQBQ7N2PD0AH8EJMT2ASXKPNK09S9H9B0ZTYMB5KK2VB7C72","E5PWK8V063470P271354CCZ3JS5H6GB8GWPDCDB125CVT4YHR3MGTYAFTDJP38F2WSNERY5KNE9VK85N70WC6VZ22WF7MABK2FJZM3179NHBV92FQC19BR21002AZHHE4A171QYFX69NXV3XA90HY27ECQ28YCPZNKEPQXXVCNHVSFJJE8553M68KM81EW45GXEV3797W2KX4","JQGN8RXXYMHG8MD2GSNQ6NQ8QCH8A6WDT0S0WY4ZP5424RGY975300CQC1C575K619FY2JN71RQ62RM6W2TBVZX0Z5B8A2PQ2MTN6W486AHBJKX6ST9HA05EQ9VMJN3PGDSF1GACENFK8CA1EW4KNG185PG6CP7YY1TNX3FZ43H020K8SYTQWMPM6FAZ5N4MA6CQND1ZJ6AC0","NJ5PM2Z4VNYKG6X93KWH0GQZNMBKHYM2FWX0BSNPGJ18Q7R32YC9FPC2Z45MPJ5EZY4E3FD0CD67TB9K4879SQ33SCG4Z9BARNQVWXMK975GRGAB4306D1W7PF1QD1MTVW79DHY3M2VVC01R4T41M3TXYTD1X3JF1PBGC4T63MF9MJS4KZTS03Z84AY6C9WK9RTNHXN7R0VJC","1ERPPXVG2NJ4RHCD8QED1E25R6QCAKKZWRV3EAVS4Y17ZDTXK46TSBJ1MF4HN26K3FHRE3PVBV4DA1RJ36R70BSAHSW7KTEGJNR8M5FBJ7ZDT907X2H0RMPCDANGGT9Q8VWYKBAHQ1T72S0ZAEC079SKV16XWTRX698P3P2TCJQDT7EA0G67N82YFPRE7YVGFKTMYXYC7EH1E","91CHR983Z222JN1695J11YY836RHP6J02SSZ8T5ACZ15X0D1YAW8VTCMECW3CPMCHG2RWTS5C30HSRWTYHAE3J3F61Q5N34ZSBVFPWJMN8WSFTAFEG7WZDWCM1VJZX5KZGXF7RTCS09MHJ0WRMCGC18WSR2SDV64P9HE1WTNCD8KT65JGQ3ZPWAWVJNJS63YNTGDPZ4RHTZ78","8Y2GMEETX3N9XTAB124JYKMHQH29009WJ5KF6V31WWZ99GXWZJHC5C03P3CKMB4R8M7ARQ3XD7AHPJTY2EKKPPF9QXWN7685HDTPT2SBCQ67XHHRA2RJ5E1S4VFP2YB1H0W1D9RE4C6WDT7FNVWXFHXAEQAJRJ577QNJD8G4ZSARSJ2SP6625AW9VQVVRFS0FWMBWJZNHXGWE","33MH8RQ5PXQFB0Q2T0H30VM9G4J0ZQHB3JHH8MWF5K3F3KDBAR20ZG4APHCTF1XRG530W7K2476P7BNFKENP2WXJ4HAW6RS91EQKGYD6PEHRYCVF8JJTMWR32WFVPV5Z2M1FWKNNC5N4CC3MK7S2K1V05PQMZ347KYD45FW7Y4KKJJ89TDPT7SFSQP1A0J00SN3MMTSEGXMVC","HR05CWNNQ5VBAF0WNHP8DZGN3S0DGYJ37EYP8S57MK8KDXQHXTPV2SXNJ2M5SZMS43BR168FJTB1SC9EW9P9YT7DYRBRM7G0VSD86P4QBG42NH06XYCCF7TVN9H9EB5K1G07M2Y3TT4WBAZ2Z350PV67ZANG43054GZA4N08BN2WEDFN94BNFKF45C10C3FFG77D9P3PDMGVG","JS43E6R3YEVTRZGRTS3RF0F9PCW7NBTWB6XEV146AXNTSKEKCDQ8CX76GX6HA06N0RTA699M99FXMD4JX04VY961YXJ18G96Z1AEEJ5BR5A8GYEWSNJSR5Y55ETBCKHBHR6CJ8A3659Y45FNG7H4C1K44WZ520PE4NAQVJ9QS1H2RWQ5ZZ8H8J8WGBDQC6DQYRTQHR0WF2156"]],"link_encs":[["7A1HVQDZ7C2M4K913020KMYGK4K86PM30APZ8AH5ERT5Z83QZPJRTGEFXF0TGYH36R1N36N3VKB5V845FK33ZW3R4G01X6CZNE37XHXE707T0TA4DYGYNX295QSVF5VEPD8QMK1J7ZAGDS27QP9H1QVK3NRBTZVRZ23XAV642CGEFS1ZMPWEDSHWECQ4G6CFK1V0K2118BQYS41R9P05NZ18PY3Z1FWNZH472HTKWT74KC752S0W1C2ASM428009","VN0WT37PVBAEYW3GH2ZGZ6X37R0XATMMGHJ86G953NVD6TE608Z3A6P9PG9XAFAGPQ13GSQ435C8763NR5T28RQHH4JB9N578TW7FHA5Z4Z6MDS6CMFK9A6ZGGT35G28NH6YG55EZR6GCM5E2ZKA8A9TR8Z8C6N4BTEVJCCJRWQ5MG8W4TR5N3YNPA7NAM49TYECT56BHJPCD6DHJ3XQF2XTZQTCECDJWZG4TXK3DEZM7Y55XFJ49C7F1P17MWQJ","88RWAYX9765MBR4KBM2BN9NSQ5KHWFC0AQZ6B0RF617VF5CSJJZBB69XEVDFC12K5D0T06KBE71BHK9E5ZTEWD5FK4Z2JVKD6WV6QBFHQ8Y1ZPP7Y2EYB9XFW7S9NVM27FNX8RKWTAJ45FFYKJPNR7MX5HZZ77B25ZB57Q0NJB1E7ECH4QP5BKZG63CF5M75VA633AJC7A4XWR8JJTTQDZ40KJMXE15X9KPABVZM6HSGF19P3N2SE2VSSWFXF3XH","MBPTJC2E7C7YQMAKCJN8EYBX1Z0CG8Z35G5VW3AZH3PAM6DF32AXKFX06Y9686R1CC1TP5BWX94DMN78RXSZGXFTEKPK3RSEVWQZCBR6BWB7YEEJZTV0C5MGREJE73RP40730074SPPW6CW665XV5TJA5329ZM0XC0KJ3MR9V70TDCE3M9DMB1JXPN3M97A74XG6KNJ57FKDQH4C4NV3Z3MJ276Y9MDAPDF7GM7MW7AQ7T1Y98RJ4H6163WCSN6V","Q2YNH71E69EDZ1NF8PFRS66PJYZ360SPQVQK73422944P34FGDZVJPMF59Y5KVJSB8VTJRPVMBJQX5Q8M8BHEJX1YAHDXAAWBTTBFM12CACZT72CYH6S80TJ8GYGS9ED1HBBWWT8RTZGMD8R4NMBJJRED107EASZJTH4SW32FJ04X62EA7YAMAJFTPCC5E6QJ43F0H592Y3J81NXQS66P6GQXKQMCN5W3FX8ZJ3160V542YVE2FNV3X7WD88FVX3","5H1650VJX95VFVA0G8ZNGWBCACCZ53D6SNJ2S4QVD27X2128KWC4MXRH8B3FK89QXV07NTEAJEXGDZC8BSYW11JZGF7H3D2YVXX8ZEEZBHWESCN7TNSE9AQK5NBV709PNHP9KHE0K7KEJTY2GY08CWAN1G9CQ3ZPF71NEHC7C0C47GYNVHX9PTXAYH2ZKQJSRNNSERC62CFX7NQD5TPF6WFEYC9R1AB3TWEQQF3FKY5EAE04EAEQJWSW0ZFGRCZZ","F5DZP7M59MGX6YJ3M3CEXVSJ0FGD3Q3EVHSQDSDDKSFPJ7ZBN60SYEBB293PDCFZVKC7K4HZ54E3XM7G3GECK2ENZYRCQMS6PS7VM7DYKF7S56KP0N4SE6X69Z6EDA309XPHHPDGRG1593SZ63YN63M815C2JP9M5E7H4FWYWQQNA9C81FQFNH48NVF43G3A54EEM3JV6VKB7X9H3SESC7YRA862NZF42C2FP5MCSACPQBSX0HV2Y6QRZW53BTW2","HJ9AV53HDRMNPEJJPSEPJV98XXVK1D93WT9CQNNY7B0GKY2TTAW55C83EHJQ6SHMRYEWQPBS096ZKRDFHNM5YJF09V0NFEYTTJGSMFCDYKW5GYCZRH3FGZHKEMNN3S98WV5KWDG5PT0S9TCYTDW074WPNMTH9V9KE08E6N8ATBX62PZ2X326TT3N15RG3ZKHQ09360981ANE6MVMTM2J4RXPFMA936KTXAS0EF4FTTGZA1GHS4FNG0JRFBXTNF0D","BXGHTGZ5EWJY0XKATMGECZB8QM74WH1RY4FB4KZP0VQ4STWVBY0NMQ8832YWB04GQ2B50Q5RTD61091HH1R3S2DK6QD7VJYE9BZ4EEQJTDTSSQT86DQEK2QPTXQ8W39WBTAT8PKBT037JM9DCYAC4CRPGTQWBZK3M1Q8BZHRVXYVGGVC5GWG3YQX075EQCSD9Y18N08JKNFHJKVZH6X27ABDHC323HQS30VS15BD65GZ35MH4HBFT7CAHPDZ5C4D","D3EF8ZKVNK6PCTC7KWGDGTSA3804J3N76R186NKQC3YJPW9P9B6CXDN9910S5RNEGST7MSY566PFZY2NP95WZNJTMG8988H3HFVXTXK2TST59AFBNDNTGAK3Y149WVWNDAVZ3G4NM9R75HSWC75BGNDRJ1F6DQ8QACZRAN9E4K6J3NZ3BNG5A2JY48TVXEC3E6TEDMF3E4Q9QSWQXPEHE48TJB5J7Y98WTDDWWV3S186BGM5V06PTGS3V2QRMRW1","YXS4AQF8X7VR35BBA8RBRMCJCSRJ1K2820ERJV75TZ5X3XBB4S5A1EKAGQHRF6KENMX4GEM3NT30K20FD9W03B79AKARV0KK6SMW0W1DY68KWGF69JH95RCHXCSVVV8XGVC48CBW1ZBVPFXS1BEJRDBHJV5229R5CZ9AXC3DNSS7S2QYENQZT7ZSKM9VKYGN604GT55SXD0NXJ0QCVT6TG8MTGTTKSM9D1DFKGDRMP251FCEZVG4XGZSVHKGVHGY","2QYSG97X93Z5TR1JT9FJJG05T1ZKZCPKZ4FNTBNF3DT2WPJR12QF1EPB5NSYQK068XFRKK46YGTT5GX6QEJJZ2B61A35RM30HNTP9S794V2TSME26S4RG32M6AYCNZFR6YEXBKF828ER3HMS1XAF8H475HQSM4M0004GN2S2EHMBXR62TYBEYE20GB123X5V9B31W9650S1E833Y6AH8SKQRE5JY1AK0NATPP6DBKWRC3FTEV6QBDVDGB8B3FKVB","XHTD1E2BZMY311GRBP3PJ6TNJ5S9MMWYZEPFKBF98X6Z3B9GK8EKYZ9DMN0CR9JYQ95KVWETVEKGWMF3V9VT3KK29WGDBPCX1V44J09E2AA1V72A6Y6B6KXPPESB3SM2TH0DZZC1AYQG7TNAMWTQ028ECEZAW30DE3S4ZWKE78ZZJVKP4ZXFXWN3JTRY2TX112B3TRYH94GNYJ1JKGHVG55ESYQTFN7VPPW8GMRSJRVNJZ09S4P37G21HBH46NCK","2N7GXE2T60XCGFPMTS8YJZ64G6STTNPQ4R31CCRJ9Q3F7KJK9RNW4JAQ0VP0A0SCQGR12HJ09NW0TMWKFP46XHQWRJP9WMQA8TXPP6F1SR9XQX7A8KB73EGH3BPN8X5N7YY95M7ZBD1F4SJK24HR0P0GV6BMEEQFTE86QGTP67Z7KC1JPANYM1B3MX9Z9CVESD83WX4VG459X6YR2HR8T576M0EV5K73P9397H25ZSP0NXSAMGY87R0DCQM1GFR3","7WKT07H36FAWM8W3X54AN2VAGJC5DWKP2MJMHES1NA2VFVJD3WAWJJCNWARWP17SQSEWS38HF36A63BNR5Q0Z2R7VETVASYVEYRFQYFGW3KXCVT5AS2ZHJCDMWRJ0N5EQXP0J7MNMCCCQZ8F304SKWWMAQJ8EB5Z7KKM1T0Z8WQHZACR8YTATF4XSXSSJVPGGCXNC47B11QM4RN3HDGXF9MBZZMKASBZRF8AA2EH4M1VFG8NR243ZE3ZKK3818C7","FKZQB344EWN58WPNVZ0BR5A7D30ZR58HZN0PQPVJBKWK3F4A4DPNEV3Q8QXR70E8V5WT4JX8AG9H3X5RZ74DQPKH7RT4M4747NY98P4J09R58JP7BR58KA8Q0AHS6069GHNWC0SX9GF33RV9SA5ZYA75CV37V1N5GRAPX6A0SWR76B6HYSPA94G5PY02R9B05W8Y5MDWDEY70NW4ZXWG2ZH3S68SSNP6SASAJV85KMH6CH7FZXTDYQTAR0HDB4K7","3073VTHEC9679DTAPZZ2J52N4PXFJ2B5JDQQ4FEPWCDFWMTXJK4KTSVMSF6DNMY8BH3EJDS192CK16JZG95JGDGYXMYBAFCY4X77AGA33DZSNXT32VWNVKKP6PN5E7JWRDJT8SSCRJ6C90P9YZ31SFF4PPKFPSVZFRC3PD87CKT8M9D6XDWN61D0PMXKMWGJVSNQDR8NP86MJYE8KGHSX0SWDV2J9N3X9212CXV1JDQNS3DFJ7XEX38EEKWQDSJP"],["8HRTEYDE0W9T678MM8X20QADH4XHBW7JTHJVFDJDANF90K84P7FK7TN0KJGE5BW52VQQFB10YNEQ8S9J3WVRV229RSMC9FKNRJ60968J536EYP24C5WNACGATG6AG8N4YNDQNQQM8V2KQ6DX2XVEH3Z86W9ZYKW7SS4V4B2WM1ZT9WA80SM1H73H9JN62GMMQE37XHVQZ212836WQJMFFKR29ZSYZAE45FJ14AQMP6YKF56M4F1PSATNFZ62VC87","R2CW58HWX89YHVRJ8WYRJ1A2GSC24H1BQW38VGBXP0MXYZS939BWPG8KQM4ED9M19QVXKZ4RC21RN62EC7E1SSAF5VW0BFBGER1FWW3ZHJ78FFF1TNJGRVBE2PF18W1ZXMATHZ12RRKDPSYT81YTGRGAYZ71GHYD1SKYD711P2J80ZTKC6YCWJ6172Z14VWVFA6H8JWWA8VYZ4XTARPXZRWR1Y1YE4232XYZ8X6A8HHWQXS5ZNSM0WPK8BW0KSTA","DE3DY2F0TCKEXVV65EHTWFKKAB2ANTDS435K869BXNVA8NQH4JA6X3WM0H5VAVFT3RCAVQ1WYCRWZ2PKDABAFEXF8RP1R4ABX04ZZS4XPB9K4R1T5KK2HX7GMWQX8RZ16Q40C44DJ70YTHVBD0BM6AF2R5G86SRZ8KERPS21RMPYWYBBWM8DR5YS1D3HM6CEJE6ZX6K398XJGK59E3FCHYJX0B2SCRKJB5E5G1S6PAB3QXKZ2SDGGQR7F8XSESZR","ZNXFM7CDW0M2XRNBTD1GM4FDG74G2XDMTF53N7R71J93YPMPXTT4G6CVZM9YZHCK6GQ35P9CHEKNWK9Q3VESZCYR7TDMMQYYFBNPPM8E4CVHNHFZSBSAHP6MMSWQJ07GZA9FRW7ZGDH6ZNEWNVMQK0V8GV0035C8PP2SYMH66FWMA24E3D15RMJSF4C817TD7SD4F7WG6RCBQ6Y0DDHAY4VW1TB5V4W55J3N0GEMX7AJ98T0TSZQRS6R9PSP1JA2","2J8G5KH2TYWDYHA2KX2CEPW58CRDHK05YX5TER84CGAEBAQASD9Q0ED8R67VBVMDQE2C9NG7V4T3QZ2VRPJPYGQXFPCXBQF99FXK1GBMEGCVP8Y4S4TTN8NJCRBTY6CXA03KQ00JKVJMVSWA85969C2F8YAG8GS5TBPF3D8ZT2C56D2ZD65ZB7F0E11S7B2RWWES7TXQ7HKXDWEBFMYTC2WNGZT1NET0JG21MP03KPS6BEMAX5Q5B139QJNCW8F2","K1605K54DR7EXXPBSWWFNJMXQE6QZS3BN3WWVBF2Y83WD42NMB9FZJG0HXPXVGVJTV1EFEXBAV7PY28FAXTVJHAZ06CDD13ZF7JE12D1BN6T6AT4RHN6N00A2ZJQTE1VCWGBF49Y5B75ZXNS7F9K352AR5A5ZXG21KV7VNT2ZPJ2B1YQG11QY05W920TEGSC8TW0J41HN035ZHCG8KBF5QN5RMGKSVZQV708K7DPVYM7NC2V7X2ZHZ44CBCPEXHD","MGQFD9DMYZD61RHS6KFGC5YGRG2Q56CYNX4GYBGASQ1P69N76M6P4CA3WZV163VFR71JPAXPK11BFCVVCS3A9E59WAR6R0ZWXXN5P9XJXD7DSA1PWCS0JVJ851BCMPWE54M23JGHNRJ03VE76HW8ZH45B6WGG809T8T2HQRBHBPFN3WC9NSE7NY07YWH6V3MFMVPQ2GZJ6QJP4C9D2AGM88H71PD0H65YZY07HD72F3VV1QBJX6T1GPY3NYPEHH3","KWJQX6FGX9XTEXEXYVWJ657TGZHYWHX24KEE1XA5TW5NVVQMQGQVC6HE6GA4RBC1MM0S1GWN7Q838QQEP5DQNDZS7VDBSEP7SPHC832WRN492FN8CEVSJ01AAE36HRXNM8DN6JPK160DJ4M842W9STXQRWPPF94S6HER2WZX330EZ9CECD2NKWTPTYSDGA5FVZS2GC9ERQGYP4YKZWZ8DXRJWCNVE1G7BYYPWNTHPVX0EW5F9GHT4MR29YG9YMRS","50FNFP9CQNHME6EDC0JFNXDVHW58CMC0TJRRZ0FGAYK0MAYQ9EV5WZEYBDV5Y967H72AQBJ28GXD394BDW949KGEMNSAWYE3YX5GSZZNZ9CCXEDKJJSC7W1P1Z04B9VAAM07SPWN4EMREMR9R4XXCD014YHPMMKP64C58X93FCSFJNW8XDF5FW0S2KZJHSP8FGRNQ6D62CQJCVPS615D4N52XNPWTFS1R379GVKSZ4FRR6SRTT4R3PA271HTX3ZQ","NA6QQDXM6SB52138M8CN3E4ZY7P0P9QS9AJ6EGNKYYK58XTRR4D4BANF0ARSTVZGMSJ7FP803B87J21SR2KT35V5K4FEHWVQ9WB9F9ZFMREFAA3SP3NWXNYJQQS0YA2K3RRAWJYJ2XSPQERXW71702QAEQRZQPMGXCMDKFNY9V7Q77147RQ7M87G8ZHZHX83D9C6QHKWGKTSQ0R66MZRCX29C6P6A5EGHACWVXD32Z8SA1ZPNJBDZY0PNJJM68D0","4M00E1KQVQ5KTJPXTCT3EYFN1SH975ZASNAWN7Y0NB12ANR6VZ1163GKCK9WBQJ34Q9P0G763PQ0B68WCJT2KXYXX5X3AV91BPDGGWBYB220HQ52V5VES6H7D75KYS176MA27G09X9PAGTQJ78VQHDAZCYZ5TS82D7HM5GT618605NVEYTYQYT3BA38R6V0RHYGE0CHNEXZTM0TFGDXQZ3P6CTNMYPPPM4E4PMCCQC4XR2S6CGVWYE1TXN0DC59S","N98TSJ6HGBS2N3K3MRPW6WEHBP409B4DC7PW8GMHM2QM2VGC0XQE1AQQ9AT1J4MZAK0B3RAMWF74XZT0D1CBDFABDY8M2M3X6QM7M4C8Z2DV7B2KHF3GBV4FMEG2PWAPW97QJ47383BT5XEYC4KQNXNK1K22NXYQJK0W5GS64KRFZWWKENZABG8VYZ7AYDAXMPVRSCQCCDE1RS6V8MDAVP65SAB59NSM0QQMBAPVBDTWD0P3HCZMG4F17QGTYZYP","ABT20WXHNAHYANMGCVGR9VC1QEQMS5YYRMMPS8GE04RK7XDF4CFXV9HEC1TAN77PBZJXAGX8GVZ8Y0068T84M1DQ90F2VEZGHR1JBBJ91S6RER8FKC301STT9MSF0CYBQTG5GAZK948305FBT6QRFX5BYS4NB5ZX86YMTNZ01V5C26W6Q6T78E5MHC7PH21SQ2VERSMDBWC640068HDX4PCJWBV7809DBSHR2ASJBMM32C49SG4EYVW74H4385QA","M0HBDKZV4TAYEBJJFBMMV55735T3F114RQMHBW2FRTDNNEM6NVBYR65Q25GJ1XNZJV0J7CY55CWMNEARHS2F9TYZPM0BVFZ429DVB2BTX8ZJ6Y9M1RSMVZZZM3GVE865PFEYCVG40AVC5T01Q8XGW1FDZKZTXBVCR5J7H24870S3BJB820X7MQMXDHNNT7VA4XZQJRN96JNHFC3RWB8R2X6Z4BZA9AYETNHP180MY0CJB8CJ8P9DTFHFRAY4M8WE","HV2FVXRE3NGW0WR4XMX0QZX2Z0B7MQB2ANCC95GY0FY9WD74D6CSM11YC0HNMBYCX8CTX7QJS28FGK9FC2RP79M8MKBGHHP0E3SK33AHRQ6457TSMARYKRY2SNSTV7JC8Z1KPV55EH5EH9C736G42J9W84E61JRWG8PMD24V1TS0MJZWKNP1QKSET7N40VAJXZE6B2QDPH2D7F26S4SND52B8KS1HB0NH762F41QE22ANVSXV6S2TN73Y5MXTB9S","70M04PP2WVXGK3NJCMPXGQQCCCDTRW0AF5F8N8A605DHY4CKBFFXX9F5Y512JS3CY6MD9T7K1X0ANT3N5VJRWPF9Y1S97EA2NAZ2W783DEP32Q1M3Y93VBHKCBQNM9M456VSMZA16E8SP3EGPJ9K7QVE0KYV0VWFJRXCA2CWDNNFDEKEJ6GK164SKGZ5MV1QQEDM258SKTQQ0EER895QRAA50PHNNQF1Z8MGHMZCV9WP0ZSJASVK63E4Z073TTVD","799TV24HVN9JDQBG0WFGA5FH0RNJYFJG2XF1B93W5W571CRPMEAHCAKA294YT63VV0K7C46AQYF1AVS58ZJ58CESDYWMY770P95EXPM5H38NV3F3RDTDB7EX5Q9XN0X8QRW37BTN8VBQ4PZ6Z1Y4539C7QGE2F012RBKGK47BST1TQP3XA427H1CDGT8HJBHBY8BD4TH1N7EE0XZF2QV6AJ0JCPWD0H6TKPVMGH1QYKVEKEPQBX20S8K502AYGKQ"],["23NQ8CW42D7FAY7FD7636SFA7THRQA2EGJYMFYF75FA1ZATVSMPPYSHTEC58J84PKDHRD0KPFMK811CTSGW0W274D8A3E5FKEX6P5AZ2ST0K9PK65ZP6R6CR8RMWSGQQX5VAJ2YVJ3RNV20C7YJA0CDCJNQX3JKGRX5NRY8GY3RNA38HG3SM0NW85AKZ0SXP1XRZRY0A0T3SM6E4Q6PTJMJZJ677BRZHXAGB7BEMSK6KDERXHVN9AGN4J9SZ5AY0","ZNZFHJ4X5T79G4DXBX8TPG0RV8SV4B7ZKV56CAN893MZ6Z00CNFPH6KPPXTKM8MB6MTPSCHEA0PRGRHSAKPBSAVR019GB9E75TFSXV452ZQN8Q54ZH9BJPKMAMB26X8BDQKGYK01FAWRWZXJ1TW9GVPJ9TS03WKQS64TVRNH17TTFVED3K8MFMDC9SFSJ4FZC2J19XTSWASXSY4BDE4T8Z2VGWEGDTYF0PNPMHDN2FQN2T25T6TA2DW702HM9MBC","SCC6Y84J99YQ598PR67G5VDT1N9E6S2VEZ22QWD7PQDX108ZZJ2AK16YF9PS8WSAP1PCPM0AYD1R9522XZNGWEHX92RE3R0WVM43A38450XH0EHY6XYKCK0NWJJ9XKN0FG4GACRF6X1WG4Y7AYW6FBFMBKHWDHP8BCTT0J9Q9XXZEBC88C57DMG18ABP6S248JAX9JCN0GZ4Q25AX6XYA2W2KD7CW97H23H11NS47XMC0GFKF7FDZK11JQXASJMT","PJBV9WQ064WKS9WVV0JWN3JHGNCY82T42FC0ANKNV5SEE8BBPJTQ6HXM76D8FAH00A6GNB2HMRYAAS0ZTGESRE2JFDD5HFAWW2EX9BN1QA7T6EB1G4PBFFZXVD3AN3GZKK72A4VT5AFM9QF7GHEXVWC29AHH5HH3ZHYQ1AR5TB5G7CNRCM8GA1Q61P574H0G2DWJJ35XE79D3G6SEWPCPB3VZS89B3XY9A1HV86R6BVY6C48D7FHXEA903AVVSMP","7HS65FGXB1Y1E39DHB5J0SRX8KDS7K6YBNP1XAPBQQNBM8N2T1QAVVS37XG15A2ABWK9J2CP16WCWNMG1ZCS6KEY98DXRNKNEQ099M9P392D00R8HDE1Y8C918PV0DVXKM3141CYJFXTQCYM9A5ZYKDEXNF09NTWHNDT3F990R6EWYS0TT4F4YDW9605SWTPJ2G29CKQNP13N1KKFRJS4BME8VE800YH6HTCSWTPXQ19TFXCHET6PF9EZE00RF9D","39HPW4NZCE7T1TF3ZFW6FGSSSETD2NMY5ED7T49572WAZ5FBJJSGGK8G45CQSSS488KZ2WRVM2N79ZKD78AX08X7TAW7DGWKB5KX9KRFCAF7ZZJQP5W6A1652XYNV2NWR51MS30ME0Y6RPXCMVC7K2B3HK5TFQ893HE8JJJSD2TF3AS9RGMPRBXXPCTC1R3Y5MZJQ79RKV70PVW65MA00RPMC27VGBWW5MA7FWK4X8JWQ8PHB9666MVM64AZJZSC","WFPTT5ZYQZNF2TYKSMFT6D7RP85980PTXNP6PS1HY3J5Y4ZY8DEVNPGFDP85SQ2DST4QQ1EHQQ9CHP9T1Q25XSGNKAHF9QAXX6N029SNH3C0MTG5VAQWQMCSPAYBW08EKFJS560JXX1AT9JCD0CJXZTX1P7TC4XD1JJCBDYKQ592CJZDKBCVMDN2CAG7RNXVSKQ8EWCRM7NCEF3YVWBJ7SQRE9B3BB067SVSRJQ9SB0PTGRP93WRNQ2JEGC584K8","P0VHPX17WERWA5G19P5NV2ZBWXGX3YWFTYHMSWQ2C5H82TPQ08BNDPE813NYWWT231EMTY7VRW2ZS8S447T4K5BCD7S0YQX2D92G5VRP10YTC57P16QZ37T0QZ9JENVPT6665TZ97JERST3M8SG7BD3ZDM5H0S9KCQTXXDJCR9NB1WHNJFTYZ0DJYJFDNZMTKREQG3R5VF5DQXXWKJ2GVKZG6ED2VTJR11MKYTQBMXSKP50RJYEZMVCZQ8H5KZBV","VDS1SGX5A31KBPTBPBR82C4ZD5FG2XRDM5KGVAR7X8XGXP7ZMGREXS9CYFKA9SZ3GMF0H7X4H733C81HPNDDC2NPF4443CB0KY0XSMJ34SA6TY99SE0NTZ96RZ36CZP77HRQQ93WC2EN3FBGS9WQMZEQ7GKNZSH6RBGXMF237ZGRN4FPRGX771VXY5DQXFMPTFYRDHCPWJR9FZKF7TQT0MB1K412PRFHSHM9S8FZJCHYR2PP443WF9SZWGNM2CV8","J8KEHBYBAQVMTR1XTNDY23VSTQVW5CYE2EDNRE9DBP56DV1PSC2XXFH8KKHXT8D3TM5G6MCHE0JWNHN6417G696MHY5NZ6CRAYYBNR4YBPHDF94CHBMZSXH6KD3N0AVW3F0KK88DPA9G2CZ8C82QTV8Z8C8WRRWAHRH0X6SEGK4BJPEB3WEGRMD9AFYTXCPQ0N7BXK83052V4HD6W7AB0AE7EQ789XF6C8AD4TN7ARF7MJDMTCY624B6QR5K15Y5","R5D4QJ0HM2A9E8WEVZYKG9V612D1ZRRB6RCGK4S8MDBM7NRBP7DDJGV1H7SN0JEZJSTW7DGVA7WRK0Z8HSH6VGB2NZ2CRZKZTTX15W58EB2XKJKZJYA0AR5MKTNHPP7D39PFF8BP39PJX9VRFHGXMWB84GHVMND6S2M2WWVHMBVNK2D19TJ1240NZFV6GP64416JT0QW06ZQMXVHVD4GFRESTSVBH1G78AMJ972RAHBF4DM0PJXY0PTXYWQSP2GZ","KZ0R0MPKXYKS13894TAC8SMNGYQ9F1EB9VN9S4P7VJ8KDXJEREYR64HDRAP4HBRY9D50YDKDPE9AHMW9ETR4CXK3E0YQJQ2YGPAWJ823GH7KHJXK1GTFSZ3TERT1VB31FGXC0950TY0MAHESMPHNRXSG9738C0CEJ5Z1TNBVV48WJCH2JPCKAPDMTSFYFAECZ28WJ7J9EW459NNT5H4D0DB5ZZ2CY16CCC8PBC0K4ZYP1Y2ZW5MK0N0MJTXGQ49T","YVH178SVB7QK1HM2HVSDGBZ1QJN9NRTG3RXMC9YVS74VXTW719NRFMAXKJHVZJE77HNRW53S8DSD3QTYDZ9ZNZ1185GZXP0NNP40NQFYJQSBHWVE7WDPR5KZT42BWQE1P20B3F5HCHV3VD1ZCGCZ1NF5K5P4WKT01K7XKXNGFTX9HY5YAG30THBY4S3Q3CPMQBVCBXDHW929DHRBZWHZGGGBCTVNRRNDA7J0EVBAVYYHES2PDK9YW5STCG83S5AB","MA1RKAT9SCJSXJG7T5P1QF1VW9GRBCYK03P8Z55KSCK4SN1K0MFSXR3YERD4VA5QQ1N7KKHPDAPNYHQ6QW2TPBVM932A5G42VEKZ9CH5ZH7346RKDT8BMNSFYQ799W6042CCJ3JSESFYMER8JHZNBVFWFGMKMJJ642BCP9PPA9H7B0XSSGK2CZYWK3RQX5WKZK4V40AMTRJ9JRZNX7QKXRN9W464J3X3BRYHR8Y70C42HH0HVW86CPNVKWE0Q36C","K8CP0BM60F6P218ZMC4HW307DNN3FHHEG3XMWN55KEZR4RQ86ADXGJF2FKD7QW2TVQ3AWN2KVJ6QMP6SFJHF11C6T68FWKWMQVGQWYY28591XTQ3D78FB9N06T2W3R6EERTHRQNK7X9WP2BV3VM43A39YXVJ0W0BHJ0XSZQENN218ZR4GTVDX48788WXFD6064ZXGMWGD2EKWMS4BBVWK59FVZPGDRQD6EN3TRA784HBBWE831JWBEVX3YGPX9T0","QC2WWZPKJDCFZA65QKSGKW7YTAS9RP0W4H0FP96Z5TGCCD0K9FXFX4F75MQEDNKCM0B34D8ZR46CHR97HMMEB3W2T8W6P8X5289Z729365HVDQWCGHMJSGM7AHA1HS6ZND695X67D42H7XB7NQ5X57XG2S32R767YPQJERZ0ESG3S01NGNQ0P62KM3AT28JX41GMHPEA92V1Z80RYS8GWE9CGZ6K66NVGW72SN8DJGDFVC7771HP93X6DYN0TF63","H3P6CNRKH4BSCAWG3T9RRA89EBF1F91JEYEY4RZJQ2NNCZZASY362XHME0KNM36R4SNQD956CBW15XAXVK70TZM6TBVMHJ8KKQQQKBG4Q91ZHKAS10NEVD5B36QGYM7CMJM3DVJ00KTK0ZAM8C4YRNZDZKV5CNFGDR0DDP1P38704Y3G9KFRJ455SD39J7ZYDB5GQV17ZF2PE7GY927S1KSXV4KKXQ7FRX1VZRJGAD1F3DY4KJFRZJGAT5951JNS"]]} diff --git a/src/lib/baseline/refresh_reveal.req b/src/lib/baseline/refresh_reveal.req new file mode 100644 index 000000000..3fb143960 --- /dev/null +++ b/src/lib/baseline/refresh_reveal.req @@ -0,0 +1,7 @@ +POST /refresh/reveal HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 255 + +{"session_hash":"V97SB8T670M9V71D1Q0KVQ4GSJCVQ5AAKTTH9QKT0ZJZZBFNZAV4NA8NMWRRGVPFEBEGB6ANCN9BPQASJ40TM4Y1C49648TJJ07PGSG","transfer_privs":[["EQKJA401A9NJ2YJDFZJ1EV8AYXBHWZB6NT5T0TWSJHVKVDM6W8A0"],["TKDJ4DF3GZVG0DGAB9E3RGBGSTANYB6JVVWXJGPMB2AY4VQNTBA0"]]}
\ No newline at end of file diff --git a/src/lib/baseline/reserve_status.req b/src/lib/baseline/reserve_status.req new file mode 100644 index 000000000..4f988f669 --- /dev/null +++ b/src/lib/baseline/reserve_status.req @@ -0,0 +1,4 @@ +GET /reserve/status?reserve_pub=TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0 HTTP/1.1 +Host: localhost:8081 +Accept: */* + diff --git a/src/lib/baseline/reserve_withdraw.req b/src/lib/baseline/reserve_withdraw.req new file mode 100644 index 000000000..484950250 --- /dev/null +++ b/src/lib/baseline/reserve_withdraw.req @@ -0,0 +1,7 @@ +POST /reserve/withdraw HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 919 + +{"coin_ev":"Q5X8A8TCBFH7E5BMY7HSB17SHFTM1JPJGV61P2CA7Z9EXG8P2HYS69B31NZESKXHSZHNJ2DQN3CC2AWFNC6V90J577JD3TXBMAY8Y5M9V60KKT73Z1DW24JFSNAK91G1F2WT55ADP1EG7N5F9AY7A7ZJD03MPYSH0RDP7SVZS2KRPA5JRHFR4GDJ59CFNE7A43M95ZKQHQAS8","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","reserve_pub":"TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0","reserve_sig":"8427B3RTB217124EB1C37ZVJFC08KN17RHGHE9ENZQMQVJ0S11SAX6H8Z06SWCKT06DRQ9DQ8XD786XKQ94T27PYR9GC9EMT1Y02W10"}
\ No newline at end of file diff --git a/src/lib/baseline/wire.req b/src/lib/baseline/wire.req new file mode 100644 index 000000000..a4f1d0749 --- /dev/null +++ b/src/lib/baseline/wire.req @@ -0,0 +1,5 @@ +GET /wire HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/lib/baseline/wire_sepa.req b/src/lib/baseline/wire_sepa.req new file mode 100644 index 000000000..80d3d4619 --- /dev/null +++ b/src/lib/baseline/wire_sepa.req @@ -0,0 +1,5 @@ +GET /wire/sepa HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/lib/baseline/wire_test.req b/src/lib/baseline/wire_test.req new file mode 100644 index 000000000..684352c96 --- /dev/null +++ b/src/lib/baseline/wire_test.req @@ -0,0 +1,5 @@ +GET /wire/test HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c new file mode 100644 index 000000000..6b0aa6ff6 --- /dev/null +++ b/src/lib/exchange_api_common.c @@ -0,0 +1,353 @@ +/* + This file is part of TALER + Copyright (C) 2015-2017 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_common.c + * @brief common functions for the exchange API + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" + + +/** + * Verify a coins transaction history as returned by the exchange. + * + * @param currency expected currency for the coin + * @param coin_pub public key of the coin + * @param history history of the coin in json encoding + * @param[out] total how much of the coin has been spent according to @a history + * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not + */ +int +TALER_EXCHANGE_verify_coin_history (const char *currency, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + json_t *history, + struct TALER_Amount *total) +{ + size_t len; + int add; + struct TALER_Amount rtotal; + + if (NULL == history) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + len = json_array_size (history); + if (0 == len) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + total)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &rtotal)); + for (size_t off=0;off<len;off++) + { + json_t *transaction; + struct TALER_Amount amount; + const char *type; + struct GNUNET_JSON_Specification spec_glob[] = { + TALER_JSON_spec_amount ("amount", + &amount), + GNUNET_JSON_spec_string ("type", + &type), + GNUNET_JSON_spec_end() + }; + + transaction = json_array_get (history, + off); + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec_glob, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + add = GNUNET_SYSERR; + if (0 == strcasecmp (type, + "DEPOSIT")) + { + struct TALER_DepositRequestPS dr; + struct TALER_CoinSpendSignatureP sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &dr.h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("h_wire", + &dr.h_wire), + GNUNET_JSON_spec_absolute_time_nbo ("timestamp", + &dr.timestamp), + GNUNET_JSON_spec_absolute_time_nbo ("refund_deadline", + &dr.refund_deadline), + TALER_JSON_spec_amount_nbo ("deposit_fee", + &dr.deposit_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &dr.merchant), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + dr.purpose.size = htonl (sizeof (dr)); + dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); + TALER_amount_hton (&dr.amount_with_fee, + &amount); + dr.coin_pub = *coin_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT, + &dr.purpose, + &sig.eddsa_signature, + &coin_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* TODO: check that deposit fee and coin value match + our expectations from /keys! */ + add = GNUNET_YES; + } + else if (0 == strcasecmp (type, + "MELT")) + { + struct TALER_RefreshMeltCoinAffirmationPS rm; + struct TALER_CoinSpendSignatureP sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("rc", + &rm.rc), + TALER_JSON_spec_amount_nbo ("melt_fee", + &rm.melt_fee), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rm.purpose.size = htonl (sizeof (rm)); + rm.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); + TALER_amount_hton (&rm.amount_with_fee, + &amount); + rm.coin_pub = *coin_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, + &rm.purpose, + &sig.eddsa_signature, + &coin_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* TODO: check that deposit fee and coin value match + our expectations from /keys! */ + add = GNUNET_YES; + } + else if (0 == strcasecmp (type, + "REFUND")) + { + struct TALER_RefundRequestPS rr; + struct TALER_MerchantSignatureP sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("merchant_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &rr.h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &rr.merchant), + GNUNET_JSON_spec_uint64 ("rtransaction_id", + &rr.rtransaction_id), + TALER_JSON_spec_amount_nbo ("refund_fee", + &rr.refund_fee), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rr.purpose.size = htonl (sizeof (rr)); + rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND); + rr.coin_pub = *coin_pub; + TALER_amount_hton (&rr.refund_amount, + &amount); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, + &rr.purpose, + &sig.eddsa_sig, + &rr.merchant.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* NOTE: theoretically, we could also check that the given + merchant_pub and h_contract_terms appear in the + history under deposits. However, there is really no benefit + for the exchange to lie here, so not checking is probably OK + (an auditor ought to check, though). Then again, we similarly + had no reason to check the merchant's signature (other than a + well-formendess check). */ + /* TODO: check that deposit fee and coin value match + our expectations from /keys! */ + add = GNUNET_NO; + } + else if (0 == strcasecmp (type, + "PAYBACK")) + { + struct TALER_PaybackConfirmationPS pc; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &exchange_pub), + GNUNET_JSON_spec_fixed_auto ("reserve_pub", + &pc.reserve_pub), + GNUNET_JSON_spec_absolute_time_nbo ("timestamp", + &pc.timestamp), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + pc.purpose.size = htonl (sizeof (pc)); + pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK); + pc.coin_pub = *coin_pub; + TALER_amount_hton (&pc.payback_amount, + &amount); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK, + &pc.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + add = GNUNET_YES; + } + else + { + /* signature not supported, new version on server? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_YES == add) + { + /* This amount should be added to the total */ + if (GNUNET_OK != + TALER_amount_add (total, + total, + &amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + else + { + /* This amount should be subtracted from the total. + + However, for the implementation, we first *add* up all of + these negative amounts, as we might get refunds before + deposits from a semi-evil exchange. Then, at the end, we do + the subtraction by calculating "total = total - rtotal" */ + GNUNET_assert (GNUNET_NO == add); + if (GNUNET_OK != + TALER_amount_add (&rtotal, + &rtotal, + &amount)) + { + /* overflow in refund history? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + } + + /* Finally, subtract 'rtotal' from total to handle the subtractions */ + if (GNUNET_OK != + TALER_amount_subtract (total, + total, + &rtotal)) + { + /* underflow in history? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Obtain meta data about an exchange (online) signing + * key. + * + * @param keys from where to obtain the meta data + * @param exchange_pub public key to lookup + * @return NULL on error (@a exchange_pub not known) + */ +const struct TALER_EXCHANGE_SigningPublicKey * +TALER_EXCHANGE_get_exchange_signing_key_info (const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ExchangePublicKeyP *exchange_pub) +{ + for (unsigned int i=0;i<keys->num_sign_keys;i++) + { + const struct TALER_EXCHANGE_SigningPublicKey *spk; + + spk = &keys->sign_keys[i]; + if (0 == memcmp (exchange_pub, + &spk->key, + sizeof (struct TALER_ExchangePublicKeyP))) + return spk; + } + return NULL; +} + +/* end of exchange_api_common.c */ diff --git a/src/lib/exchange_api_curl_defaults.c b/src/lib/exchange_api_curl_defaults.c new file mode 100644 index 000000000..45aa36b9f --- /dev/null +++ b/src/lib/exchange_api_curl_defaults.c @@ -0,0 +1,75 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_curl_defaults.c + * @brief curl easy handle defaults + * @author Florian Dold + */ + +#include "exchange_api_curl_defaults.h" + + +/** + * Get a curl handle with the right defaults + * for the exchange lib. In the future, we might manage a pool of connections here. + * + * @param url URL to query + */ +CURL * +TEL_curl_easy_get (const char *url) +{ + CURL *eh; + + eh = curl_easy_init (); + + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_ENCODING, + "deflate")); +#ifdef CURLOPT_TCP_FASTOPEN + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TCP_FASTOPEN, + 1L)); +#endif + { + /* Unfortunately libcurl needs chunk to be alive until after + curl_easy_perform. To avoid manual cleanup, we keep + one static list here. */ + static struct curl_slist *chunk = NULL; + if (NULL == chunk) + { + /* With POST requests, we do not want to wait for the + "100 Continue" response, as our request bodies are usually + small and directy sending them saves us a round trip. + + Clearing the expect header like this disables libcurl's + default processing of the header. + + Disabling this header is safe for other HTTP methods, thus + we don't distinguish further before setting it. */ + chunk = curl_slist_append (chunk, "Expect:"); + } + GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HTTPHEADER, chunk)); + } + + return eh; +} diff --git a/src/lib/exchange_api_curl_defaults.h b/src/lib/exchange_api_curl_defaults.h new file mode 100644 index 000000000..7ebf4e966 --- /dev/null +++ b/src/lib/exchange_api_curl_defaults.h @@ -0,0 +1,41 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file lib/exchange_api_curl_defaults.h + * @brief curl easy handle defaults + * @author Florian Dold + */ + +#ifndef _TALER_CURL_DEFAULTS_H +#define _TALER_CURL_DEFAULTS_H + + +#include "platform.h" +#include <gnunet/gnunet_curl_lib.h> + + +/** + * Get a curl handle with the right defaults + * for the exchange lib. In the future, we might manage a pool of connections here. + * + * @param url URL to query + */ +CURL * +TEL_curl_easy_get (const char *url); + +#endif /* _TALER_CURL_DEFAULTS_H */ diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c new file mode 100644 index 000000000..55b3ca6b3 --- /dev/null +++ b/src/lib/exchange_api_deposit.c @@ -0,0 +1,597 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2018 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_deposit.c + * @brief Implementation of the /deposit request of the exchange's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_auditor_service.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Deposit Handle + */ +struct TALER_EXCHANGE_DepositHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_DepositResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Information the exchange should sign in response. + */ + struct TALER_DepositConfirmationPS depconf; + + /** + * Value of the /deposit transaction, including fee. + */ + struct TALER_Amount amount_with_fee; + + /** + * Total value of the coin being transacted with. + */ + struct TALER_Amount coin_value; + +}; + + +/** + * Signature of functions called with the result from our call to the + * auditor's /deposit-confirmation handler. + * + * @param cls closure + * @param http_status HTTP status code, 200 on success + * @param ec taler protocol error status code, 0 on success + * @param json raw json response + */ +static void +acc_confirmation_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const json_t *json) +{ + /* FIXME: clean up state, some logging on errors! */ +} + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param dh deposit handle + * @param json json reply with the signature + * @param exchange_sig[out] set to the exchange's signature + * @param exchange_pub[out] set to the exchange's public key + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_signature_ok (const struct TALER_EXCHANGE_DepositHandle *dh, + const json_t *json, + struct TALER_ExchangeSignatureP *exchange_sig, + struct TALER_ExchangePublicKeyP *exchange_pub) +{ + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("sig", exchange_sig), + GNUNET_JSON_spec_fixed_auto ("pub", exchange_pub), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = TALER_EXCHANGE_get_keys (dh->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT, + &dh->depconf.purpose, + &exchange_sig->eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 /* #5447: replace with "for all auditors, if auditor selected for DC notification... */) + { + struct TALER_AUDITOR_DepositConfirmationHandle *dch; + const struct TALER_EXCHANGE_SigningPublicKey *spk; + struct TALER_Amount amount_without_fee; + + spk = TALER_EXCHANGE_get_signing_key_details (key_state, + exchange_pub); + GNUNET_assert (NULL != spk); + TALER_amount_ntoh (&amount_without_fee, + &dh->depconf.amount_without_fee); + dch = TALER_AUDITOR_deposit_confirmation (NULL /* FIXME: auditor */, + &dh->depconf.h_wire, + &dh->depconf.h_contract_terms, + GNUNET_TIME_absolute_ntoh (dh->depconf.timestamp), + GNUNET_TIME_absolute_ntoh (dh->depconf.refund_deadline), + &amount_without_fee, + &dh->depconf.coin_pub, + &dh->depconf.merchant, + exchange_pub, + exchange_sig, + &key_state->master_pub, + spk->valid_from, + spk->valid_until, + spk->valid_legal, + &spk->master_sig, + &acc_confirmation_cb, + NULL /* FIXME: context! */); + } + + + return GNUNET_OK; +} + + +/** + * Verify that the signatures on the "403 FORBIDDEN" response from the + * exchange demonstrating customer double-spending are valid. + * + * @param dh deposit handle + * @param json json reply with the signature(s) and transaction history + * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_signature_forbidden (const struct TALER_EXCHANGE_DepositHandle *dh, + const json_t *json) +{ + json_t *history; + struct TALER_Amount total; + + history = json_object_get (json, + "history"); + if (GNUNET_OK != + TALER_EXCHANGE_verify_coin_history (dh->coin_value.currency, + &dh->depconf.coin_pub, + history, + &total)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&total, + &total, + &dh->amount_with_fee)) + { + /* clearly not OK if our transaction would have caused + the overflow... */ + return GNUNET_OK; + } + + if (0 >= TALER_amount_cmp (&total, + &dh->coin_value)) + { + /* transaction should have still fit */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* everything OK, proof of double-spending was provided */ + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /deposit request. + * + * @param cls the `struct TALER_EXCHANGE_DepositHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_deposit_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_DepositHandle *dh = cls; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP *es = NULL; + struct TALER_ExchangePublicKeyP *ep = NULL; + const json_t *j = response; + + dh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + verify_deposit_signature_ok (dh, + j, + &exchange_sig, + &exchange_pub)) + { + GNUNET_break_op (0); + response_code = 0; + } + else + { + es = &exchange_sig; + ep = &exchange_pub; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + /* Double spending; check signatures on transaction history */ + if (GNUNET_OK != + verify_deposit_signature_forbidden (dh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + dh->cb (dh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + es, + ep, + j); + TALER_EXCHANGE_deposit_cancel (dh); +} + + +/** + * Verify signature information about the deposit. + * + * @param dki public key information + * @param amount the amount to be deposited + * @param h_wire hash of the merchant’s account details + * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) + * @param coin_pub coin’s public key + * @param denom_pub denomination key with which the coin is signed + * @param denom_sig exchange’s unblinded signature of the coin + * @param timestamp timestamp when the deposit was finalized + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed) + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. + * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not + */ +static int +verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki, + const struct TALER_Amount *amount, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_DenominationSignature *denom_sig, + const struct TALER_DenominationPublicKey *denom_pub, + struct GNUNET_TIME_Absolute timestamp, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct GNUNET_TIME_Absolute refund_deadline, + const struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_DepositRequestPS dr; + struct TALER_CoinPublicInfo coin_info; + + dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); + dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); + dr.h_contract_terms = *h_contract_terms; + dr.h_wire = *h_wire; + dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); + dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); + TALER_amount_hton (&dr.amount_with_fee, + amount); + TALER_amount_hton (&dr.deposit_fee, + &dki->fee_deposit); + dr.merchant = *merchant_pub; + dr.coin_pub = *coin_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT, + &dr.purpose, + &coin_sig->eddsa_signature, + &coin_pub->eddsa_pub)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n"); + { + TALER_LOG_DEBUG ("... amount_with_fee was %s\n", + TALER_amount2s (amount)); + TALER_LOG_DEBUG ("... deposit_fee was %s\n", + TALER_amount2s (&dki->fee_deposit)); + } + + return GNUNET_SYSERR; + } + + /* check coin signature */ + coin_info.coin_pub = *coin_pub; + coin_info.denom_pub = *denom_pub; + coin_info.denom_sig = *denom_sig; + if (GNUNET_YES != + TALER_test_coin_valid (&coin_info)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); + return GNUNET_SYSERR; + } + if (0 < TALER_amount_cmp (&dki->fee_deposit, + amount)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("Deposit amount smaller than fee\n"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Submit a deposit permission to the exchange and get the exchange's response. + * Note that while we return the response verbatim to the caller for + * further processing, we do already verify that the response is + * well-formed (i.e. that signatures included in the response are all + * valid). If the exchange's reply is not well-formed, we return an + * HTTP status code of zero to @a cb. + * + * We also verify that the @a coin_sig is valid for this deposit + * request, and that the @a ub_sig is a valid signature for @a + * coin_pub. Also, the @a exchange must be ready to operate (i.e. have + * finished processing the /keys reply). If either check fails, we do + * NOT initiate the transaction with the exchange and instead return NULL. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param amount the amount to be deposited + * @param wire_deadline date until which the merchant would like the exchange to settle the balance (advisory, the exchange cannot be + * forced to settle in the past or upon very short notice, but of course a well-behaved exchange will limit aggregation based on the advice received) + * @param wire_details the merchant’s account details, in a format supported by the exchange + * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) + * @param coin_pub coin’s public key + * @param denom_pub denomination key with which the coin is signed + * @param denom_sig exchange’s unblinded signature of the coin + * @param timestamp timestamp when the contract was finalized, must not be too far in the future + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_EXCHANGE_DepositHandle * +TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Absolute wire_deadline, + json_t *wire_details, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_DenominationSignature *denom_sig, + const struct TALER_DenominationPublicKey *denom_pub, + struct GNUNET_TIME_Absolute timestamp, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct GNUNET_TIME_Absolute refund_deadline, + const struct TALER_CoinSpendSignatureP *coin_sig, + TALER_EXCHANGE_DepositResultCallback cb, + void *cb_cls) +{ + const struct TALER_EXCHANGE_Keys *key_state; + const struct TALER_EXCHANGE_DenomPublicKey *dki; + struct TALER_EXCHANGE_DepositHandle *dh; + struct GNUNET_CURL_Context *ctx; + json_t *deposit_obj; + CURL *eh; + struct GNUNET_HashCode h_wire; + struct TALER_Amount amount_without_fee; + + (void) GNUNET_TIME_round_abs (&wire_deadline); + (void) GNUNET_TIME_round_abs (&refund_deadline); + GNUNET_assert (refund_deadline.abs_value_us <= wire_deadline.abs_value_us); + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + /* initialize h_wire */ + if (GNUNET_OK != + TALER_JSON_merchant_wire_signature_hash (wire_details, + &h_wire)) + { + GNUNET_break (0); + return NULL; + } + key_state = TALER_EXCHANGE_get_keys (exchange); + dki = TALER_EXCHANGE_get_denomination_key (key_state, + denom_pub); + GNUNET_assert (NULL != dki); + GNUNET_assert (GNUNET_SYSERR != + TALER_amount_subtract (&amount_without_fee, + amount, + &dki->fee_deposit)); + if (GNUNET_OK != + verify_signatures (dki, + amount, + &h_wire, + h_contract_terms, + coin_pub, + denom_sig, + denom_pub, + timestamp, + merchant_pub, + refund_deadline, + coin_sig)) + { + GNUNET_break_op (0); + return NULL; + } + + deposit_obj = json_pack ("{s:o, s:O," /* f/wire */ + " s:o, s:o," /* H_wire, h_contract_terms */ + " s:o, s:o," /* coin_pub, denom_pub */ + " s:o, s:o," /* ub_sig, timestamp */ + " s:o," /* merchant_pub */ + " s:o, s:o," /* refund_deadline, wire_deadline */ + " s:o}", /* coin_sig */ + "contribution", TALER_JSON_from_amount (amount), + "wire", wire_details, + "H_wire", GNUNET_JSON_from_data_auto (&h_wire), + "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), + "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), + "denom_pub", GNUNET_JSON_from_rsa_public_key (denom_pub->rsa_public_key), + "ub_sig", GNUNET_JSON_from_rsa_signature (denom_sig->rsa_signature), + "timestamp", GNUNET_JSON_from_time_abs (timestamp), + "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub), + "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline), + "wire_transfer_deadline", GNUNET_JSON_from_time_abs (wire_deadline), + "coin_sig", GNUNET_JSON_from_data_auto (coin_sig) + ); + if (NULL == deposit_obj) + { + GNUNET_break (0); + return NULL; + } + + dh = GNUNET_new (struct TALER_EXCHANGE_DepositHandle); + dh->exchange = exchange; + dh->cb = cb; + dh->cb_cls = cb_cls; + dh->url = TEAH_path_to_url (exchange, "/deposit"); + dh->depconf.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); + dh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT); + dh->depconf.h_contract_terms = *h_contract_terms; + dh->depconf.h_wire = h_wire; + dh->depconf.timestamp = GNUNET_TIME_absolute_hton (timestamp); + dh->depconf.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); + TALER_amount_hton (&dh->depconf.amount_without_fee, + &amount_without_fee); + dh->depconf.coin_pub = *coin_pub; + dh->depconf.merchant = *merchant_pub; + dh->amount_with_fee = *amount; + dh->coin_value = dki->value; + + eh = TEL_curl_easy_get (dh->url); + GNUNET_assert (NULL != (dh->json_enc = + json_dumps (deposit_obj, + JSON_COMPACT))); + json_decref (deposit_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for deposit: `%s'\n", + dh->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + dh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (dh->json_enc))); + ctx = TEAH_handle_to_context (exchange); + dh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_deposit_finished, + dh); + return dh; +} + + +/** + * Cancel a deposit permission request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param deposit the deposit permission request handle + */ +void +TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit) +{ + if (NULL != deposit->job) + { + GNUNET_CURL_job_cancel (deposit->job); + deposit->job = NULL; + } + GNUNET_free (deposit->url); + GNUNET_free (deposit->json_enc); + GNUNET_free (deposit); +} + + +/* end of exchange_api_deposit.c */ diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c new file mode 100644 index 000000000..9743b1f09 --- /dev/null +++ b/src/lib/exchange_api_handle.c @@ -0,0 +1,1779 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/exchange_api_handle.c + * @brief Implementation of the "handle" component of the exchange's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <microhttpd.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "taler_auditor_service.h" +#include "taler_signatures.h" +#include "exchange_api_handle.h" +#include "exchange_api_curl_defaults.h" +#include "backoff.h" + +/** + * Which revision of the Taler protocol is implemented + * by this library? Used to determine compatibility. + */ +#define TALER_PROTOCOL_CURRENT 2 + +/** + * How many revisions back are we compatible to? + */ +#define TALER_PROTOCOL_AGE 0 + +/** + * Current version for (local) JSON serialization of persisted + * /keys data. + */ +#define TALER_SERIALIZATION_FORMAT_VERSION 0 + + +/** + * Log error related to CURL operations. + * + * @param type log level + * @param function which function failed to run + * @param code what was the curl error code + */ +#define CURL_STRERROR(type, function, code) \ + GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \ + function, __FILE__, __LINE__, curl_easy_strerror (code)); + +/** + * Stages of initialization for the `struct TALER_EXCHANGE_Handle` + */ +enum ExchangeHandleState +{ + /** + * Just allocated. + */ + MHS_INIT = 0, + + /** + * Obtained the exchange's certification data and keys. + */ + MHS_CERT = 1, + + /** + * Failed to initialize (fatal). + */ + MHS_FAILED = 2 +}; + + +/** + * Data for the request to get the /keys of a exchange. + */ +struct KeysRequest; + + +/** + * Entry in list of ongoing interactions with an auditor. + */ +struct AuditorInteractionEntry +{ + /** + * DLL entry. + */ + struct AuditorInteractionEntry *next; + + /** + * DLL entry. + */ + struct AuditorInteractionEntry *prev; + + /** + * Interaction state. + */ + struct TALER_AUDITOR_DepositConfirmationHandle *dch; +}; + + +/** + * Entry in DLL of auditors used by an exchange. + */ +struct AuditorListEntry +{ + /** + * Next pointer of DLL. + */ + struct AuditorListEntry *next; + + /** + * Prev pointer of DLL. + */ + struct AuditorListEntry *prev; + + /** + * Base URL of the auditor. + */ + const char *auditor_url; + + /** + * Handle to the auditor. + */ + struct TALER_AUDITOR_Handle *ah; + + /** + * Head of DLL of interactions with this auditor. + */ + struct AuditorInteractionEntry *ai_head; + + /** + * Tail of DLL of interactions with this auditor. + */ + struct AuditorInteractionEntry *ai_tail; + + /** + * Public key of the auditor. + */ + struct TALER_AuditorPublicKeyP auditor_pub; + + /** + * Flag indicating that the auditor is available and that protocol + * version compatibility is given. + */ + int is_up; + +}; + + +/** + * Handle to the exchange + */ +struct TALER_EXCHANGE_Handle +{ + /** + * The context of this handle + */ + struct GNUNET_CURL_Context *ctx; + + /** + * The URL of the exchange (i.e. "http://exchange.taler.net/") + */ + char *url; + + /** + * Function to call with the exchange's certification data, + * NULL if this has already been done. + */ + TALER_EXCHANGE_CertificationCallback cert_cb; + + /** + * Closure to pass to @e cert_cb. + */ + void *cert_cb_cls; + + /** + * Data for the request to get the /keys of a exchange, + * NULL once we are past stage #MHS_INIT. + */ + struct KeysRequest *kr; + + /** + * Task for retrying /keys request. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * Raw key data of the exchange, only valid if + * @e handshake_complete is past stage #MHS_CERT. + */ + json_t *key_data_raw; + + /** + * Head of DLL of auditors of this exchange. + */ + struct AuditorListEntry *auditors_head; + + /** + * Tail of DLL of auditors of this exchange. + */ + struct AuditorListEntry *auditors_tail; + + /** + * Key data of the exchange, only valid if + * @e handshake_complete is past stage #MHS_CERT. + */ + struct TALER_EXCHANGE_Keys key_data; + + /** + * Retry /keys frequency. + */ + struct GNUNET_TIME_Relative retry_delay; + + /** + * When does @e key_data expire? + */ + struct GNUNET_TIME_Absolute key_data_expiration; + + /** + * Stage of the exchange's initialization routines. + */ + enum ExchangeHandleState state; + +}; + + +/* ***************** Internal /keys fetching ************* */ + +/** + * Data for the request to get the /keys of a exchange. + */ +struct KeysRequest +{ + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this handle + */ + char *url; + + /** + * Entry for this request with the `struct GNUNET_CURL_Context`. + */ + struct GNUNET_CURL_Job *job; + + /** + * Expiration time according to "Expire:" header. + * 0 if not provided by the server. + */ + struct GNUNET_TIME_Absolute expire; + +}; + + +/** + * Iterate over all available auditors for @a h, calling + * @param ah and giving it a chance to start a deposit + * confirmation interaction. + * + * @param h exchange to go over auditors for + * @param ac function to call per auditor + * @param ac_cls closure for @a ac + */ +void +TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h, + TEAH_AuditorCallback ac, + void *ac_cls) +{ + // FIXME! +} + + +/** + * Release memory occupied by a keys request. + * Note that this does not cancel the request + * itself. + * + * @param kr request to free + */ +static void +free_keys_request (struct KeysRequest *kr) +{ + GNUNET_free (kr->url); + GNUNET_free (kr); +} + + +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +/** + * Parse a exchange's signing key encoded in JSON. + * + * @param[out] sign_key where to return the result + * @param check_sigs should we check signatures? + * @param[in] sign_key_obj json to parse + * @param master_key master key to use to verify signature + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + * invalid or the json malformed. + */ +static int +parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, + int check_sigs, + json_t *sign_key_obj, + const struct TALER_MasterPublicKeyP *master_key) +{ + struct TALER_ExchangeSigningKeyValidityPS sign_key_issue; + struct TALER_MasterSignatureP sign_key_issue_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("master_sig", + &sign_key_issue_sig), + GNUNET_JSON_spec_fixed_auto ("key", + &sign_key->key), + GNUNET_JSON_spec_absolute_time ("stamp_start", + &sign_key->valid_from), + GNUNET_JSON_spec_absolute_time ("stamp_expire", + &sign_key->valid_until), + GNUNET_JSON_spec_absolute_time ("stamp_end", + &sign_key->valid_legal), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (sign_key_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (! check_sigs) + return GNUNET_OK; + sign_key_issue.signkey_pub = sign_key->key; + sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); + sign_key_issue.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)); + sign_key_issue.master_public_key = *master_key; + sign_key_issue.start = GNUNET_TIME_absolute_hton (sign_key->valid_from); + sign_key_issue.expire = GNUNET_TIME_absolute_hton (sign_key->valid_until); + sign_key_issue.end = GNUNET_TIME_absolute_hton (sign_key->valid_legal); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, + &sign_key_issue.purpose, + &sign_key_issue_sig.eddsa_signature, + &master_key->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + sign_key->master_sig = sign_key_issue_sig; + return GNUNET_OK; +} + + +/** + * Parse a exchange's denomination key encoded in JSON. + * + * @param[out] denom_key where to return the result + * @param check_sigs should we check signatures? + * @param[in] denom_key_obj json to parse + * @param master_key master key to use to verify signature + * @param hash_context where to accumulate data for signature verification + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + * invalid or the json malformed. + */ +static int +parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key, + int check_sigs, + json_t *denom_key_obj, + struct TALER_MasterPublicKeyP *master_key, + struct GNUNET_HashContext *hash_context) +{ + struct TALER_DenominationKeyValidityPS denom_key_issue; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("master_sig", + &denom_key->master_sig), + GNUNET_JSON_spec_absolute_time ("stamp_expire_deposit", + &denom_key->expire_deposit), + GNUNET_JSON_spec_absolute_time ("stamp_expire_withdraw", + &denom_key->withdraw_valid_until), + GNUNET_JSON_spec_absolute_time ("stamp_start", + &denom_key->valid_from), + GNUNET_JSON_spec_absolute_time ("stamp_expire_legal", + &denom_key->expire_legal), + TALER_JSON_spec_amount ("value", + &denom_key->value), + TALER_JSON_spec_amount ("fee_withdraw", + &denom_key->fee_withdraw), + TALER_JSON_spec_amount ("fee_deposit", + &denom_key->fee_deposit), + TALER_JSON_spec_amount ("fee_refresh", + &denom_key->fee_refresh), + TALER_JSON_spec_amount ("fee_refund", + &denom_key->fee_refund), + GNUNET_JSON_spec_rsa_public_key ("denom_pub", + &denom_key->key.rsa_public_key), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (denom_key_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + GNUNET_CRYPTO_rsa_public_key_hash (denom_key->key.rsa_public_key, + &denom_key->h_key); + if (! check_sigs) + return GNUNET_OK; + memset (&denom_key_issue, + 0, + sizeof (denom_key_issue)); + denom_key_issue.purpose.purpose + = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY); + denom_key_issue.purpose.size + = htonl (sizeof (struct TALER_DenominationKeyValidityPS)); + denom_key_issue.master = *master_key; + denom_key_issue.denom_hash = denom_key->h_key; + denom_key_issue.start = GNUNET_TIME_absolute_hton (denom_key->valid_from); + denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (denom_key->withdraw_valid_until); + denom_key_issue.expire_deposit = GNUNET_TIME_absolute_hton (denom_key->expire_deposit); + denom_key_issue.expire_legal = GNUNET_TIME_absolute_hton (denom_key->expire_legal); + TALER_amount_hton (&denom_key_issue.value, + &denom_key->value); + TALER_amount_hton (&denom_key_issue.fee_withdraw, + &denom_key->fee_withdraw); + TALER_amount_hton (&denom_key_issue.fee_deposit, + &denom_key->fee_deposit); + TALER_amount_hton (&denom_key_issue.fee_refresh, + &denom_key->fee_refresh); + TALER_amount_hton (&denom_key_issue.fee_refund, + &denom_key->fee_refund); + EXITIF (GNUNET_SYSERR == + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY, + &denom_key_issue.purpose, + &denom_key->master_sig.eddsa_signature, + &master_key->eddsa_pub)); + GNUNET_CRYPTO_hash_context_read (hash_context, + &denom_key_issue.denom_hash, + sizeof (struct GNUNET_HashCode)); + return GNUNET_OK; + + EXITIF_exit: + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; +} + + +/** + * Parse a exchange's auditor information encoded in JSON. + * + * @param[out] auditor where to return the result + * @param check_sig should we check signatures + * @param[in] auditor_obj json to parse + * @param key_data information about denomination keys + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + * invalid or the json malformed. + */ +static int +parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, + int check_sigs, + json_t *auditor_obj, + const struct TALER_EXCHANGE_Keys *key_data) +{ + json_t *keys; + json_t *key; + unsigned int len; + unsigned int off; + unsigned int i; + const char *auditor_url; + struct TALER_ExchangeKeyValidityPS kv; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("auditor_pub", + &auditor->auditor_pub), + GNUNET_JSON_spec_string ("auditor_url", + &auditor_url), + GNUNET_JSON_spec_json ("denomination_keys", + &keys), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (auditor_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + auditor->auditor_url = GNUNET_strdup (auditor_url); + kv.purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS); + kv.purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS)); + GNUNET_CRYPTO_hash (auditor_url, + strlen (auditor_url) + 1, + &kv.auditor_url_hash); + kv.master = key_data->master_pub; + len = json_array_size (keys); + auditor->denom_keys = GNUNET_new_array (len, + struct TALER_EXCHANGE_AuditorDenominationInfo); + i = 0; + off = 0; + json_array_foreach (keys, i, key) { + struct TALER_AuditorSignatureP auditor_sig; + struct GNUNET_HashCode denom_h; + const struct TALER_EXCHANGE_DenomPublicKey *dk; + unsigned int dk_off; + struct GNUNET_JSON_Specification kspec[] = { + GNUNET_JSON_spec_fixed_auto ("auditor_sig", + &auditor_sig), + GNUNET_JSON_spec_fixed_auto ("denom_pub_h", + &denom_h), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (key, + kspec, + NULL, NULL)) + { + GNUNET_break_op (0); + continue; + } + dk = NULL; + dk_off = UINT_MAX; + for (unsigned int j=0;j<key_data->num_denom_keys;j++) + { + if (0 == memcmp (&denom_h, + &key_data->denom_keys[j].h_key, + sizeof (struct GNUNET_HashCode))) + { + dk = &key_data->denom_keys[j]; + dk_off = j; + break; + } + } + if (NULL == dk) + { + GNUNET_break_op (0); + continue; + } + if (check_sigs) + { + kv.start = GNUNET_TIME_absolute_hton (dk->valid_from); + kv.expire_withdraw = GNUNET_TIME_absolute_hton (dk->withdraw_valid_until); + kv.expire_deposit = GNUNET_TIME_absolute_hton (dk->expire_deposit); + kv.expire_legal = GNUNET_TIME_absolute_hton (dk->expire_legal); + TALER_amount_hton (&kv.value, + &dk->value); + TALER_amount_hton (&kv.fee_withdraw, + &dk->fee_withdraw); + TALER_amount_hton (&kv.fee_deposit, + &dk->fee_deposit); + TALER_amount_hton (&kv.fee_refresh, + &dk->fee_refresh); + TALER_amount_hton (&kv.fee_refund, + &dk->fee_refund); + kv.denom_hash = dk->h_key; + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS, + &kv.purpose, + &auditor_sig.eddsa_sig, + &auditor->auditor_pub.eddsa_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + } + auditor->denom_keys[off].denom_key_offset = dk_off; + auditor->denom_keys[off].auditor_sig = auditor_sig; + off++; + } + auditor->num_denom_keys = off; + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Decode the JSON in @a resp_obj from the /keys response + * and store the data in the @a key_data. + * + * @param[in] resp_obj JSON object to parse + * @param check_sig #GNUNET_YES if we should check the signature + * @param[out] key_data where to store the results we decoded + * @param[out] where to store version compatibility data + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + * (malformed JSON) + */ +static int +decode_keys_json (const json_t *resp_obj, + int check_sig, + struct TALER_EXCHANGE_Keys *key_data, + enum TALER_EXCHANGE_VersionCompatibility *vc) +{ + struct TALER_ExchangeSignatureP sig; + struct GNUNET_HashContext *hash_context; + struct TALER_ExchangePublicKeyP pub; + unsigned int age; + unsigned int revision; + unsigned int current; + struct GNUNET_JSON_Specification mspec[] = { + GNUNET_JSON_spec_fixed_auto ("eddsa_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("eddsa_pub", + &pub), + /* sig and pub must be first, as we skip those if + check_sig is false! */ + GNUNET_JSON_spec_fixed_auto ("master_public_key", + &key_data->master_pub), + GNUNET_JSON_spec_absolute_time ("list_issue_date", + &key_data->list_issue_date), + GNUNET_JSON_spec_relative_time + ("reserve_closing_delay", + &key_data->reserve_closing_delay), + GNUNET_JSON_spec_end() + }; + + if (JSON_OBJECT != json_typeof (resp_obj)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* check the version */ + { + const char *ver; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("version", + &ver), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (3 != sscanf (ver, + "%u:%u:%u", + ¤t, + &revision, + &age)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *vc = TALER_EXCHANGE_VC_MATCH; + if (TALER_PROTOCOL_CURRENT < current) + { + *vc |= TALER_EXCHANGE_VC_NEWER; + if (TALER_PROTOCOL_CURRENT < current - age) + *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; + } + if (TALER_PROTOCOL_CURRENT > current) + { + *vc |= TALER_EXCHANGE_VC_OLDER; + if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current) + *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; + } + key_data->version = GNUNET_strdup (ver); + } + + hash_context = NULL; + EXITIF (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + (check_sig) ? mspec : &mspec[2], + NULL, NULL)); + + /* parse the master public key and issue date of the response */ + if (check_sig) + hash_context = GNUNET_CRYPTO_hash_context_start (); + + /* parse the signing keys */ + { + json_t *sign_keys_array; + json_t *sign_key_obj; + unsigned int index; + + EXITIF (NULL == (sign_keys_array = + json_object_get (resp_obj, + "signkeys"))); + EXITIF (JSON_ARRAY != json_typeof (sign_keys_array)); + EXITIF (0 == (key_data->num_sign_keys = + json_array_size (sign_keys_array))); + key_data->sign_keys + = GNUNET_new_array (key_data->num_sign_keys, + struct TALER_EXCHANGE_SigningPublicKey); + index = 0; + json_array_foreach (sign_keys_array, index, sign_key_obj) { + EXITIF (GNUNET_SYSERR == + parse_json_signkey (&key_data->sign_keys[index], + check_sig, + sign_key_obj, + &key_data->master_pub)); + } + } + + /* parse the denomination keys, merging with the + possibly EXISTING array as required (/keys cherry picking) */ + { + json_t *denom_keys_array; + json_t *denom_key_obj; + unsigned int index; + + EXITIF (NULL == (denom_keys_array = + json_object_get (resp_obj, + "denoms"))); + EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); + + index = 0; + json_array_foreach (denom_keys_array, index, denom_key_obj) { + struct TALER_EXCHANGE_DenomPublicKey dk; + bool found = false; + + EXITIF (GNUNET_SYSERR == + parse_json_denomkey (&dk, + check_sig, + denom_key_obj, + &key_data->master_pub, + hash_context)); + for (unsigned int j=0;j<key_data->num_denom_keys;j++) + { + if (0 == memcmp (&dk, + &key_data->denom_keys[j], + sizeof (dk))) + { + found = true; + break; + } + } + if (found) + { + /* 0:0:0 did not support /keys cherry picking */ + GNUNET_break_op (0 == current); + continue; + } + if (key_data->denom_keys_size == key_data->num_denom_keys) + GNUNET_array_grow (key_data->denom_keys, + key_data->denom_keys_size, + key_data->denom_keys_size * 2 + 2); + key_data->denom_keys[key_data->num_denom_keys++] = dk; + + /* Update "last_denom_issue_date" */ + TALER_LOG_DEBUG ("Crawling DK 'valid_from': %s\n", + GNUNET_STRINGS_absolute_time_to_string (dk.valid_from)); + key_data->last_denom_issue_date + = GNUNET_TIME_absolute_max (key_data->last_denom_issue_date, + dk.valid_from); + }; + } + /* parse the auditor information */ + { + json_t *auditors_array; + json_t *auditor_info; + unsigned int index; + + EXITIF (NULL == (auditors_array = + json_object_get (resp_obj, + "auditors"))); + EXITIF (JSON_ARRAY != json_typeof (auditors_array)); + + /* Merge with the existing auditor information we have (/keys cherry picking) */ + index = 0; + json_array_foreach (auditors_array, index, auditor_info) { + struct TALER_EXCHANGE_AuditorInformation ai; + bool found = false; + + memset (&ai, + 0, + sizeof (ai)); + EXITIF (GNUNET_SYSERR == + parse_json_auditor (&ai, + check_sig, + auditor_info, + key_data)); + for (unsigned int j=0;j<key_data->num_auditors;j++) + { + struct TALER_EXCHANGE_AuditorInformation *aix = &key_data->auditors[j]; + + if (0 == memcmp (&ai.auditor_pub, + &aix->auditor_pub, + sizeof (struct TALER_AuditorPublicKeyP))) + { + found = true; + /* Merge denomination key signatures of downloaded /keys into existing + auditor information 'aix'. */ + GNUNET_array_grow (aix->denom_keys, + aix->num_denom_keys, + aix->num_denom_keys + ai.num_denom_keys); + memcpy (&aix->denom_keys[aix->num_denom_keys - ai.num_denom_keys], + ai.denom_keys, + ai.num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); + break; + } + } + if (found) + continue; /* we are done */ + if (key_data->auditors_size == key_data->num_auditors) + GNUNET_array_grow (key_data->auditors, + key_data->auditors_size, + key_data->auditors_size * 2 + 2); + key_data->auditors[key_data->num_auditors++] = ai; + }; + } + + if (check_sig) + { + struct TALER_ExchangeKeySetPS ks; + + /* Validate signature... */ + ks.purpose.size = htonl (sizeof (ks)); + ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET); + ks.list_issue_date = GNUNET_TIME_absolute_hton (key_data->list_issue_date); + GNUNET_CRYPTO_hash_context_finish (hash_context, + &ks.hc); + hash_context = NULL; + EXITIF (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_data, + &pub)); + EXITIF (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET, + &ks.purpose, + &sig.eddsa_signature, + &pub.eddsa_pub)); + } + return GNUNET_OK; + EXITIF_exit: + + if (NULL != hash_context) + GNUNET_CRYPTO_hash_context_abort (hash_context); + return GNUNET_SYSERR; +} + + +/** + * Free key data object. + * + * @param key_data data to free (pointer itself excluded) + */ +static void +free_key_data (struct TALER_EXCHANGE_Keys *key_data) +{ + GNUNET_array_grow (key_data->sign_keys, + key_data->num_sign_keys, + 0); + for (unsigned int i=0;i<key_data->num_denom_keys;i++) + GNUNET_CRYPTO_rsa_public_key_free (key_data->denom_keys[i].key.rsa_public_key); + + GNUNET_array_grow (key_data->denom_keys, + key_data->denom_keys_size, + 0); + for (unsigned int i=0;i<key_data->num_auditors;i++) + { + GNUNET_array_grow (key_data->auditors[i].denom_keys, + key_data->auditors[i].num_denom_keys, + 0); + GNUNET_free (key_data->auditors[i].auditor_url); + } + GNUNET_array_grow (key_data->auditors, + key_data->auditors_size, + 0); + GNUNET_free_non_null (key_data->version); + key_data->version = NULL; +} + + +/** + * Initiate download of /keys from the exchange. + * + * @param cls exchange where to download /keys from + */ +static void +request_keys (void *cls); + + +/** + * Check if our current response for /keys is valid, and if + * not trigger download. + * + * @param exchange exchange to check keys for + * @param force_download #GNUNET_YES to force download even if /keys is still valid + * @return until when the response is current, 0 if we are re-downloading + */ +struct GNUNET_TIME_Absolute +TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange, + int force_download) +{ + if (NULL != exchange->kr) + return GNUNET_TIME_UNIT_ZERO_ABS; + if ( (GNUNET_NO == force_download) && + (0 < GNUNET_TIME_absolute_get_remaining (exchange->key_data_expiration).rel_value_us) ) + return exchange->key_data_expiration; + if (NULL == exchange->retry_task) + exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, + exchange); + return GNUNET_TIME_UNIT_ZERO_ABS; +} + + +/** + * Callback used when downloading the reply to a /keys request + * is complete. + * + * @param cls the `struct KeysRequest` + * @param response_code HTTP response code, 0 on error + * @param resp_obj parsed JSON result, NULL on error + */ +static void +keys_completed_cb (void *cls, + long response_code, + const void *resp_obj) +{ + struct KeysRequest *kr = cls; + struct TALER_EXCHANGE_Handle *exchange = kr->exchange; + struct TALER_EXCHANGE_Keys kd; + struct TALER_EXCHANGE_Keys kd_old; + enum TALER_EXCHANGE_VersionCompatibility vc; + const json_t *j = resp_obj; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received keys from URL `%s' with status %ld.\n", + kr->url, + response_code); + kd_old = exchange->key_data; + memset (&kd, + 0, + sizeof (struct TALER_EXCHANGE_Keys)); + vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR; + switch (response_code) + { + case 0: + free_keys_request (kr); + exchange->kr = NULL; + GNUNET_assert (NULL == exchange->retry_task); + exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay); + exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, + &request_keys, + exchange); + return; + case MHD_HTTP_OK: + if (NULL == j) + { + response_code = 0; + break; + } + /* We keep the denomination keys and auditor signatures from the + previous iteration (/keys cherry picking) */ + kd.num_denom_keys = kd_old.num_denom_keys; + kd.last_denom_issue_date = kd_old.last_denom_issue_date; + GNUNET_array_grow (kd.denom_keys, + kd.denom_keys_size, + kd.num_denom_keys); + + /* First make a shallow copy, we then need another pass for the RSA key... */ + memcpy (kd.denom_keys, + kd_old.denom_keys, + kd_old.num_denom_keys * sizeof (struct TALER_EXCHANGE_DenomPublicKey)); + for (unsigned int i=0;i<kd_old.num_denom_keys;i++) + kd.denom_keys[i].key.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (kd_old.denom_keys[i].key.rsa_public_key); + + kd.num_auditors = kd_old.num_auditors; + kd.auditors = GNUNET_new_array (kd.num_auditors, + struct TALER_EXCHANGE_AuditorInformation); + /* Now the necessary deep copy... */ + for (unsigned int i=0;i<kd_old.num_auditors;i++) + { + const struct TALER_EXCHANGE_AuditorInformation *aold = &kd_old.auditors[i]; + struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i]; + + anew->auditor_pub = aold->auditor_pub; + anew->auditor_url = GNUNET_strdup (aold->auditor_url); + GNUNET_array_grow (anew->denom_keys, + anew->num_denom_keys, + aold->num_denom_keys); + memcpy (anew->denom_keys, + aold->denom_keys, + aold->num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); + } + + if (GNUNET_OK != + decode_keys_json (j, + GNUNET_YES, + &kd, + &vc)) + { + TALER_LOG_ERROR ("Could not decode /keys response\n"); + response_code = 0; + for (unsigned int i=0;i<kd.num_auditors;i++) + { + struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i]; + + GNUNET_array_grow (anew->denom_keys, + anew->num_denom_keys, + 0); + GNUNET_free (anew->auditor_url); + } + GNUNET_free (kd.auditors); + kd.auditors = NULL; + kd.num_auditors = 0; + for (unsigned int i=0;i<kd_old.num_denom_keys;i++) + GNUNET_CRYPTO_rsa_public_key_free (kd.denom_keys[i].key.rsa_public_key); + GNUNET_array_grow (kd.denom_keys, + kd.denom_keys_size, + 0); + kd.num_denom_keys = 0; + break; + } + json_decref (exchange->key_data_raw); + exchange->key_data_raw = json_deep_copy (j); + exchange->retry_delay = GNUNET_TIME_UNIT_ZERO; + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + break; + } + exchange->key_data = kd; + TALER_LOG_DEBUG ("Last DK issue date update to: %s\n", + GNUNET_STRINGS_absolute_time_to_string + (exchange->key_data.last_denom_issue_date)); + + + if (MHD_HTTP_OK != response_code) + { + exchange->kr = NULL; + free_keys_request (kr); + exchange->state = MHS_FAILED; + if (NULL != exchange->key_data_raw) + { + json_decref (exchange->key_data_raw); + exchange->key_data_raw = NULL; + } + free_key_data (&kd_old); + /* notify application that we failed */ + exchange->cert_cb (exchange->cert_cb_cls, + NULL, + vc); + return; + } + + exchange->kr = NULL; + exchange->key_data_expiration = kr->expire; + free_keys_request (kr); + exchange->state = MHS_CERT; + /* notify application about the key information */ + exchange->cert_cb (exchange->cert_cb_cls, + &exchange->key_data, + vc); + free_key_data (&kd_old); +} + + +/* ********************* library internal API ********* */ + + +/** + * Get the context of a exchange. + * + * @param h the exchange handle to query + * @return ctx context to execute jobs in + */ +struct GNUNET_CURL_Context * +TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h) +{ + return h->ctx; +} + + +/** + * Check if the handle is ready to process requests. + * + * @param h the exchange handle to query + * @return #GNUNET_YES if we are ready, #GNUNET_NO if not + */ +int +TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h) +{ + return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO; +} + + +/** + * Obtain the URL to use for an API request. + * + * @param h handle for the exchange + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URL to use with cURL + */ +char * +TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h, + const char *path) +{ + return TEAH_path_to_url2 (h->url, + path); +} + + +/** + * Obtain the URL to use for an API request. + * + * @param base_url base URL of the exchange (i.e. "http://exchange/") + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URL to use with cURL + */ +char * +TEAH_path_to_url2 (const char *base_url, + const char *path) +{ + char *url; + + if ( ('/' == path[0]) && + (0 < strlen (base_url)) && + ('/' == base_url[strlen (base_url) - 1]) ) + path++; /* avoid generating URL with "//" from concat */ + GNUNET_asprintf (&url, + "%s%s", + base_url, + path); + return url; +} + + +/** + * Parse HTTP timestamp. + * + * @param date header to parse header + * @param at where to write the result + * @return #GNUNET_OK on success + */ +static int +parse_date_string (const char *date, + struct GNUNET_TIME_Absolute *at) +{ + struct tm now; + time_t t; + const char *end; + + memset (&now, + 0, + sizeof (now)); + end = strptime (date, + "%a, %d %b %Y %H:%M:%S %Z", /* RFC-1123 standard spec */ + &now); + if ( (NULL == end) || + ( (*end != '\n') && + (*end != '\r') ) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + t = mktime (&now); + if (((time_t) -1) == t) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "mktime"); + return GNUNET_SYSERR; + } + if (t < 0) + t = 0; /* can happen due to timezone issues if date was 1.1.1970 */ + at->abs_value_us = 1000LL * 1000LL * t; + return GNUNET_OK; +} + + +/** + * Function called for each header in the HTTP /keys response. + * Finds the "Expire:" header and parses it, storing the result + * in the "expire" field fo the keys request. + * + * @param buffer header data received + * @param size size of an item in @a buffer + * @param nitems number of items in @a buffer + * @param userdata the `struct KeysRequest` + * @return `size * nitems` on success (everything else aborts) + */ +static size_t +header_cb (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct KeysRequest *kr = userdata; + size_t total = size * nitems; + char *val; + + if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": ")) + return total; + if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ", + buffer, + strlen (MHD_HTTP_HEADER_EXPIRES ": "))) + return total; + val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")], + total - strlen (MHD_HTTP_HEADER_EXPIRES ": ")); + if (GNUNET_OK != + parse_date_string (val, + &kr->expire)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s-header `%s'\n", + MHD_HTTP_HEADER_EXPIRES, + val); + kr->expire = GNUNET_TIME_UNIT_ZERO_ABS; + } + GNUNET_free (val); + return total; +} + + +/* ********************* public API ******************* */ + + +/** + * Deserialize the key data and use it to bootstrap @a exchange to + * more efficiently recover the state. Errors in @a data must be + * tolerated (i.e. by re-downloading instead). + * + * @param exchange which exchange's key and wire data should be deserialized + * @return data the data to deserialize + */ +static void +deserialize_data (struct TALER_EXCHANGE_Handle *exchange, + const json_t *data) +{ + enum TALER_EXCHANGE_VersionCompatibility vc; + json_t *keys; + const char *url; + uint32_t version; + struct GNUNET_TIME_Absolute expire; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ("version", + &version), + GNUNET_JSON_spec_json ("keys", + &keys), + GNUNET_JSON_spec_string ("url", + &url), + GNUNET_JSON_spec_absolute_time ("expire", + &expire), + GNUNET_JSON_spec_end() + }; + struct TALER_EXCHANGE_Keys key_data; + + if (NULL == data) + return; + if (GNUNET_OK != + GNUNET_JSON_parse (data, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return; + } + if (0 != version) + return; /* unsupported version */ + if (0 != strcmp (url, + exchange->url)) + { + GNUNET_break (0); + return; + } + memset (&key_data, + 0, + sizeof (struct TALER_EXCHANGE_Keys)); + if (GNUNET_OK != + decode_keys_json (keys, + GNUNET_NO, + &key_data, + &vc)) + { + GNUNET_break (0); + return; + } + /* decode successful, initialize with the result */ + GNUNET_assert (NULL == exchange->key_data_raw); + exchange->key_data_raw = json_deep_copy (keys); + exchange->key_data = key_data; + exchange->key_data_expiration = expire; + exchange->state = MHS_CERT; + /* notify application about the key information */ + exchange->cert_cb (exchange->cert_cb_cls, + &exchange->key_data, + vc); +} + + +/** + * Serialize the latest key data from @a exchange to be persisted on + * disk (to be used with #TALER_EXCHANGE_OPTION_DATA to more + * efficiently recover the state). + * + * @param exchange which exchange's key and wire data should be + * serialized + * @return NULL on error (i.e. no current data available); + * otherwise JSON object owned by the caller + */ +json_t * +TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange) +{ + const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data; + struct GNUNET_TIME_Absolute now; + json_t *keys; + json_t *signkeys; + json_t *denoms; + json_t *auditors; + + now = GNUNET_TIME_absolute_get (); + signkeys = json_array (); + for (unsigned int i=0;i<kd->num_sign_keys;i++) + { + const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i]; + json_t *signkey; + + if (now.abs_value_us > sk->valid_until.abs_value_us) + continue; /* skip keys that have expired */ + signkey = json_pack ("{s:o, s:o, s:o, s:o, s:o}", + "key", + GNUNET_JSON_from_data_auto (&sk->key), + "master_sig", + GNUNET_JSON_from_data_auto (&sk->master_sig), + "stamp_start", + GNUNET_JSON_from_time_abs (sk->valid_from), + "stamp_expire", + GNUNET_JSON_from_time_abs (sk->valid_until), + "stamp_end", + GNUNET_JSON_from_time_abs (sk->valid_legal)); + if (NULL == signkey) + { + GNUNET_break (0); + continue; + } + json_array_append_new (signkeys, + signkey); + } + denoms = json_array (); + for (unsigned int i=0;i<kd->num_denom_keys;i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i]; + json_t *denom; + + if (now.abs_value_us > dk->expire_deposit.abs_value_us) + continue; /* skip keys that have expired */ + denom = json_pack ("{s:o, s:o, s:o, s:o, s:o " + ",s:o, s:o, s:o, s:o, s:o " + ",s:o}", + "stamp_expire_deposit", + GNUNET_JSON_from_time_abs (dk->expire_deposit), + "stamp_expire_withdraw", + GNUNET_JSON_from_time_abs (dk->withdraw_valid_until), + "stamp_start", + GNUNET_JSON_from_time_abs (dk->valid_from), + "stamp_expire_legal", + GNUNET_JSON_from_time_abs (dk->expire_legal), + "value", + TALER_JSON_from_amount (&dk->value), + "fee_withdraw", + /* #6 */ + TALER_JSON_from_amount (&dk->fee_withdraw), + "fee_deposit", + TALER_JSON_from_amount (&dk->fee_deposit), + "fee_refresh", + TALER_JSON_from_amount (&dk->fee_refresh), + "fee_refund", + TALER_JSON_from_amount (&dk->fee_refund), + "master_sig", + GNUNET_JSON_from_data_auto (&dk->master_sig), + /* #10 */ + "denom_pub", + GNUNET_JSON_from_rsa_public_key (dk->key.rsa_public_key)); + if (NULL == denom) + { + GNUNET_break (0); + continue; + } + json_array_append_new (denoms, + denom); + } + auditors = json_array (); + for (unsigned int i=0;i<kd->num_auditors;i++) + { + const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i]; + json_t *a; + json_t *adenoms; + + adenoms = json_array (); + for (unsigned int j=0;j<ai->num_denom_keys;j++) + { + const struct TALER_EXCHANGE_AuditorDenominationInfo *adi = &ai->denom_keys[j]; + const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[adi->denom_key_offset]; + json_t *k; + + if (now.abs_value_us > dk->expire_deposit.abs_value_us) + continue; /* skip auditor signatures for denomination keys that have expired */ + GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys); + k = json_pack ("{s:o, s:o}", + "denom_pub_h", + GNUNET_JSON_from_data_auto (&dk->h_key), + "auditor_sig", + GNUNET_JSON_from_data_auto (&adi->auditor_sig)); + if (NULL == k) + { + GNUNET_break (0); + continue; + } + json_array_append_new (adenoms, + k); + } + + a = json_pack ("{s:o, s:s, s:o}", + "auditor_pub", + GNUNET_JSON_from_data_auto (&ai->auditor_pub), + "auditor_url", + ai->auditor_url, + "denomination_keys", + adenoms); + if (NULL == a) + { + GNUNET_break (0); + continue; + } + json_array_append_new (auditors, + a); + } + keys = json_pack ("{s:s, s:o, s:o, s:o, s:o" + ",s:o, s:o}", + /* 1 */ + "version", + kd->version, + "master_public_key", + GNUNET_JSON_from_data_auto (&kd->master_pub), + "reserve_closing_delay", + GNUNET_JSON_from_time_rel (kd->reserve_closing_delay), + "list_issue_date", + GNUNET_JSON_from_time_abs (kd->list_issue_date), + "signkeys", + signkeys, + /* #6 */ + "denoms", + denoms, + "auditors", + auditors); + if (NULL == keys) + { + GNUNET_break (0); + return NULL; + } + return json_pack ("{s:I, s:o, s:s, s:o}", + "version", + (json_int_t) TALER_SERIALIZATION_FORMAT_VERSION, + "expire", + GNUNET_JSON_from_time_abs (exchange->key_data_expiration), + "url", + exchange->url, + "keys", + keys); +} + + +/** + * Initialise a connection to the exchange. Will connect to the + * exchange and obtain information about the exchange's master + * public key and the exchange's auditor. + * The respective information will be passed to the @a cert_cb + * once available, and all future interactions with the exchange + * will be checked to be signed (where appropriate) by the + * respective master key. + * + * @param ctx the context + * @param url HTTP base URL for the exchange + * @param cert_cb function to call with the exchange's + * certification information + * @param cert_cb_cls closure for @a cert_cb + * @param ... list of additional arguments, + * terminated by #TALER_EXCHANGE_OPTION_END. + * @return the exchange handle; NULL upon error + */ +struct TALER_EXCHANGE_Handle * +TALER_EXCHANGE_connect + (struct GNUNET_CURL_Context *ctx, + const char *url, + TALER_EXCHANGE_CertificationCallback cert_cb, + void *cert_cb_cls, + ...) +{ + struct TALER_EXCHANGE_Handle *exchange; + va_list ap; + enum TALER_EXCHANGE_Option opt; + + exchange = GNUNET_new (struct TALER_EXCHANGE_Handle); + exchange->ctx = ctx; + exchange->url = GNUNET_strdup (url); + exchange->cert_cb = cert_cb; + exchange->cert_cb_cls = cert_cb_cls; + exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, + exchange); + va_start (ap, cert_cb_cls); + while (TALER_EXCHANGE_OPTION_END != + (opt = va_arg (ap, int))) + { + switch (opt) { + case TALER_EXCHANGE_OPTION_END: + GNUNET_assert (0); + break; + case TALER_EXCHANGE_OPTION_DATA: + { + const json_t *data = va_arg (ap, const json_t *); + + deserialize_data (exchange, + data); + break; + } + default: + GNUNET_assert (0); + break; + } + } + va_end (ap); + return exchange; +} + + +/** + * Initiate download of /keys from the exchange. + * + * @param cls exchange where to download /keys from + */ +static void +request_keys (void *cls) +{ + struct TALER_EXCHANGE_Handle *exchange = cls; + struct KeysRequest *kr; + CURL *eh; + + exchange->retry_task = NULL; + GNUNET_assert (NULL == exchange->kr); + kr = GNUNET_new (struct KeysRequest); + kr->exchange = exchange; + if (GNUNET_YES == + TEAH_handle_is_ready (exchange)) + { + char *arg; + + TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n", + GNUNET_STRINGS_absolute_time_to_string (exchange->key_data.last_denom_issue_date)); + GNUNET_asprintf (&arg, + "/keys?last_issue_date=%llu", + (unsigned long long) exchange->key_data.last_denom_issue_date.abs_value_us / 1000000LLU); + kr->url = TEAH_path_to_url (exchange, + arg); + GNUNET_free (arg); + } + else + { + kr->url = TEAH_path_to_url (exchange, + "/keys"); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting keys with URL `%s'.\n", + kr->url); + eh = TEL_curl_easy_get (kr->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_VERBOSE, + 0)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT, + (long) 300)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &header_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + kr)); + kr->job = GNUNET_CURL_job_add (exchange->ctx, + eh, + GNUNET_YES, + &keys_completed_cb, + kr); + exchange->kr = kr; +} + + +/** + * Disconnect from the exchange + * + * @param exchange the exchange handle + */ +void +TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange) +{ + if (NULL != exchange->kr) + { + GNUNET_CURL_job_cancel (exchange->kr->job); + free_keys_request (exchange->kr); + exchange->kr = NULL; + } + free_key_data (&exchange->key_data); + if (NULL != exchange->key_data_raw) + { + json_decref (exchange->key_data_raw); + exchange->key_data_raw = NULL; + } + if (NULL != exchange->retry_task) + { + GNUNET_SCHEDULER_cancel (exchange->retry_task); + exchange->retry_task = NULL; + } + GNUNET_free (exchange->url); + GNUNET_free (exchange); +} + + +/** + * Lookup the given @a pub in @a keys. + * + * @param keys the exchange's key set + * @param pub claimed current online signing key for the exchange + * @return NULL if @a pub was not found + */ +const struct TALER_EXCHANGE_SigningPublicKey * +TALER_EXCHANGE_get_signing_key_details (const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ExchangePublicKeyP *pub) +{ + for (unsigned int i=0;i<keys->num_sign_keys;i++) + { + struct TALER_EXCHANGE_SigningPublicKey *spk = &keys->sign_keys[i]; + + if (0 == memcmp (pub, + &spk->key, + sizeof (struct TALER_ExchangePublicKeyP))) + return spk; + } + return NULL; +} + + +/** + * Test if the given @a pub is a the current signing key from the exchange + * according to @a keys. + * + * @param keys the exchange's key set + * @param pub claimed current online signing key for the exchange + * @return #GNUNET_OK if @a pub is (according to /keys) a current signing key + */ +int +TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ExchangePublicKeyP *pub) +{ + struct GNUNET_TIME_Absolute now; + + /* we will check using a tolerance of 1h for the time */ + now = GNUNET_TIME_absolute_get (); + for (unsigned int i=0;i<keys->num_sign_keys;i++) + if ( (keys->sign_keys[i].valid_from.abs_value_us <= now.abs_value_us + 60 * 60 * 1000LL * 1000LL) && + (keys->sign_keys[i].valid_until.abs_value_us > now.abs_value_us - 60 * 60 * 1000LL * 1000LL) && + (0 == memcmp (pub, + &keys->sign_keys[i].key, + sizeof (struct TALER_ExchangePublicKeyP))) ) + return GNUNET_OK; + return GNUNET_SYSERR; +} + + +/** + * Get exchange's base URL. + * + * @param exchange exchange handle. + * @return the base URL from the handle. + */ +const char * +TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange) +{ + return exchange->url; +} + + +/** + * Obtain the denomination key details from the exchange. + * + * @param keys the exchange's key set + * @param pk public key of the denomination to lookup + * @return details about the given denomination key, NULL if the key is + * not found + */ +const struct TALER_EXCHANGE_DenomPublicKey * +TALER_EXCHANGE_get_denomination_key (const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_DenominationPublicKey *pk) +{ + for (unsigned int i=0;i<keys->num_denom_keys;i++) + if (0 == GNUNET_CRYPTO_rsa_public_key_cmp (pk->rsa_public_key, + keys->denom_keys[i].key.rsa_public_key)) + return &keys->denom_keys[i]; + return NULL; +} + + +/** + * Obtain the denomination key details from the exchange. + * + * @param keys the exchange's key set + * @param hc hash of the public key of the denomination to lookup + * @return details about the given denomination key + */ +const struct TALER_EXCHANGE_DenomPublicKey * +TALER_EXCHANGE_get_denomination_key_by_hash (const struct TALER_EXCHANGE_Keys *keys, + const struct GNUNET_HashCode *hc) +{ + for (unsigned int i=0;i<keys->num_denom_keys;i++) + if (0 == memcmp (hc, + &keys->denom_keys[i].h_key, + sizeof (struct GNUNET_HashCode))) + return &keys->denom_keys[i]; + return NULL; +} + + +/** + * Obtain the keys from the exchange. + * + * @param exchange the exchange handle + * @return the exchange's key set + */ +const struct TALER_EXCHANGE_Keys * +TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange) +{ + (void) TALER_EXCHANGE_check_keys_current (exchange, + GNUNET_NO); + return &exchange->key_data; +} + + +/** + * Obtain the keys from the exchange in the + * raw JSON format + * + * @param exchange the exchange handle + * @return the exchange's keys in raw JSON + */ +json_t * +TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange) +{ + (void) TALER_EXCHANGE_check_keys_current (exchange, + GNUNET_NO); + return json_deep_copy (exchange->key_data_raw); +} + + +/* end of exchange_api_handle.c */ diff --git a/src/lib/exchange_api_handle.h b/src/lib/exchange_api_handle.h new file mode 100644 index 000000000..f06fa4eef --- /dev/null +++ b/src/lib/exchange_api_handle.h @@ -0,0 +1,103 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_handle.h + * @brief Internal interface to the handle part of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_auditor_service.h" +#include "taler_exchange_service.h" +#include "taler_crypto_lib.h" + + +/** + * Function called for each auditor to give us a chance to possibly + * launch a deposit confirmation interaction. + * + * @param cls closure + * @param ah handle to the auditor + * @param auditor_pub public key of the auditor + * @return NULL if no deposit confirmation interaction was launched + */ +typedef struct TALER_AUDITOR_DepositConfirmationHandle * +(*TEAH_AuditorCallback)(void *cls, + struct TALER_AUDITOR_Handle *ah, + const struct TALER_AuditorPublicKeyP *auditor_pub); + + +/** + * Iterate over all available auditors for @a h, calling + * @param ah and giving it a chance to start a deposit + * confirmation interaction. + * + * @param h exchange to go over auditors for + * @param ac function to call per auditor + * @param ac_cls closure for @a ac + */ +void +TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h, + TEAH_AuditorCallback ac, + void *ac_cls); + + +/** + * Get the context of a exchange. + * + * @param h the exchange handle to query + * @return ctx context to execute jobs in + */ +struct GNUNET_CURL_Context * +TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h); + + +/** + * Check if the handle is ready to process requests. + * + * @param h the exchange handle to query + * @return #GNUNET_YES if we are ready, #GNUNET_NO if not + */ +int +TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h); + + +/** + * Obtain the URL to use for an API request. + * + * @param h the exchange handle to query + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URL to use with cURL + */ +char * +TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h, + const char *path); + + +/** + * Obtain the URL to use for an API request. + * + * @param base_url base URL of the exchange (i.e. "http://exchange/") + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URL to use with cURL + */ +char * +TEAH_path_to_url2 (const char *base_url, + const char *path); + + +/* end of exchange_api_handle.h */ diff --git a/src/lib/exchange_api_payback.c b/src/lib/exchange_api_payback.c new file mode 100644 index 000000000..6c1772aff --- /dev/null +++ b/src/lib/exchange_api_payback.c @@ -0,0 +1,374 @@ +/* + This file is part of TALER + Copyright (C) 2017 GNUnet e.V. and Inria + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_payback.c + * @brief Implementation of the /payback request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Payback Handle + */ +struct TALER_EXCHANGE_PaybackHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Denomination key of the coin. + */ + const struct TALER_EXCHANGE_DenomPublicKey *pk; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_PaybackResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Public key of the coin we are trying to get paid back. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. If it is, call the + * callback. + * + * @param ph payback handle + * @param json json reply with the signature + * @return #GNUNET_OK if the signature is valid and we called the callback; + * #GNUNET_SYSERR if not (callback must still be called) + */ +static int +verify_payback_signature_ok (const struct TALER_EXCHANGE_PaybackHandle *ph, + const json_t *json) +{ + struct TALER_PaybackConfirmationPS pc; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_Amount amount; + struct GNUNET_TIME_Absolute timestamp; + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub), + TALER_JSON_spec_amount ("amount", &amount), + GNUNET_JSON_spec_absolute_time ("timestamp", ×tamp), + GNUNET_JSON_spec_fixed_auto ("reserve_pub", &pc.reserve_pub), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = TALER_EXCHANGE_get_keys (ph->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK); + pc.purpose.size = htonl (sizeof (pc)); + pc.timestamp = GNUNET_TIME_absolute_hton (timestamp); + TALER_amount_hton (&pc.payback_amount, + &amount); + pc.coin_pub = ph->coin_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK, + &pc.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + ph->cb (ph->cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + &amount, + timestamp, + &pc.reserve_pub, + json); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /payback request. + * + * @param cls the `struct TALER_EXCHANGE_PaybackHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_payback_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_PaybackHandle *ph = cls; + const json_t *j = response; + + ph->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + verify_payback_signature_ok (ph, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + TALER_EXCHANGE_payback_cancel (ph); + return; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + { + /* Insufficient funds, proof attached */ + json_t *history; + struct TALER_Amount total; + const struct TALER_EXCHANGE_DenomPublicKey *dki; + + dki = ph->pk; + history = json_object_get (j, + "history"); + if (GNUNET_OK != + TALER_EXCHANGE_verify_coin_history (dki->fee_deposit.currency, + &ph->coin_pub, + history, + &total)) + { + GNUNET_break_op (0); + response_code = 0; + } + ph->cb (ph->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + &total, + GNUNET_TIME_UNIT_FOREVER_ABS, + NULL, + j); + TALER_EXCHANGE_payback_cancel (ph); + return; + } + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_GONE: + /* Kind of normal: the money was already sent to the merchant + (it was too late for the refund). */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + ph->cb (ph->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + NULL, + GNUNET_TIME_UNIT_FOREVER_ABS, + NULL, + j); + TALER_EXCHANGE_payback_cancel (ph); +} + + +/** + * Ask the exchange to pay back a coin due to the exchange triggering + * the emergency payback protocol for a given denomination. The value + * of the coin will be refunded to the original customer (without fees). + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to pay back + * @param denom_sig signature over the coin by the exchange using @a pk + * @param ps secret internals of the original planchet + * @param payback_cb the callback to call when the final result for this request is available + * @param payback_cb_cls closure for @a payback_cb + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_PaybackHandle * +TALER_EXCHANGE_payback (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_DenominationSignature *denom_sig, + const struct TALER_PlanchetSecretsP *ps, + TALER_EXCHANGE_PaybackResultCallback payback_cb, + void *payback_cb_cls) +{ + struct TALER_EXCHANGE_PaybackHandle *ph; + struct GNUNET_CURL_Context *ctx; + struct TALER_PaybackRequestPS pr; + struct TALER_CoinSpendSignatureP coin_sig; + json_t *payback_obj; + CURL *eh; + + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_PAYBACK); + pr.purpose.size = htonl (sizeof (struct TALER_PaybackRequestPS)); + GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv, + &pr.coin_pub.eddsa_pub); + pr.h_denom_pub = pk->h_key; + pr.coin_blind = ps->blinding_key; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&ps->coin_priv.eddsa_priv, + &pr.purpose, + &coin_sig.eddsa_signature)); + + payback_obj = json_pack ("{s:o, s:o," /* denom pub/sig */ + " s:o, s:o," /* coin pub/sig */ + " s:o}", /* coin_bks */ + "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->key.rsa_public_key), + "denom_sig", GNUNET_JSON_from_rsa_signature (denom_sig->rsa_signature), + "coin_pub", GNUNET_JSON_from_data_auto (&pr.coin_pub), + "coin_sig", GNUNET_JSON_from_data_auto (&coin_sig), + "coin_blind_key_secret", GNUNET_JSON_from_data_auto (&ps->blinding_key) + ); + if (NULL == payback_obj) + { + GNUNET_break (0); + return NULL; + } + + ph = GNUNET_new (struct TALER_EXCHANGE_PaybackHandle); + ph->coin_pub = pr.coin_pub; + ph->exchange = exchange; + ph->pk = pk; + ph->cb = payback_cb; + ph->cb_cls = payback_cb_cls; + ph->url = TEAH_path_to_url (exchange, "/payback"); + + ph->json_enc = json_dumps (payback_obj, + JSON_COMPACT); + json_decref (payback_obj); + if (NULL == ph->json_enc) + { + GNUNET_break (0); + GNUNET_free (ph->url); + GNUNET_free (ph); + return NULL; + } + eh = TEL_curl_easy_get (ph->url); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for payback: `%s'\n", + ph->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + ph->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (ph->json_enc))); + ctx = TEAH_handle_to_context (exchange); + ph->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_payback_finished, + ph); + return ph; +} + + +/** + * Cancel a payback request. This function cannot be used on a + * request handle if the callback was already invoked. + * + * @param ph the payback handle + */ +void +TALER_EXCHANGE_payback_cancel (struct TALER_EXCHANGE_PaybackHandle *ph) +{ + if (NULL != ph->job) + { + GNUNET_CURL_job_cancel (ph->job); + ph->job = NULL; + } + GNUNET_free (ph->url); + GNUNET_free (ph->json_enc); + GNUNET_free (ph); +} + + +/* end of exchange_api_payback.c */ diff --git a/src/lib/exchange_api_refresh.c b/src/lib/exchange_api_refresh.c new file mode 100644 index 000000000..b766b7957 --- /dev/null +++ b/src/lib/exchange_api_refresh.c @@ -0,0 +1,1678 @@ +/* + This file is part of TALER + Copyright (C) 2015, 2016, 2017 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_refresh.c + * @brief Implementation of the /refresh/melt+reveal requests of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/* ********************* /refresh/ common ***************************** */ + +/* structures for committing refresh data to disk before doing the + network interaction(s) */ + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Header of serialized information about a coin we are melting. + */ +struct MeltedCoinP +{ + /** + * Private key of the coin. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Amount this coin contributes to the melt, including fee. + */ + struct TALER_AmountNBO melt_amount_with_fee; + + /** + * The applicable fee for withdrawing a coin of this denomination + */ + struct TALER_AmountNBO fee_melt; + + /** + * The original value of the coin. + */ + struct TALER_AmountNBO original_value; + + /** + * Transfer private keys for each cut-and-choose dimension. + */ + struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; + + /** + * Timestamp indicating when coins of this denomination become invalid. + */ + struct GNUNET_TIME_AbsoluteNBO expire_deposit; + + /** + * Size of the encoded public key that follows. + */ + uint16_t pbuf_size; + + /** + * Size of the encoded signature that follows. + */ + uint16_t sbuf_size; + + /* Followed by serializations of: + 1) struct TALER_DenominationPublicKey pub_key; + 2) struct TALER_DenominationSignature sig; + */ +}; + + +/** + * Header of serialized data about a melt operation, suitable for + * persisting it on disk. + */ +struct MeltDataP +{ + + /** + * Hash over the melting session. + */ + struct TALER_RefreshCommitmentP rc; + + /** + * Number of coins we are melting, in NBO + */ + uint16_t num_melted_coins GNUNET_PACKED; + + /** + * Number of coins we are creating, in NBO + */ + uint16_t num_fresh_coins GNUNET_PACKED; + + /* Followed by serializations of: + 1) struct MeltedCoinP melted_coins[num_melted_coins]; + 2) struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins]; + 3) TALER_CNC_KAPPA times: + 3a) struct TALER_PlanchetSecretsP fresh_coins[num_fresh_coins]; + */ +}; + + +GNUNET_NETWORK_STRUCT_END + + +/** + * Information about a coin we are melting. + */ +struct MeltedCoin +{ + /** + * Private key of the coin. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Amount this coin contributes to the melt, including fee. + */ + struct TALER_Amount melt_amount_with_fee; + + /** + * The applicable fee for melting a coin of this denomination + */ + struct TALER_Amount fee_melt; + + /** + * The original value of the coin. + */ + struct TALER_Amount original_value; + + /** + * Transfer private keys for each cut-and-choose dimension. + */ + struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; + + /** + * Timestamp indicating when coins of this denomination become invalid. + */ + struct GNUNET_TIME_Absolute expire_deposit; + + /** + * Denomination key of the original coin. + */ + struct TALER_DenominationPublicKey pub_key; + + /** + * Exchange's signature over the coin. + */ + struct TALER_DenominationSignature sig; + +}; + + +/** + * Melt data in non-serialized format for convenient processing. + */ +struct MeltData +{ + + /** + * Hash over the committed data during refresh operation. + */ + struct TALER_RefreshCommitmentP rc; + + /** + * Number of coins we are creating + */ + uint16_t num_fresh_coins; + + /** + * Information about the melted coin. + */ + struct MeltedCoin melted_coin; + + /** + * Array of @e num_fresh_coins denomination keys for the coins to be + * freshly exchangeed. + */ + struct TALER_DenominationPublicKey *fresh_pks; + + /** + * Arrays of @e num_fresh_coins with information about the fresh + * coins to be created, for each cut-and-choose dimension. + */ + struct TALER_PlanchetSecretsP *fresh_coins[TALER_CNC_KAPPA]; +}; + + +/** + * Free all information associated with a melted coin session. + * + * @param mc melted coin to release, the pointer itself is NOT + * freed (as it is typically not allocated by itself) + */ +static void +free_melted_coin (struct MeltedCoin *mc) +{ + if (NULL != mc->pub_key.rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (mc->pub_key.rsa_public_key); + if (NULL != mc->sig.rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (mc->sig.rsa_signature); +} + + +/** + * Free all information associated with a melting session. Note + * that we allow the melting session to be only partially initialized, + * as we use this function also when freeing melt data that was not + * fully initialized (i.e. due to failures in #deserialize_melt_data()). + * + * @param md melting data to release, the pointer itself is NOT + * freed (as it is typically not allocated by itself) + */ +static void +free_melt_data (struct MeltData *md) +{ + free_melted_coin (&md->melted_coin); + if (NULL != md->fresh_pks) + { + for (unsigned int i=0;i<md->num_fresh_coins;i++) + if (NULL != md->fresh_pks[i].rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (md->fresh_pks[i].rsa_public_key); + GNUNET_free (md->fresh_pks); + } + + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + GNUNET_free (md->fresh_coins[i]); + /* Finally, clean up a bit... + (NOTE: compilers might optimize this away, so this is + not providing any strong assurances that the key material + is purged.) */ + memset (md, + 0, + sizeof (struct MeltData)); +} + + +/** + * Serialize information about a coin we are melting. + * + * @param mc information to serialize + * @param buf buffer to write data in, NULL to just compute + * required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + * @a buf is NULL, number of bytes required; 0 on error + */ +static size_t +serialize_melted_coin (const struct MeltedCoin *mc, + char *buf, + size_t off) +{ + struct MeltedCoinP mcp; + unsigned int i; + char *pbuf; + size_t pbuf_size; + char *sbuf; + size_t sbuf_size; + + sbuf_size = GNUNET_CRYPTO_rsa_signature_encode (mc->sig.rsa_signature, + &sbuf); + pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (mc->pub_key.rsa_public_key, + &pbuf); + if (NULL == buf) + { + GNUNET_free (sbuf); + GNUNET_free (pbuf); + return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; + } + if ( (sbuf_size > UINT16_MAX) || + (pbuf_size > UINT16_MAX) ) + { + GNUNET_break (0); + return 0; + } + mcp.coin_priv = mc->coin_priv; + TALER_amount_hton (&mcp.melt_amount_with_fee, + &mc->melt_amount_with_fee); + TALER_amount_hton (&mcp.fee_melt, + &mc->fee_melt); + TALER_amount_hton (&mcp.original_value, + &mc->original_value); + for (i=0;i<TALER_CNC_KAPPA;i++) + mcp.transfer_priv[i] = mc->transfer_priv[i]; + mcp.expire_deposit = GNUNET_TIME_absolute_hton (mc->expire_deposit); + mcp.pbuf_size = htons ((uint16_t) pbuf_size); + mcp.sbuf_size = htons ((uint16_t) sbuf_size); + memcpy (&buf[off], + &mcp, + sizeof (struct MeltedCoinP)); + memcpy (&buf[off + sizeof (struct MeltedCoinP)], + pbuf, + pbuf_size); + memcpy (&buf[off + sizeof (struct MeltedCoinP) + pbuf_size], + sbuf, + sbuf_size); + GNUNET_free (sbuf); + GNUNET_free (pbuf); + return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; +} + + +/** + * Deserialize information about a coin we are melting. + * + * @param[out] mc information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_melted_coin (struct MeltedCoin *mc, + const char *buf, + size_t size, + int *ok) +{ + struct MeltedCoinP mcp; + unsigned int i; + size_t pbuf_size; + size_t sbuf_size; + size_t off; + + if (size < sizeof (struct MeltedCoinP)) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + memcpy (&mcp, + buf, + sizeof (struct MeltedCoinP)); + pbuf_size = ntohs (mcp.pbuf_size); + sbuf_size = ntohs (mcp.sbuf_size); + if (size < sizeof (struct MeltedCoinP) + pbuf_size + sbuf_size) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + off = sizeof (struct MeltedCoinP); + mc->pub_key.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (&buf[off], + pbuf_size); + off += pbuf_size; + mc->sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_decode (&buf[off], + sbuf_size); + off += sbuf_size; + if ( (NULL == mc->pub_key.rsa_public_key) || + (NULL == mc->sig.rsa_signature) ) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + + mc->coin_priv = mcp.coin_priv; + TALER_amount_ntoh (&mc->melt_amount_with_fee, + &mcp.melt_amount_with_fee); + TALER_amount_ntoh (&mc->fee_melt, + &mcp.fee_melt); + TALER_amount_ntoh (&mc->original_value, + &mcp.original_value); + for (i=0;i<TALER_CNC_KAPPA;i++) + mc->transfer_priv[i] = mcp.transfer_priv[i]; + mc->expire_deposit = GNUNET_TIME_absolute_ntoh (mcp.expire_deposit); + return off; +} + + +/** + * Serialize information about a denomination key. + * + * @param dk information to serialize + * @param buf buffer to write data in, NULL to just compute + * required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + * @a buf is NULL, number of bytes required + */ +static size_t +serialize_denomination_key (const struct TALER_DenominationPublicKey *dk, + char *buf, + size_t off) +{ + char *pbuf; + size_t pbuf_size; + uint32_t be; + + pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key, + &pbuf); + if (NULL == buf) + { + GNUNET_free (pbuf); + return pbuf_size + sizeof (uint32_t); + } + be = htonl ((uint32_t) pbuf_size); + memcpy (&buf[off], + &be, + sizeof (uint32_t)); + memcpy (&buf[off + sizeof (uint32_t)], + pbuf, + pbuf_size); + GNUNET_free (pbuf); + return pbuf_size + sizeof (uint32_t); +} + + +/** + * Deserialize information about a denomination key. + * + * @param[out] dk information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_denomination_key (struct TALER_DenominationPublicKey *dk, + const char *buf, + size_t size, + int *ok) +{ + size_t pbuf_size; + uint32_t be; + + if (size < sizeof (uint32_t)) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + memcpy (&be, + buf, + sizeof (uint32_t)); + pbuf_size = ntohl (be); + if (size < sizeof (uint32_t) + pbuf_size) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + dk->rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (&buf[sizeof (uint32_t)], + pbuf_size); + if (NULL == dk->rsa_public_key) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + return sizeof (uint32_t) + pbuf_size; +} + + +/** + * Serialize information about a fresh coin we are generating. + * + * @param fc information to serialize + * @param buf buffer to write data in, NULL to just compute + * required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + * @a buf is NULL, number of bytes required + */ +static size_t +serialize_fresh_coin (const struct TALER_PlanchetSecretsP *fc, + char *buf, + size_t off) +{ + if (NULL != buf) + memcpy (&buf[off], + fc, + sizeof (struct TALER_PlanchetSecretsP)); + return sizeof (struct TALER_PlanchetSecretsP); +} + + +/** + * Deserialize information about a fresh coin we are generating. + * + * @param[out] fc information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_fresh_coin (struct TALER_PlanchetSecretsP *fc, + const char *buf, + size_t size, + int *ok) +{ + if (size < sizeof (struct TALER_PlanchetSecretsP)) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + memcpy (fc, + buf, + sizeof (struct TALER_PlanchetSecretsP)); + return sizeof (struct TALER_PlanchetSecretsP); +} + + +/** + * Serialize melt data. + * + * @param md data to serialize + * @param[out] res_size size of buffer returned + * @return serialized melt data + */ +static char * +serialize_melt_data (const struct MeltData *md, + size_t *res_size) +{ + size_t size; + size_t asize; + char *buf; + + size = 0; + asize = (size_t) -1; /* make the compiler happy */ + buf = NULL; + /* we do 2 iterations, #1 to determine total size, #2 to + actually construct the buffer */ + do { + if (0 == size) + { + size = sizeof (struct MeltDataP); + } + else + { + struct MeltDataP *mdp; + + buf = GNUNET_malloc (size); + asize = size; /* just for invariant check later */ + size = sizeof (struct MeltDataP); + mdp = (struct MeltDataP *) buf; + mdp->rc = md->rc; + mdp->num_fresh_coins = htons (md->num_fresh_coins); + } + size += serialize_melted_coin (&md->melted_coin, + buf, + size); + for (unsigned int i=0;i<md->num_fresh_coins;i++) + size += serialize_denomination_key (&md->fresh_pks[i], + buf, + size); + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + for(unsigned int j=0;j<md->num_fresh_coins;j++) + size += serialize_fresh_coin (&md->fresh_coins[i][j], + buf, + size); + } while (NULL == buf); + GNUNET_assert (size == asize); + *res_size = size; + return buf; +} + + +/** + * Deserialize melt data. + * + * @param buf serialized data + * @param buf_size size of @a buf + * @return deserialized melt data, NULL on error + */ +static struct MeltData * +deserialize_melt_data (const char *buf, + size_t buf_size) +{ + struct MeltData *md; + struct MeltDataP mdp; + size_t off; + int ok; + + if (buf_size < sizeof (struct MeltDataP)) + return NULL; + memcpy (&mdp, + buf, + sizeof (struct MeltDataP)); + md = GNUNET_new (struct MeltData); + md->rc = mdp.rc; + md->num_fresh_coins = ntohs (mdp.num_fresh_coins); + md->fresh_pks = GNUNET_new_array (md->num_fresh_coins, + struct TALER_DenominationPublicKey); + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + md->fresh_coins[i] = GNUNET_new_array (md->num_fresh_coins, + struct TALER_PlanchetSecretsP); + off = sizeof (struct MeltDataP); + ok = GNUNET_YES; + off += deserialize_melted_coin (&md->melted_coin, + &buf[off], + buf_size - off, + &ok); + for (unsigned int i=0;(i<md->num_fresh_coins)&&(GNUNET_YES == ok);i++) + off += deserialize_denomination_key (&md->fresh_pks[i], + &buf[off], + buf_size - off, + &ok); + + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + for (unsigned int j=0;(j<md->num_fresh_coins)&&(GNUNET_YES == ok);j++) + off += deserialize_fresh_coin (&md->fresh_coins[i][j], + &buf[off], + buf_size - off, + &ok); + if (off != buf_size) + { + GNUNET_break (0); + ok = GNUNET_NO; + } + if (GNUNET_YES != ok) + { + free_melt_data (md); + GNUNET_free (md); + return NULL; + } + return md; +} + + +/** + * Melt (partially spent) coins to obtain fresh coins that are + * unlinkable to the original coin(s). Note that melting more + * than one coin in a single request will make those coins linkable, + * so the safest operation only melts one coin at a time. + * + * This API is typically used by a wallet. Note that to ensure that + * no money is lost in case of hardware failures, is operation does + * not actually initiate the request. Instead, it generates a buffer + * which the caller must store before proceeding with the actual call + * to #TALER_EXCHANGE_refresh_melt() that will generate the request. + * + * This function does verify that the given request data is internally + * consistent. However, the @a melts_sigs are only verified if + * @a check_sigs is set to #GNUNET_YES, as this may be relatively + * expensive and should be redundant. + * + * Aside from some non-trivial cryptographic operations that might + * take a bit of CPU time to complete, this function returns + * its result immediately and does not start any asynchronous + * processing. This function is also thread-safe. + * + * @param melt_priv private key of the coin to melt + * @param melt_amount amount specifying how much + * the coin will contribute to the melt (including fee) + * @param melt_sig signature affirming the + * validity of the public keys corresponding to the + * @a melt_priv private key + * @param melt_pk denomination key information + * record corresponding to the @a melt_sig + * validity of the keys + * @param check_sig verify the validity of the @a melt_sig signature + * @param fresh_pks_len length of the @a pks array + * @param fresh_pks array of @a pks_len denominations of fresh coins to create + * @param[out] res_size set to the size of the return value, or 0 on error + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * Otherwise, pointer to a buffer of @a res_size to store persistently + * before proceeding to #TALER_EXCHANGE_refresh_melt(). + * Non-null results should be freed using GNUNET_free(). + */ +char * +TALER_EXCHANGE_refresh_prepare (const struct TALER_CoinSpendPrivateKeyP *melt_priv, + const struct TALER_Amount *melt_amount, + const struct TALER_DenominationSignature *melt_sig, + const struct TALER_EXCHANGE_DenomPublicKey *melt_pk, + int check_sig, + unsigned int fresh_pks_len, + const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks, + size_t *res_size) +{ + struct MeltData md; + char *buf; + struct TALER_Amount total; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA]; + struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA]; + + GNUNET_CRYPTO_eddsa_key_get_public (&melt_priv->eddsa_priv, + &coin_pub.eddsa_pub); + /* build up melt data structure */ + md.num_fresh_coins = fresh_pks_len; + md.melted_coin.coin_priv = *melt_priv; + md.melted_coin.melt_amount_with_fee = *melt_amount; + md.melted_coin.fee_melt = melt_pk->fee_refresh; + md.melted_coin.original_value = melt_pk->value; + md.melted_coin.expire_deposit + = melt_pk->expire_deposit; + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (melt_amount->currency, + &total)); + md.melted_coin.pub_key.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (melt_pk->key.rsa_public_key); + md.melted_coin.sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_dup (melt_sig->rsa_signature); + md.fresh_pks = GNUNET_new_array (fresh_pks_len, + struct TALER_DenominationPublicKey); + for (unsigned int i=0;i<fresh_pks_len;i++) + { + md.fresh_pks[i].rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pks[i].key.rsa_public_key); + if ( (GNUNET_OK != + TALER_amount_add (&total, + &total, + &fresh_pks[i].value)) || + (GNUNET_OK != + TALER_amount_add (&total, + &total, + &fresh_pks[i].fee_withdraw)) ) + { + GNUNET_break (0); + free_melt_data (&md); + return NULL; + } + } + /* verify that melt_amount is above total cost */ + if (1 == + TALER_amount_cmp (&total, + melt_amount) ) + { + /* Eh, this operation is more expensive than the + @a melt_amount. This is not OK. */ + GNUNET_break (0); + free_melt_data (&md); + return NULL; + } + + /* build up coins */ + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + { + struct GNUNET_CRYPTO_EcdhePrivateKey *tpk; + + tpk = GNUNET_CRYPTO_ecdhe_key_create (); + md.melted_coin.transfer_priv[i].ecdhe_priv = *tpk; + GNUNET_free (tpk); + + GNUNET_CRYPTO_ecdhe_key_get_public (&md.melted_coin.transfer_priv[i].ecdhe_priv, + &rce[i].transfer_pub.ecdhe_pub); + TALER_link_derive_transfer_secret (melt_priv, + &md.melted_coin.transfer_priv[i], + &trans_sec[i]); + md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len, + struct TALER_PlanchetSecretsP); + rce[i].new_coins = GNUNET_new_array (fresh_pks_len, + struct TALER_RefreshCoinData); + for (unsigned int j=0;j<fresh_pks_len;j++) + { + struct TALER_PlanchetSecretsP *fc = &md.fresh_coins[i][j]; + struct TALER_RefreshCoinData *rcd = &rce[i].new_coins[j]; + struct TALER_PlanchetDetail pd; + + TALER_planchet_setup_refresh (&trans_sec[i], + j, + fc); + if (GNUNET_OK != + TALER_planchet_prepare (&md.fresh_pks[j], + fc, + &pd)) + { + GNUNET_break_op (0); + free_melt_data (&md); + return NULL; + } + rcd->dk = &md.fresh_pks[j]; + rcd->coin_ev = pd.coin_ev; + rcd->coin_ev_size = pd.coin_ev_size; + } + } + + /* Compute refresh commitment */ + TALER_refresh_get_commitment (&md.rc, + TALER_CNC_KAPPA, + fresh_pks_len, + rce, + &coin_pub, + melt_amount); + /* finally, serialize everything */ + buf = serialize_melt_data (&md, + res_size); + for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++) + { + for (unsigned int j = 0; j < fresh_pks_len; j++) + GNUNET_free_non_null (rce[i].new_coins[j].coin_ev); + GNUNET_free_non_null (rce[i].new_coins); + } + free_melt_data (&md); + return buf; +} + + +/* ********************* /refresh/melt ***************************** */ + + +/** + * @brief A /refresh/melt Handle + */ +struct TALER_EXCHANGE_RefreshMeltHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with refresh melt failure results. + */ + TALER_EXCHANGE_RefreshMeltCallback melt_cb; + + /** + * Closure for @e result_cb and @e melt_failure_cb. + */ + void *melt_cb_cls; + + /** + * Actual information about the melt operation. + */ + struct MeltData *md; +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param rmh melt handle + * @param json json reply with the signature + * @param[out] exchange_pub public key of the exchange used for the signature + * @param[out] noreveal_index set to the noreveal index selected by the exchange + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_refresh_melt_signature_ok (struct TALER_EXCHANGE_RefreshMeltHandle *rmh, + const json_t *json, + struct TALER_ExchangePublicKeyP *exchange_pub, + uint32_t *noreveal_index) +{ + struct TALER_ExchangeSignatureP exchange_sig; + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub), + GNUNET_JSON_spec_uint32 ("noreveal_index", noreveal_index), + GNUNET_JSON_spec_end() + }; + struct TALER_RefreshMeltConfirmationPS confirm; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* check that exchange signing key is permitted */ + key_state = TALER_EXCHANGE_get_keys (rmh->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* check that noreveal index is in permitted range */ + if (TALER_CNC_KAPPA <= *noreveal_index) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* verify signature by exchange */ + confirm.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT); + confirm.purpose.size = htonl (sizeof (struct TALER_RefreshMeltConfirmationPS)); + confirm.rc = rmh->md->rc; + confirm.noreveal_index = htonl (*noreveal_index); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT, + &confirm.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Verify that the signatures on the "403 FORBIDDEN" response from the + * exchange demonstrating customer double-spending are valid. + * + * @param rmh melt handle + * @param json json reply with the signature(s) and transaction history + * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not + */ +static int +verify_refresh_melt_signature_forbidden (struct TALER_EXCHANGE_RefreshMeltHandle *rmh, + const json_t *json) +{ + json_t *history; + struct TALER_Amount original_value; + struct TALER_Amount melt_value_with_fee; + struct TALER_Amount total; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("history", &history), + GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), + TALER_JSON_spec_amount ("original_value", &original_value), + TALER_JSON_spec_amount ("requested_value", &melt_value_with_fee), + GNUNET_JSON_spec_end() + }; + const struct MeltedCoin *mc; + + /* parse JSON reply */ + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* Find out which coin was deemed problematic by the exchange */ + mc = &rmh->md->melted_coin; + + /* check basic coin properties */ + if (0 != TALER_amount_cmp (&original_value, + &mc->original_value)) + { + /* We disagree on the value of the coin */ + GNUNET_break_op (0); + json_decref (history); + return GNUNET_SYSERR; + } + if (0 != TALER_amount_cmp (&melt_value_with_fee, + &mc->melt_amount_with_fee)) + { + /* We disagree on the value of the coin */ + GNUNET_break_op (0); + json_decref (history); + return GNUNET_SYSERR; + } + + /* verify coin history */ + history = json_object_get (json, + "history"); + if (GNUNET_OK != + TALER_EXCHANGE_verify_coin_history (original_value.currency, + &coin_pub, + history, + &total)) + { + GNUNET_break_op (0); + json_decref (history); + return GNUNET_SYSERR; + } + json_decref (history); + + /* check if melt operation was really too expensive given history */ + if (GNUNET_OK != + TALER_amount_add (&total, + &total, + &melt_value_with_fee)) + { + /* clearly not OK if our transaction would have caused + the overflow... */ + return GNUNET_OK; + } + + if (0 >= TALER_amount_cmp (&total, + &original_value)) + { + /* transaction should have still fit */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* everything OK, valid proof of double-spending was provided */ + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /refresh/melt request. + * + * @param cls the `struct TALER_EXCHANGE_RefreshMeltHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_refresh_melt_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RefreshMeltHandle *rmh = cls; + uint32_t noreveal_index = TALER_CNC_KAPPA; /* invalid value */ + struct TALER_ExchangePublicKeyP exchange_pub; + const json_t *j = response; + + rmh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + verify_refresh_melt_signature_ok (rmh, + j, + &exchange_pub, + &noreveal_index)) + { + GNUNET_break_op (0); + response_code = 0; + } + if (NULL != rmh->melt_cb) + { + rmh->melt_cb (rmh->melt_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + noreveal_index, + (0 == response_code) ? NULL : &exchange_pub, + j); + rmh->melt_cb = NULL; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + /* Double spending; check signatures on transaction history */ + if (GNUNET_OK != + verify_refresh_melt_signature_forbidden (rmh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, exchange says one of the signatures is + invalid; assuming we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != rmh->melt_cb) + rmh->melt_cb (rmh->melt_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + UINT32_MAX, + NULL, + j); + TALER_EXCHANGE_refresh_melt_cancel (rmh); +} + + +/** + * Submit a melt request to the exchange and get the exchange's + * response. + * + * This API is typically used by a wallet. Note that to ensure that + * no money is lost in case of hardware failures, the provided + * argument should have been constructed using + * #TALER_EXCHANGE_refresh_prepare and committed to persistent storage + * prior to calling this function. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param refresh_data_length size of the @a refresh_data (returned + * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) + * @param refresh_data the refresh data as returned from + #TALER_EXCHANGE_refresh_prepare()) + * @param melt_cb the callback to call with the result + * @param melt_cb_cls closure for @a melt_cb + * @return a handle for this request; NULL if the argument was invalid. + * In this case, neither callback will be called. + */ +struct TALER_EXCHANGE_RefreshMeltHandle * +TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange, + size_t refresh_data_length, + const char *refresh_data, + TALER_EXCHANGE_RefreshMeltCallback melt_cb, + void *melt_cb_cls) +{ + json_t *melt_obj; + struct TALER_EXCHANGE_RefreshMeltHandle *rmh; + CURL *eh; + struct GNUNET_CURL_Context *ctx; + struct MeltData *md; + struct TALER_CoinSpendSignatureP confirm_sig; + struct TALER_RefreshMeltCoinAffirmationPS melt; + + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + md = deserialize_melt_data (refresh_data, + refresh_data_length); + if (NULL == md) + { + GNUNET_break (0); + return NULL; + } + + melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); + melt.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); + melt.rc = md->rc; + TALER_amount_hton (&melt.amount_with_fee, + &md->melted_coin.melt_amount_with_fee); + TALER_amount_hton (&melt.melt_fee, + &md->melted_coin.fee_melt); + GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv, + &melt.coin_pub.eddsa_pub); + GNUNET_CRYPTO_eddsa_sign (&md->melted_coin.coin_priv.eddsa_priv, + &melt.purpose, + &confirm_sig.eddsa_signature); + melt_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", + "coin_pub", + GNUNET_JSON_from_data_auto (&melt.coin_pub), + "denom_pub", + GNUNET_JSON_from_rsa_public_key (md->melted_coin.pub_key.rsa_public_key), + "denom_sig", + GNUNET_JSON_from_rsa_signature (md->melted_coin.sig.rsa_signature), + "confirm_sig", + GNUNET_JSON_from_data_auto (&confirm_sig), + "value_with_fee", + TALER_JSON_from_amount (&md->melted_coin.melt_amount_with_fee), + "rc", + GNUNET_JSON_from_data_auto (&melt.rc)); + if (NULL == melt_obj) + { + GNUNET_break (0); + free_melt_data (md); + return NULL; + } + + /* and now we can at last begin the actual request handling */ + rmh = GNUNET_new (struct TALER_EXCHANGE_RefreshMeltHandle); + rmh->exchange = exchange; + rmh->melt_cb = melt_cb; + rmh->melt_cb_cls = melt_cb_cls; + rmh->md = md; + rmh->url = TEAH_path_to_url (exchange, + "/refresh/melt"); + eh = TEL_curl_easy_get (rmh->url); + GNUNET_assert (NULL != (rmh->json_enc = + json_dumps (melt_obj, + JSON_COMPACT))); + json_decref (melt_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + rmh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (rmh->json_enc))); + ctx = TEAH_handle_to_context (exchange); + rmh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_refresh_melt_finished, + rmh); + return rmh; +} + + +/** + * Cancel a refresh execute request. This function cannot be used + * on a request handle if either callback was already invoked. + * + * @param rmh the refresh melt handle + */ +void +TALER_EXCHANGE_refresh_melt_cancel (struct TALER_EXCHANGE_RefreshMeltHandle *rmh) +{ + if (NULL != rmh->job) + { + GNUNET_CURL_job_cancel (rmh->job); + rmh->job = NULL; + } + free_melt_data (rmh->md); /* does not free 'md' itself */ + GNUNET_free (rmh->md); + GNUNET_free (rmh->url); + GNUNET_free (rmh->json_enc); + GNUNET_free (rmh); +} + + +/* ********************* /refresh/reveal ***************************** */ + + +/** + * @brief A /refresh/reveal Handle + */ +struct TALER_EXCHANGE_RefreshRevealHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_RefreshRevealCallback reveal_cb; + + /** + * Closure for @e reveal_cb. + */ + void *reveal_cb_cls; + + /** + * Actual information about the melt operation. + */ + struct MeltData *md; + + /** + * The index selected by the exchange in cut-and-choose to not be revealed. + */ + uint16_t noreveal_index; + +}; + + +/** + * We got a 200 OK response for the /refresh/reveal operation. + * Extract the coin signatures and return them to the caller. + * The signatures we get from the exchange is for the blinded value. + * Thus, we first must unblind them and then should verify their + * validity. + * + * If everything checks out, we return the unblinded signatures + * to the application via the callback. + * + * @param rrh operation handle + * @param json reply from the exchange + * @param[out] coin_privs array of length `num_fresh_coins`, initialized to contain private keys + * @param[out] sigs array of length `num_fresh_coins`, initialized to cointain RSA signatures + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +refresh_reveal_ok (struct TALER_EXCHANGE_RefreshRevealHandle *rrh, + const json_t *json, + struct TALER_CoinSpendPrivateKeyP *coin_privs, + struct TALER_DenominationSignature *sigs) +{ + json_t *jsona; + struct GNUNET_JSON_Specification outer_spec[] = { + GNUNET_JSON_spec_json ("ev_sigs", &jsona), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + outer_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (! json_is_array (jsona)) + { + /* We expected an array of coins */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_SYSERR; + } + if (rrh->md->num_fresh_coins != json_array_size (jsona)) + { + /* Number of coins generated does not match our expectation */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_SYSERR; + } + for (unsigned int i=0;i<rrh->md->num_fresh_coins;i++) + { + const struct TALER_PlanchetSecretsP *fc; + struct TALER_DenominationPublicKey *pk; + json_t *jsonai; + struct GNUNET_CRYPTO_RsaSignature *blind_sig; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_HashCode coin_hash; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_rsa_signature ("ev_sig", &blind_sig), + GNUNET_JSON_spec_end() + }; + struct TALER_FreshCoin coin; + + fc = &rrh->md->fresh_coins[rrh->noreveal_index][i]; + pk = &rrh->md->fresh_pks[i]; + jsonai = json_array_get (jsona, i); + GNUNET_assert (NULL != jsonai); + + if (GNUNET_OK != + GNUNET_JSON_parse (jsonai, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_SYSERR; + } + + /* needed to verify the signature, and we didn't store it earlier, + hence recomputing it here... */ + GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), + &coin_hash); + if (GNUNET_OK != + TALER_planchet_to_coin (pk, + blind_sig, + fc, + &coin_hash, + &coin)) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_rsa_signature_free (blind_sig); + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_rsa_signature_free (blind_sig); + coin_privs[i] = coin.coin_priv; + sigs[i] = coin.sig; + } + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /refresh/reveal request. + * + * @param cls the `struct TALER_EXCHANGE_RefreshHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_refresh_reveal_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RefreshRevealHandle *rrh = cls; + const json_t *j = response; + + rrh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + struct TALER_CoinSpendPrivateKeyP coin_privs[rrh->md->num_fresh_coins]; + struct TALER_DenominationSignature sigs[rrh->md->num_fresh_coins]; + int ret; + + memset (sigs, 0, sizeof (sigs)); + ret = refresh_reveal_ok (rrh, + j, + coin_privs, + sigs); + if (GNUNET_OK != ret) + { + response_code = 0; + } + else + { + rrh->reveal_cb (rrh->reveal_cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + rrh->md->num_fresh_coins, + coin_privs, + sigs, + j); + rrh->reveal_cb = NULL; + } + for (unsigned int i=0;i<rrh->md->num_fresh_coins;i++) + if (NULL != sigs[i].rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + /* Nothing really to verify, exchange says our reveal is inconsitent + with our commitment, so either side is buggy; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != rrh->reveal_cb) + rrh->reveal_cb (rrh->reveal_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + 0, + NULL, + NULL, + j); + TALER_EXCHANGE_refresh_reveal_cancel (rrh); +} + + +/** + * Submit a /refresh/reval request to the exchange and get the exchange's + * response. + * + * This API is typically used by a wallet. Note that to ensure that + * no money is lost in case of hardware failures, the provided + * arguments should have been committed to persistent storage + * prior to calling this function. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param refresh_data_length size of the @a refresh_data (returned + * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) + * @param refresh_data the refresh data as returned from + #TALER_EXCHANGE_refresh_prepare()) + * @param noreveal_index response from the exchange to the + * #TALER_EXCHANGE_refresh_melt() invocation + * @param reveal_cb the callback to call with the final result of the + * refresh operation + * @param reveal_cb_cls closure for the above callback + * @return a handle for this request; NULL if the argument was invalid. + * In this case, neither callback will be called. + */ +struct TALER_EXCHANGE_RefreshRevealHandle * +TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange, + size_t refresh_data_length, + const char *refresh_data, + uint32_t noreveal_index, + TALER_EXCHANGE_RefreshRevealCallback reveal_cb, + void *reveal_cb_cls) +{ + struct TALER_EXCHANGE_RefreshRevealHandle *rrh; + json_t *transfer_privs; + json_t *new_denoms_h; + json_t *coin_evs; + json_t *reveal_obj; + CURL *eh; + struct GNUNET_CURL_Context *ctx; + struct MeltData *md; + struct TALER_TransferPublicKeyP transfer_pub; + + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + md = deserialize_melt_data (refresh_data, + refresh_data_length); + if (NULL == md) + { + GNUNET_break (0); + return NULL; + } + if (noreveal_index >= TALER_CNC_KAPPA) + { + /* We check this here, as it would be really bad to below just + disclose all the transfer keys. Note that this error should + have been caught way earlier when the exchange replied, but maybe + we had some internal corruption that changed the value... */ + GNUNET_break (0); + return NULL; + } + + /* now transfer_pub */ + GNUNET_CRYPTO_ecdhe_key_get_public (&md->melted_coin.transfer_priv[noreveal_index].ecdhe_priv, + &transfer_pub.ecdhe_pub); + + /* now new_denoms */ + GNUNET_assert (NULL != (new_denoms_h = json_array ())); + GNUNET_assert (NULL != (coin_evs = json_array ())); + for (unsigned int i=0;i<md->num_fresh_coins;i++) + { + struct GNUNET_HashCode denom_hash; + struct TALER_PlanchetDetail pd; + + GNUNET_CRYPTO_rsa_public_key_hash (md->fresh_pks[i].rsa_public_key, + &denom_hash); + GNUNET_assert (0 == + json_array_append_new (new_denoms_h, + GNUNET_JSON_from_data_auto (&denom_hash))); + + if (GNUNET_OK != + TALER_planchet_prepare (&md->fresh_pks[i], + &md->fresh_coins[noreveal_index][i], + &pd)) + { + /* This should have been noticed during the preparation stage. */ + GNUNET_break (0); + json_decref (new_denoms_h); + json_decref (coin_evs); + return NULL; + } + GNUNET_assert (0 == + json_array_append_new (coin_evs, + GNUNET_JSON_from_data (pd.coin_ev, + pd.coin_ev_size))); + GNUNET_free (pd.coin_ev); + } + + /* build array of transfer private keys */ + GNUNET_assert (NULL != (transfer_privs = json_array ())); + for (unsigned int j=0;j<TALER_CNC_KAPPA;j++) + { + if (j == noreveal_index) + { + /* This is crucial: exclude the transfer key for the + noreval index! */ + continue; + } + GNUNET_assert (0 == + json_array_append_new (transfer_privs, + GNUNET_JSON_from_data_auto (&md->melted_coin.transfer_priv[j]))); + } + + /* build main JSON request */ + reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}", + "rc", + GNUNET_JSON_from_data_auto (&md->rc), + "transfer_pub", + GNUNET_JSON_from_data_auto (&transfer_pub), + "transfer_privs", + transfer_privs, + "new_denoms_h", + new_denoms_h, + "coin_evs", + coin_evs); + if (NULL == reveal_obj) + { + GNUNET_break (0); + return NULL; + } + + /* finally, we can actually issue the request */ + rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshRevealHandle); + rrh->exchange = exchange; + rrh->noreveal_index = noreveal_index; + rrh->reveal_cb = reveal_cb; + rrh->reveal_cb_cls = reveal_cb_cls; + rrh->md = md; + rrh->url = TEAH_path_to_url (rrh->exchange, + "/refresh/reveal"); + + eh = TEL_curl_easy_get (rrh->url); + GNUNET_assert (NULL != (rrh->json_enc = + json_dumps (reveal_obj, + JSON_COMPACT))); + json_decref (reveal_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + rrh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (rrh->json_enc))); + ctx = TEAH_handle_to_context (rrh->exchange); + rrh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_refresh_reveal_finished, + rrh); + return rrh; +} + + +/** + * Cancel a refresh reveal request. This function cannot be used + * on a request handle if the callback was already invoked. + * + * @param rrh the refresh reval handle + */ +void +TALER_EXCHANGE_refresh_reveal_cancel (struct TALER_EXCHANGE_RefreshRevealHandle *rrh) +{ + if (NULL != rrh->job) + { + GNUNET_CURL_job_cancel (rrh->job); + rrh->job = NULL; + } + GNUNET_free (rrh->url); + GNUNET_free (rrh->json_enc); + free_melt_data (rrh->md); /* does not free 'md' itself */ + GNUNET_free (rrh->md); + GNUNET_free (rrh); +} + + +/* end of exchange_api_refresh.c */ diff --git a/src/lib/exchange_api_refresh_link.c b/src/lib/exchange_api_refresh_link.c new file mode 100644 index 000000000..ea82c9baa --- /dev/null +++ b/src/lib/exchange_api_refresh_link.c @@ -0,0 +1,444 @@ +/* + This file is part of TALER + Copyright (C) 2015, 2016 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_refresh_link.c + * @brief Implementation of the /refresh/link request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /refresh/link Handle + */ +struct TALER_EXCHANGE_RefreshLinkHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_RefreshLinkCallback link_cb; + + /** + * Closure for @e cb. + */ + void *link_cb_cls; + + /** + * Private key of the coin, required to decode link information. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + +}; + + +/** + * Parse the provided linkage data from the "200 OK" response + * for one of the coins. + * + * @param rlh refresh link handle + * @param json json reply with the data for one coin + * @param coin_num number of the coin to decode + * @param trans_pub our transfer public key + * @param[out] coin_priv where to return private coin key + * @param[out] sig where to return private coin signature + * @param[out] pub where to return the public key for the coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +parse_refresh_link_coin (const struct TALER_EXCHANGE_RefreshLinkHandle *rlh, + const json_t *json, + unsigned int coin_num, + const struct TALER_TransferPublicKeyP *trans_pub, + struct TALER_CoinSpendPrivateKeyP *coin_priv, + struct TALER_DenominationSignature *sig, + struct TALER_DenominationPublicKey *pub) +{ + struct GNUNET_CRYPTO_RsaSignature *bsig; + struct GNUNET_CRYPTO_RsaPublicKey *rpub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_rsa_public_key ("denom_pub", &rpub), + GNUNET_JSON_spec_rsa_signature ("ev_sig", &bsig), + GNUNET_JSON_spec_end() + }; + struct TALER_TransferSecretP secret; + struct TALER_PlanchetSecretsP fc; + + /* parse reply */ + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + TALER_link_recover_transfer_secret (trans_pub, + &rlh->coin_priv, + &secret); + TALER_planchet_setup_refresh (&secret, + coin_num, + &fc); + + /* extract coin and signature */ + *coin_priv = fc.coin_priv; + sig->rsa_signature + = GNUNET_CRYPTO_rsa_unblind (bsig, + &fc.blinding_key.bks, + rpub); + /* clean up */ + pub->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (rpub); + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Parse the provided linkage data from the "200 OK" response + * for one of the coins. + * + * @param[in,out] rlh refresh link handle (callback may be zero'ed out) + * @param json json reply with the data for one coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +parse_refresh_link_ok (struct TALER_EXCHANGE_RefreshLinkHandle *rlh, + const json_t *json) +{ + unsigned int session; + unsigned int num_coins; + int ret; + + if (! json_is_array (json)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + num_coins = 0; + /* Theoretically, a coin may have been melted repeatedly + into different sessions; so the response is an array + which contains information by melting session. That + array contains another array. However, our API returns + a single 1d array, so we flatten the 2d array that is + returned into a single array. Note that usually a coin + is melted at most once, and so we'll only run this + loop once for 'session=0' in most cases. + + num_coins tracks the size of the 1d array we return, + whilst 'i' and 'session' track the 2d array. */ + for (session=0;session<json_array_size (json); session++) + { + json_t *jsona; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("new_coins", &jsona), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (json, + session), + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (! json_is_array (jsona)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + + /* count all coins over all sessions */ + num_coins += json_array_size (jsona); + GNUNET_JSON_parse_free (spec); + } + /* Now that we know how big the 1d array is, allocate + and fill it. */ + { + unsigned int off_coin; /* index into 1d array */ + unsigned int i; + struct TALER_CoinSpendPrivateKeyP coin_privs[num_coins]; + struct TALER_DenominationSignature sigs[num_coins]; + struct TALER_DenominationPublicKey pubs[num_coins]; + + memset (sigs, 0, sizeof (sigs)); + memset (pubs, 0, sizeof (pubs)); + off_coin = 0; + for (session=0;session<json_array_size (json); session++) + { + json_t *jsona; + struct TALER_TransferPublicKeyP trans_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("new_coins", + &jsona), + GNUNET_JSON_spec_fixed_auto ("transfer_pub", + &trans_pub), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (json, + session), + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (! json_is_array (jsona)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + + /* decode all coins */ + for (i=0;i<json_array_size (jsona);i++) + { + GNUNET_assert (i + off_coin < num_coins); + if (GNUNET_OK != + parse_refresh_link_coin (rlh, + json_array_get (jsona, + i), + i, + &trans_pub, + &coin_privs[i+off_coin], + &sigs[i+off_coin], + &pubs[i+off_coin])) + { + GNUNET_break_op (0); + break; + } + } + /* check if we really got all, then invoke callback */ + off_coin += i; + if (i != json_array_size (jsona)) + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + GNUNET_JSON_parse_free (spec); + break; + } + GNUNET_JSON_parse_free (spec); + } /* end of for (session) */ + + if (off_coin == num_coins) + { + rlh->link_cb (rlh->link_cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + num_coins, + coin_privs, + sigs, + pubs, + json); + rlh->link_cb = NULL; + ret = GNUNET_OK; + } + else + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + } + + /* clean up */ + GNUNET_assert (off_coin <= num_coins); + for (i=0;i<off_coin;i++) + { + if (NULL != sigs[i].rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); + if (NULL != pubs[i].rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (pubs[i].rsa_public_key); + } + } + return ret; +} + + +/** + * Function called when we're done processing the + * HTTP /refresh/link request. + * + * @param cls the `struct TALER_EXCHANGE_RefreshLinkHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_refresh_link_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RefreshLinkHandle *rlh = cls; + const json_t *j = response; + + rlh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + parse_refresh_link_ok (rlh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, exchange says this coin was not melted; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != rlh->link_cb) + rlh->link_cb (rlh->link_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + 0, + NULL, + NULL, + NULL, + j); + TALER_EXCHANGE_refresh_link_cancel (rlh); +} + + +/** + * Submit a link request to the exchange and get the exchange's response. + * + * This API is typically not used by anyone, it is more a threat + * against those trying to receive a funds transfer by abusing the + * /refresh protocol. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param coin_priv private key to request link data for + * @param link_cb the callback to call with the useful result of the + * refresh operation the @a coin_priv was involved in (if any) + * @param link_cb_cls closure for @a link_cb + * @return a handle for this request + */ +struct TALER_EXCHANGE_RefreshLinkHandle * +TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + TALER_EXCHANGE_RefreshLinkCallback link_cb, + void *link_cb_cls) +{ + struct TALER_EXCHANGE_RefreshLinkHandle *rlh; + CURL *eh; + struct GNUNET_CURL_Context *ctx; + struct TALER_CoinSpendPublicKeyP coin_pub; + char *pub_str; + char *arg_str; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &coin_pub.eddsa_pub); + pub_str = GNUNET_STRINGS_data_to_string_alloc (&coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)); + GNUNET_asprintf (&arg_str, + "/refresh/link?coin_pub=%s", + pub_str); + GNUNET_free (pub_str); + + rlh = GNUNET_new (struct TALER_EXCHANGE_RefreshLinkHandle); + rlh->exchange = exchange; + rlh->link_cb = link_cb; + rlh->link_cb_cls = link_cb_cls; + rlh->coin_priv = *coin_priv; + rlh->url = TEAH_path_to_url (exchange, arg_str); + GNUNET_free (arg_str); + + + eh = TEL_curl_easy_get (rlh->url); + ctx = TEAH_handle_to_context (exchange); + rlh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_refresh_link_finished, + rlh); + return rlh; +} + + +/** + * Cancel a refresh link request. This function cannot be used + * on a request handle if the callback was already invoked. + * + * @param rlh the refresh link handle + */ +void +TALER_EXCHANGE_refresh_link_cancel (struct TALER_EXCHANGE_RefreshLinkHandle *rlh) +{ + if (NULL != rlh->job) + { + GNUNET_CURL_job_cancel (rlh->job); + rlh->job = NULL; + } + GNUNET_free (rlh->url); + GNUNET_free (rlh); +} + + +/* end of exchange_api_refresh_link.c */ diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c new file mode 100644 index 000000000..75ebdc4e3 --- /dev/null +++ b/src/lib/exchange_api_refund.c @@ -0,0 +1,416 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_refund.c + * @brief Implementation of the /refund request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Refund Handle + */ +struct TALER_EXCHANGE_RefundHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_RefundResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Information the exchange should sign in response. + */ + struct TALER_RefundConfirmationPS depconf; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param rh refund handle + * @param json json reply with the signature + * @param[out] exchange_pub set to the exchange's public key + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_refund_signature_ok (const struct TALER_EXCHANGE_RefundHandle *rh, + const json_t *json, + struct TALER_ExchangePublicKeyP *exchange_pub) +{ + struct TALER_ExchangeSignatureP exchange_sig; + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("sig", &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("pub", exchange_pub), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = TALER_EXCHANGE_get_keys (rh->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND, + &rh->depconf.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /refund request. + * + * @param cls the `struct TALER_EXCHANGE_RefundHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_refund_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RefundHandle *rh = cls; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangePublicKeyP *ep = NULL; + const json_t *j = response; + + rh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + verify_refund_signature_ok (rh, + j, + &exchange_pub)) + { + GNUNET_break_op (0); + response_code = 0; + } + else + { + ep = &exchange_pub; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_GONE: + /* Kind of normal: the money was already sent to the merchant + (it was too late for the refund). */ + break; + case MHD_HTTP_PRECONDITION_FAILED: + /* Client request was inconsistent; might be a currency missmatch + problem. */ + break; + case MHD_HTTP_CONFLICT: + /* Two refund requests were made about the same deposit, but + carrying different refund transaction ids. */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + rh->cb (rh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + ep, + j); + TALER_EXCHANGE_refund_cancel (rh); +} + + +/** + * Submit a refund request to the exchange and get the exchange's + * response. This API is used by a merchant. Note that + * while we return the response verbatim to the caller for further + * processing, we do already verify that the response is well-formed + * (i.e. that signatures included in the response are all valid). If + * the exchange's reply is not well-formed, we return an HTTP status code + * of zero to @a cb. + * + * The @a exchange must be ready to operate (i.e. have + * finished processing the /keys reply). If this check fails, we do + * NOT initiate the transaction with the exchange and instead return NULL. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param amount the amount to be refunded; must be larger than the refund fee + * (as that fee is still being subtracted), and smaller than the amount + * (with deposit fee) of the original deposit contribution of this coin + * @param refund_fee fee applicable to this coin for the refund + * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded + * @param coin_pub coin’s public key of the coin from the original deposit operation + * @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation); + * this is needed as we may first do a partial refund and later a full refund. If both + * refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint + * refund requests different (as requests are idempotent and otherwise the 2nd refund might not work). + * @param merchant_priv the private key of the merchant, used to generate signature for refund request + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_EXCHANGE_RefundHandle * +TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_Amount *amount, + const struct TALER_Amount *refund_fee, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t rtransaction_id, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + TALER_EXCHANGE_RefundResultCallback cb, + void *cb_cls) +{ + struct TALER_RefundRequestPS rr; + struct TALER_MerchantSignatureP merchant_sig; + + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND); + rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS)); + rr.h_contract_terms = *h_contract_terms; + rr.coin_pub = *coin_pub; + GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, + &rr.merchant.eddsa_pub); + rr.rtransaction_id = GNUNET_htonll (rtransaction_id); + TALER_amount_hton (&rr.refund_amount, + amount); + TALER_amount_hton (&rr.refund_fee, + refund_fee); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, + &rr.purpose, + &merchant_sig.eddsa_sig)); + return TALER_EXCHANGE_refund2 (exchange, + amount, + refund_fee, + h_contract_terms, + coin_pub, + rtransaction_id, + &rr.merchant, + &merchant_sig, + cb, + cb_cls); +} + + +/** + * Submit a refund request to the exchange and get the exchange's + * response. This API is used by a merchant. Note that + * while we return the response verbatim to the caller for further + * processing, we do already verify that the response is well-formed + * (i.e. that signatures included in the response are all valid). If + * the exchange's reply is not well-formed, we return an HTTP status code + * of zero to @a cb. + * + * The @a exchange must be ready to operate (i.e. have + * finished processing the /keys reply). If this check fails, we do + * NOT initiate the transaction with the exchange and instead return NULL. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param amount the amount to be refunded; must be larger than the refund fee + * (as that fee is still being subtracted), and smaller than the amount + * (with deposit fee) of the original deposit contribution of this coin + * @param refund_fee fee applicable to this coin for the refund + * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded + * @param coin_pub coin’s public key of the coin from the original deposit operation + * @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation); + * this is needed as we may first do a partial refund and later a full refund. If both + * refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint + * refund requests different (as requests are idempotent and otherwise the 2nd refund might not work). + * @param merchant_pub public key of the merchant + * @param merchant_sig signature affirming the refund from the merchant + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_EXCHANGE_RefundHandle * +TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_Amount *amount, + const struct TALER_Amount *refund_fee, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t rtransaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_MerchantSignatureP *merchant_sig, + TALER_EXCHANGE_RefundResultCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_RefundHandle *rh; + struct GNUNET_CURL_Context *ctx; + json_t *refund_obj; + CURL *eh; + +refund_obj = json_pack ("{s:o, s:o," /* amount/fee */ + " s:o, s:o," /* h_contract_terms, coin_pub */ + " s:I," /* rtransaction id */ + " s:o, s:o}", /* merchant_pub, merchant_sig */ + "refund_amount", TALER_JSON_from_amount (amount), + "refund_fee", TALER_JSON_from_amount (refund_fee), + "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), + "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), + "rtransaction_id", (json_int_t) rtransaction_id, + "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub), + "merchant_sig", GNUNET_JSON_from_data_auto (merchant_sig) + ); + if (NULL == refund_obj) + { + GNUNET_break (0); + return NULL; + } + + rh = GNUNET_new (struct TALER_EXCHANGE_RefundHandle); + rh->exchange = exchange; + rh->cb = cb; + rh->cb_cls = cb_cls; + rh->url = TEAH_path_to_url (exchange, "/refund"); + rh->depconf.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS)); + rh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND); + rh->depconf.h_contract_terms = *h_contract_terms; + rh->depconf.coin_pub = *coin_pub; + rh->depconf.merchant = *merchant_pub; + rh->depconf.rtransaction_id = GNUNET_htonll (rtransaction_id); + TALER_amount_hton (&rh->depconf.refund_amount, + amount); + TALER_amount_hton (&rh->depconf.refund_fee, + refund_fee); + + eh = TEL_curl_easy_get (rh->url); + GNUNET_assert (NULL != (rh->json_enc = + json_dumps (refund_obj, + JSON_COMPACT))); + json_decref (refund_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for refund: `%s'\n", + rh->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + rh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (rh->json_enc))); + ctx = TEAH_handle_to_context (exchange); + rh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_refund_finished, + rh); + return rh; +} + + +/** + * Cancel a refund permission request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param refund the refund permission request handle + */ +void +TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund) +{ + if (NULL != refund->job) + { + GNUNET_CURL_job_cancel (refund->job); + refund->job = NULL; + } + GNUNET_free (refund->url); + GNUNET_free (refund->json_enc); + GNUNET_free (refund); +} + + +/* end of exchange_api_refund.c */ diff --git a/src/lib/exchange_api_reserve.c b/src/lib/exchange_api_reserve.c new file mode 100644 index 000000000..d4d0cb9a1 --- /dev/null +++ b/src/lib/exchange_api_reserve.c @@ -0,0 +1,1203 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_reserve.c + * @brief Implementation of the /reserve requests of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/* ********************** /reserve/status ********************** */ + +/** + * @brief A Withdraw Status Handle + */ +struct TALER_EXCHANGE_ReserveStatusHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ReserveStatusResultCallback cb; + + /** + * Public key of the reserve we are querying. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * Parse history given in JSON format and return it in binary + * format. + * + * @param exchange connection to the exchange we can use + * @param history JSON array with the history + * @param reserve_pub public key of the reserve to inspect + * @param currency currency we expect the balance to be in + * @param[out] balance final balance + * @param history_length number of entries in @a history + * @param[out] rhistory array of length @a history_length, set to the + * parsed history entries + * @return #GNUNET_OK if history was valid and @a rhistory and @a balance + * were set, + * #GNUNET_SYSERR if there was a protocol violation in @a history + */ +static int +parse_reserve_history (struct TALER_EXCHANGE_Handle *exchange, + const json_t *history, + const struct TALER_ReservePublicKeyP *reserve_pub, + const char *currency, + struct TALER_Amount *balance, + unsigned int history_length, + struct TALER_EXCHANGE_ReserveHistory *rhistory) +{ + struct GNUNET_HashCode uuid[history_length]; + unsigned int uuid_off; + struct TALER_Amount total_in; + struct TALER_Amount total_out; + size_t off; + + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_in)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_out)); + uuid_off = 0; + for (off=0;off<history_length;off++) + { + json_t *transaction; + struct TALER_Amount amount; + const char *type; + struct GNUNET_JSON_Specification hist_spec[] = { + GNUNET_JSON_spec_string ("type", &type), + TALER_JSON_spec_amount ("amount", + &amount), + /* 'wire' and 'signature' are optional depending on 'type'! */ + GNUNET_JSON_spec_end() + }; + + transaction = json_array_get (history, + off); + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + hist_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rhistory[off].amount = amount; + + if (0 == strcasecmp (type, + "DEPOSIT")) + { + const char *wire_url; + void *wire_reference; + size_t wire_reference_size; + struct GNUNET_TIME_Absolute timestamp; + + struct GNUNET_JSON_Specification withdraw_spec[] = { + GNUNET_JSON_spec_varsize ("wire_reference", + &wire_reference, + &wire_reference_size), + GNUNET_JSON_spec_absolute_time ("timestamp", + ×tamp), + GNUNET_JSON_spec_string ("sender_account_url", + &wire_url), + GNUNET_JSON_spec_end() + }; + + rhistory[off].type = TALER_EXCHANGE_RTT_DEPOSIT; + if (GNUNET_OK != + TALER_amount_add (&total_in, + &total_in, + &amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + withdraw_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rhistory[off].details.in_details.sender_url = GNUNET_strdup (wire_url); + rhistory[off].details.in_details.wire_reference = wire_reference; + rhistory[off].details.in_details.wire_reference_size = wire_reference_size; + rhistory[off].details.in_details.timestamp = timestamp; + /* end type==DEPOSIT */ + } + else if (0 == strcasecmp (type, + "WITHDRAW")) + { + struct TALER_ReserveSignatureP sig; + struct TALER_WithdrawRequestPS withdraw_purpose; + struct GNUNET_JSON_Specification withdraw_spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &sig), + TALER_JSON_spec_amount_nbo ("withdraw_fee", + &withdraw_purpose.withdraw_fee), + GNUNET_JSON_spec_fixed_auto ("h_denom_pub", + &withdraw_purpose.h_denomination_pub), + GNUNET_JSON_spec_fixed_auto ("h_coin_envelope", + &withdraw_purpose.h_coin_envelope), + GNUNET_JSON_spec_end() + }; + unsigned int i; + + rhistory[off].type = TALER_EXCHANGE_RTT_WITHDRAWAL; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + withdraw_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + withdraw_purpose.purpose.size + = htonl (sizeof (withdraw_purpose)); + withdraw_purpose.purpose.purpose + = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); + withdraw_purpose.reserve_pub = *reserve_pub; + TALER_amount_hton (&withdraw_purpose.amount_with_fee, + &amount); + /* Check that the signature is a valid withdraw request */ + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, + &withdraw_purpose.purpose, + &sig.eddsa_signature, + &reserve_pub->eddsa_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + /* TODO: check that withdraw fee matches expectations! */ + rhistory[off].details.out_authorization_sig + = json_object_get (transaction, + "signature"); + /* Check check that the same withdraw transaction + isn't listed twice by the exchange. We use the + "uuid" array to remember the hashes of all + purposes, and compare the hashes to find + duplicates. */ + GNUNET_CRYPTO_hash (&withdraw_purpose, + ntohl (withdraw_purpose.purpose.size), + &uuid[uuid_off]); + for (i=0;i<uuid_off;i++) + { + if (0 == memcmp (&uuid[uuid_off], + &uuid[i], + sizeof (struct GNUNET_HashCode))) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + } + uuid_off++; + + if (GNUNET_OK != + TALER_amount_add (&total_out, + &total_out, + &amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + /* end type==WITHDRAW */ + } + else if (0 == strcasecmp (type, + "PAYBACK")) + { + struct TALER_PaybackConfirmationPS pc; + struct GNUNET_TIME_Absolute timestamp; + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification payback_spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &pc.coin_pub), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rhistory[off].details.payback_details.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rhistory[off].details.payback_details.exchange_pub), + GNUNET_JSON_spec_absolute_time_nbo ("timestamp", + &pc.timestamp), + GNUNET_JSON_spec_end() + }; + + rhistory[off].type = TALER_EXCHANGE_RTT_PAYBACK; + rhistory[off].amount = amount; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + payback_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rhistory[off].details.payback_details.coin_pub = pc.coin_pub; + TALER_amount_hton (&pc.payback_amount, + &amount); + pc.purpose.size = htonl (sizeof (pc)); + pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK); + pc.reserve_pub = *reserve_pub; + timestamp = GNUNET_TIME_absolute_ntoh (pc.timestamp); + rhistory[off].details.payback_details.timestamp = timestamp; + + key_state = TALER_EXCHANGE_get_keys (exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &rhistory[off].details.payback_details.exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK, + &pc.purpose, + &rhistory[off].details.payback_details.exchange_sig.eddsa_signature, + &rhistory[off].details.payback_details.exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&total_in, + &total_in, + &rhistory[off].amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* end type==PAYBACK */ + } + else if (0 == strcasecmp (type, + "CLOSING")) + { + const struct TALER_EXCHANGE_Keys *key_state; + struct TALER_ReserveCloseConfirmationPS rcc; + struct GNUNET_TIME_Absolute timestamp; + struct GNUNET_JSON_Specification closing_spec[] = { + GNUNET_JSON_spec_string ("receiver_account_details", + &rhistory[off].details.close_details.receiver_account_details), + GNUNET_JSON_spec_fixed_auto ("wtid", + &rhistory[off].details.close_details.wtid), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rhistory[off].details.close_details.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rhistory[off].details.close_details.exchange_pub), + TALER_JSON_spec_amount_nbo ("closing_fee", + &rcc.closing_fee), + GNUNET_JSON_spec_absolute_time_nbo ("timestamp", + &rcc.timestamp), + GNUNET_JSON_spec_end() + }; + + rhistory[off].type = TALER_EXCHANGE_RTT_CLOSE; + rhistory[off].amount = amount; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + closing_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + TALER_amount_hton (&rcc.closing_amount, + &amount); + GNUNET_CRYPTO_hash (rhistory[off].details.close_details.receiver_account_details, + strlen (rhistory[off].details.close_details.receiver_account_details) + 1, + &rcc.h_wire); + rcc.wtid = rhistory[off].details.close_details.wtid; + rcc.purpose.size = htonl (sizeof (rcc)); + rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED); + rcc.reserve_pub = *reserve_pub; + timestamp = GNUNET_TIME_absolute_ntoh (rcc.timestamp); + rhistory[off].details.close_details.timestamp = timestamp; + + key_state = TALER_EXCHANGE_get_keys (exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &rhistory[off].details.close_details.exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED, + &rcc.purpose, + &rhistory[off].details.close_details.exchange_sig.eddsa_signature, + &rhistory[off].details.close_details.exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&total_out, + &total_out, + &rhistory[off].amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* end type==CLOSING */ + } + else + { + /* unexpected 'type', protocol incompatibility, complain! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + + /* check balance = total_in - total_out < withdraw-amount */ + if (GNUNET_SYSERR == + TALER_amount_subtract (balance, + &total_in, + &total_out)) + { + /* total_in < total_out, why did the exchange ever allow this!? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Free memory (potentially) allocated by #parse_reserve_history(). + * + * @param rhistory result to free + * @param len number of entries in @a rhistory + */ +static void +free_rhistory (struct TALER_EXCHANGE_ReserveHistory *rhistory, + unsigned int len) +{ + for (unsigned int i=0;i<len;i++) + { + switch (rhistory[i].type) + { + case TALER_EXCHANGE_RTT_DEPOSIT: + GNUNET_free_non_null (rhistory[i].details.in_details.wire_reference); + GNUNET_free_non_null (rhistory[i].details.in_details.sender_url); + break; + case TALER_EXCHANGE_RTT_WITHDRAWAL: + break; + case TALER_EXCHANGE_RTT_PAYBACK: + break; + case TALER_EXCHANGE_RTT_CLOSE: + // should we free "receiver_account_details" ? + break; + } + } +} + + +/** + * Function called when we're done processing the + * HTTP /reserve/status request. + * + * @param cls the `struct TALER_EXCHANGE_ReserveStatusHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserve_status_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ReserveStatusHandle *rsh = cls; + const json_t *j = response; + + rsh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + /* TODO: move into separate function... */ + json_t *history; + unsigned int len; + struct TALER_Amount balance; + struct TALER_Amount balance_from_history; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("balance", &balance), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + history = json_object_get (j, + "history"); + if (NULL == history) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + len = json_array_size (history); + { + struct TALER_EXCHANGE_ReserveHistory rhistory[len]; + + memset (rhistory, 0, sizeof (rhistory)); + if (GNUNET_OK != + parse_reserve_history (rsh->exchange, + history, + &rsh->reserve_pub, + balance.currency, + &balance_from_history, + len, + rhistory)) + { + GNUNET_break_op (0); + response_code = 0; + } + if ( (0 != response_code) && + (0 != + TALER_amount_cmp (&balance_from_history, + &balance)) ) + { + /* exchange cannot add up balances!? */ + GNUNET_break_op (0); + response_code = 0; + } + if (0 != response_code) + { + rsh->cb (rsh->cb_cls, + response_code, + TALER_EC_NONE, + j, + &balance, + len, + rhistory); + rsh->cb = NULL; + } + free_rhistory (rhistory, + len); + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != rsh->cb) + { + rsh->cb (rsh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + j, + NULL, + 0, NULL); + rsh->cb = NULL; + } + TALER_EXCHANGE_reserve_status_cancel (rsh); +} + + +/** + * Submit a request to obtain the transaction history of a reserve + * from the exchange. Note that while we return the full response to the + * caller for further processing, we do already verify that the + * response is well-formed (i.e. that signatures included in the + * response are all valid and add up to the balance). If the exchange's + * reply is not well-formed, we return an HTTP status code of zero to + * @a cb. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param reserve_pub public key of the reserve to inspect + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_EXCHANGE_ReserveStatusHandle * +TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_ReservePublicKeyP *reserve_pub, + TALER_EXCHANGE_ReserveStatusResultCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ReserveStatusHandle *rsh; + struct GNUNET_CURL_Context *ctx; + CURL *eh; + char *pub_str; + char *arg_str; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + pub_str = GNUNET_STRINGS_data_to_string_alloc (reserve_pub, + sizeof (struct TALER_ReservePublicKeyP)); + GNUNET_asprintf (&arg_str, + "/reserve/status?reserve_pub=%s", + pub_str); + GNUNET_free (pub_str); + rsh = GNUNET_new (struct TALER_EXCHANGE_ReserveStatusHandle); + rsh->exchange = exchange; + rsh->cb = cb; + rsh->cb_cls = cb_cls; + rsh->reserve_pub = *reserve_pub; + rsh->url = TEAH_path_to_url (exchange, + arg_str); + GNUNET_free (arg_str); + + eh = TEL_curl_easy_get (rsh->url); + ctx = TEAH_handle_to_context (exchange); + rsh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_NO, + &handle_reserve_status_finished, + rsh); + return rsh; +} + + +/** + * Cancel a reserve status request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param rsh the reserve status request handle + */ +void +TALER_EXCHANGE_reserve_status_cancel (struct TALER_EXCHANGE_ReserveStatusHandle *rsh) +{ + if (NULL != rsh->job) + { + GNUNET_CURL_job_cancel (rsh->job); + rsh->job = NULL; + } + GNUNET_free (rsh->url); + GNUNET_free (rsh); +} + + +/* ********************** /reserve/withdraw ********************** */ + +/** + * @brief A Withdraw Sign Handle + */ +struct TALER_EXCHANGE_ReserveWithdrawHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ReserveWithdrawResultCallback cb; + + /** + * Secrets of the planchet. + */ + struct TALER_PlanchetSecretsP ps; + + /** + * Denomination key we are withdrawing. + */ + const struct TALER_EXCHANGE_DenomPublicKey *pk; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Hash of the public key of the coin we are signing. + */ + struct GNUNET_HashCode c_hash; + + /** + * Public key of the reserve we are withdrawing from. + */ + struct TALER_ReservePublicKeyP reserve_pub; + +}; + + +/** + * We got a 200 OK response for the /reserve/withdraw operation. + * Extract the coin's signature and return it to the caller. + * The signature we get from the exchange is for the blinded value. + * Thus, we first must unblind it and then should verify its + * validity against our coin's hash. + * + * If everything checks out, we return the unblinded signature + * to the application via the callback. + * + * @param wsh operation handle + * @param json reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +reserve_withdraw_ok (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh, + const json_t *json) +{ + struct GNUNET_CRYPTO_RsaSignature *blind_sig; + struct TALER_FreshCoin fc; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_rsa_signature ("ev_sig", + &blind_sig), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_planchet_to_coin (&wsh->pk->key, + blind_sig, + &wsh->ps, + &wsh->c_hash, + &fc)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + GNUNET_JSON_parse_free (spec); + + /* signature is valid, return it to the application */ + wsh->cb (wsh->cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + &fc.sig, + json); + /* make sure callback isn't called again after return */ + wsh->cb = NULL; + GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature); + return GNUNET_OK; +} + + +/** + * We got a 403 FORBIDDEN response for the /reserve/withdraw operation. + * Check the signatures on the withdraw transactions in the provided + * history and that the balances add up. We don't do anything directly + * with the information, as the JSON will be returned to the application. + * However, our job is ensuring that the exchange followed the protocol, and + * this in particular means checking all of the signatures in the history. + * + * @param wsh operation handle + * @param json reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +reserve_withdraw_payment_required (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh, + const json_t *json) +{ + struct TALER_Amount balance; + struct TALER_Amount balance_from_history; + struct TALER_Amount requested_amount; + json_t *history; + size_t len; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("balance", &balance), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + history = json_object_get (json, + "history"); + if (NULL == history) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* go over transaction history and compute + total incoming and outgoing amounts */ + len = json_array_size (history); + { + struct TALER_EXCHANGE_ReserveHistory *rhistory; + + /* Use heap allocation as "len" may be very big and thus this may + not fit on the stack. Use "GNUNET_malloc_large" as a malicious + exchange may theoretically try to crash us by giving a history + that does not fit into our memory. */ + rhistory = GNUNET_malloc_large (sizeof (struct TALER_EXCHANGE_ReserveHistory) * len); + if (NULL == rhistory) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + parse_reserve_history (wsh->exchange, + history, + &wsh->reserve_pub, + balance.currency, + &balance_from_history, + len, + rhistory)) + { + GNUNET_break_op (0); + free_rhistory (rhistory, + len); + return GNUNET_SYSERR; + } + free_rhistory (rhistory, + len); + } + + if (0 != + TALER_amount_cmp (&balance_from_history, + &balance)) + { + /* exchange cannot add up balances!? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* Compute how much we expected to charge to the reserve */ + if (GNUNET_OK != + TALER_amount_add (&requested_amount, + &wsh->pk->value, + &wsh->pk->fee_withdraw)) + { + /* Overflow here? Very strange, our CPU must be fried... */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* Check that funds were really insufficient */ + if (0 >= TALER_amount_cmp (&requested_amount, + &balance)) + { + /* Requested amount is smaller or equal to reported balance, + so this should not have failed. */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserve/withdraw request. + * + * @param cls the `struct TALER_EXCHANGE_ReserveWithdrawHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserve_withdraw_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh = cls; + const json_t *j = response; + + wsh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + reserve_withdraw_ok (wsh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + /* The exchange says that the reserve has insufficient funds; + check the signatures in the history... */ + if (GNUNET_OK != + reserve_withdraw_payment_required (wsh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_UNAUTHORIZED: + GNUNET_break (0); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, the exchange basically just says + that it doesn't know this reserve. Can happen if we + query before the wire transfer went through. + We should simply pass the JSON reply to the application. */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != wsh->cb) + { + wsh->cb (wsh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + NULL, + j); + wsh->cb = NULL; + } + TALER_EXCHANGE_reserve_withdraw_cancel (wsh); +} + + +/** + * Helper function for #TALER_EXCHANGE_reserve_withdraw2() and + * #TALER_EXCHANGE_reserve_withdraw(). + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to create + * @param reserve_sig signature from the reserve authorizing the withdrawal + * @param reserve_pub public key of the reserve to withdraw from + * @param ps secrets of the planchet + * caller must have committed this value to disk before the call (with @a pk) + * @param pd planchet details matching @a ps + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for @a res_cb + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_ReserveWithdrawHandle * +reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_ReserveSignatureP *reserve_sig, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_PlanchetSecretsP *ps, + const struct TALER_PlanchetDetail *pd, + TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; + struct GNUNET_CURL_Context *ctx; + json_t *withdraw_obj; + CURL *eh; + + wsh = GNUNET_new (struct TALER_EXCHANGE_ReserveWithdrawHandle); + wsh->exchange = exchange; + wsh->cb = res_cb; + wsh->cb_cls = res_cb_cls; + wsh->pk = pk; + wsh->reserve_pub = *reserve_pub; + wsh->c_hash = pd->c_hash; + withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub and coin_ev */ + " s:o, s:o}",/* reserve_pub and reserve_sig */ + "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->key.rsa_public_key), + "coin_ev", GNUNET_JSON_from_data (pd->coin_ev, + pd->coin_ev_size), + "reserve_pub", GNUNET_JSON_from_data_auto (reserve_pub), + "reserve_sig", GNUNET_JSON_from_data_auto (reserve_sig)); + if (NULL == withdraw_obj) + { + GNUNET_break (0); + return NULL; + } + + wsh->ps = *ps; + wsh->url = TEAH_path_to_url (exchange, "/reserve/withdraw"); + + eh = TEL_curl_easy_get (wsh->url); + GNUNET_assert (NULL != (wsh->json_enc = + json_dumps (withdraw_obj, + JSON_COMPACT))); + json_decref (withdraw_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + wsh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (wsh->json_enc))); + ctx = TEAH_handle_to_context (exchange); + wsh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_reserve_withdraw_finished, + wsh); + return wsh; +} + + +/** + * Withdraw a coin from the exchange using a /reserve/withdraw request. Note + * that to ensure that no money is lost in case of hardware failures, + * the caller must have committed (most of) the arguments to disk + * before calling, and be ready to repeat the request with the same + * arguments in case of failures. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to create + * @param reserve_priv private key of the reserve to withdraw from + * @param ps secrets of the planchet + * caller must have committed this value to disk before the call (with @a pk) + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for the above callback + * @return handle for the operation on success, NULL on error, i.e. + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_ReserveWithdrawHandle * +TALER_EXCHANGE_reserve_withdraw (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_PlanchetSecretsP *ps, + TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, + void *res_cb_cls) +{ + struct TALER_Amount amount_with_fee; + struct TALER_ReserveSignatureP reserve_sig; + struct TALER_WithdrawRequestPS req; + struct TALER_PlanchetDetail pd; + struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; + + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &req.reserve_pub.eddsa_pub); + req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)); + req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); + if (GNUNET_OK != + TALER_amount_add (&amount_with_fee, + &pk->fee_withdraw, + &pk->value)) + { + /* exchange gave us denomination keys that overflow like this!? */ + GNUNET_break_op (0); + return NULL; + } + TALER_amount_hton (&req.amount_with_fee, + &amount_with_fee); + TALER_amount_hton (&req.withdraw_fee, + &pk->fee_withdraw); + if (GNUNET_OK != + TALER_planchet_prepare (&pk->key, + ps, + &pd)) + { + GNUNET_break_op (0); + return NULL; + } + req.h_denomination_pub = pd.denom_pub_hash; + GNUNET_CRYPTO_hash (pd.coin_ev, + pd.coin_ev_size, + &req.h_coin_envelope); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, + &req.purpose, + &reserve_sig.eddsa_signature)); + wsh = reserve_withdraw_internal (exchange, + pk, + &reserve_sig, + &req.reserve_pub, + ps, + &pd, + res_cb, + res_cb_cls); + GNUNET_free (pd.coin_ev); + return wsh; +} + + +/** + * Withdraw a coin from the exchange using a /reserve/withdraw + * request. This API is typically used by a wallet to withdraw a tip + * where the reserve's signature was created by the merchant already. + * + * Note that to ensure that no money is lost in case of hardware + * failures, the caller must have committed (most of) the arguments to + * disk before calling, and be ready to repeat the request with the + * same arguments in case of failures. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to create + * @param reserve_sig signature from the reserve authorizing the withdrawal + * @param reserve_pub public key of the reserve to withdraw from + * @param ps secrets of the planchet + * caller must have committed this value to disk before the call (with @a pk) + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for @a res_cb + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_ReserveWithdrawHandle * +TALER_EXCHANGE_reserve_withdraw2 (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_ReserveSignatureP *reserve_sig, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_PlanchetSecretsP *ps, + TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; + struct TALER_PlanchetDetail pd; + + if (GNUNET_OK != + TALER_planchet_prepare (&pk->key, + ps, + &pd)) + { + GNUNET_break_op (0); + return NULL; + } + wsh = reserve_withdraw_internal (exchange, + pk, + reserve_sig, + reserve_pub, + ps, + &pd, + res_cb, + res_cb_cls); + GNUNET_free (pd.coin_ev); + return wsh; +} + + +/** + * Cancel a withdraw status request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param sign the withdraw sign request handle + */ +void +TALER_EXCHANGE_reserve_withdraw_cancel (struct TALER_EXCHANGE_ReserveWithdrawHandle *sign) +{ + if (NULL != sign->job) + { + GNUNET_CURL_job_cancel (sign->job); + sign->job = NULL; + } + GNUNET_free (sign->url); + GNUNET_free (sign->json_enc); + GNUNET_free (sign); +} + + +/* end of exchange_api_reserve.c */ diff --git a/src/lib/exchange_api_track_transaction.c b/src/lib/exchange_api_track_transaction.c new file mode 100644 index 000000000..0942ce84a --- /dev/null +++ b/src/lib/exchange_api_track_transaction.c @@ -0,0 +1,367 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_track_transaction.c + * @brief Implementation of the /track/transaction request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Deposit Wtid Handle + */ +struct TALER_EXCHANGE_TrackTransactionHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_TrackTransactionCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Information the exchange should sign in response. + * (with pre-filled fields from the request). + */ + struct TALER_ConfirmWirePS depconf; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param dwh deposit wtid handle + * @param json json reply with the signature + * @param[out] exchange_pub set to the exchange's public key + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_wtid_signature_ok (const struct TALER_EXCHANGE_TrackTransactionHandle *dwh, + const json_t *json, + struct TALER_ExchangePublicKeyP *exchange_pub) +{ + struct TALER_ExchangeSignatureP exchange_sig; + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = TALER_EXCHANGE_get_keys (dwh->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE, + &dwh->depconf.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /track/transaction request. + * + * @param cls the `struct TALER_EXCHANGE_TrackTransactionHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_deposit_wtid_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_TrackTransactionHandle *dwh = cls; + const struct TALER_WireTransferIdentifierRawP *wtid = NULL; + struct GNUNET_TIME_Absolute execution_time = GNUNET_TIME_UNIT_FOREVER_ABS; + const struct TALER_Amount *coin_contribution = NULL; + struct TALER_Amount coin_contribution_s; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangePublicKeyP *ep = NULL; + const json_t *j = response; + + dwh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("wtid", &dwh->depconf.wtid), + GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time), + TALER_JSON_spec_amount ("coin_contribution", &coin_contribution_s), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + wtid = &dwh->depconf.wtid; + dwh->depconf.execution_time = GNUNET_TIME_absolute_hton (execution_time); + TALER_amount_hton (&dwh->depconf.coin_contribution, + &coin_contribution_s); + coin_contribution = &coin_contribution_s; + if (GNUNET_OK != + verify_deposit_wtid_signature_ok (dwh, + j, + &exchange_pub)) + { + GNUNET_break_op (0); + response_code = 0; + } + else + { + ep = &exchange_pub; + } + } + break; + case MHD_HTTP_ACCEPTED: + { + /* Transaction known, but not executed yet */ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Exchange does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + dwh->cb (dwh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + ep, + j, + wtid, + execution_time, + coin_contribution); + TALER_EXCHANGE_track_transaction_cancel (dwh); +} + + +/** + * Obtain wire transfer details about an existing deposit operation. + * + * @param exchange the exchange to query + * @param merchant_priv the merchant's private key + * @param h_wire hash of merchant's wire transfer details + * @param h_contract_terms hash of the proposal data from the contract + * between merchant and customer + * @param coin_pub public key of the coin + * @param cb function to call with the result + * @param cb_cls closure for @a cb + * @return handle to abort request + */ +struct TALER_EXCHANGE_TrackTransactionHandle * +TALER_EXCHANGE_track_transaction (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + TALER_EXCHANGE_TrackTransactionCallback cb, + void *cb_cls) +{ + struct TALER_DepositTrackPS dtp; + struct TALER_MerchantSignatureP merchant_sig; + struct TALER_EXCHANGE_TrackTransactionHandle *dwh; + struct GNUNET_CURL_Context *ctx; + json_t *deposit_wtid_obj; + CURL *eh; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + dtp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION); + dtp.purpose.size = htonl (sizeof (dtp)); + dtp.h_contract_terms = *h_contract_terms; + dtp.h_wire = *h_wire; + GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, + &dtp.merchant.eddsa_pub); + + dtp.coin_pub = *coin_pub; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, + &dtp.purpose, + &merchant_sig.eddsa_sig)); + deposit_wtid_obj = json_pack ("{s:o, s:o," /* H_wire, h_contract_terms */ + " s:o," /* coin_pub */ + " s:o, s:o}", /* merchant_pub, merchant_sig */ + "H_wire", GNUNET_JSON_from_data_auto (h_wire), + "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), + "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), + "merchant_pub", GNUNET_JSON_from_data_auto (&dtp.merchant), + "merchant_sig", GNUNET_JSON_from_data_auto (&merchant_sig)); + if (NULL == deposit_wtid_obj) + { + GNUNET_break (0); + return NULL; + } + + dwh = GNUNET_new (struct TALER_EXCHANGE_TrackTransactionHandle); + dwh->exchange = exchange; + dwh->cb = cb; + dwh->cb_cls = cb_cls; + dwh->url = TEAH_path_to_url (exchange, "/track/transaction"); + dwh->depconf.purpose.size = htonl (sizeof (struct TALER_ConfirmWirePS)); + dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE); + dwh->depconf.h_wire = *h_wire; + dwh->depconf.h_contract_terms = *h_contract_terms; + dwh->depconf.coin_pub = *coin_pub; + + eh = TEL_curl_easy_get (dwh->url); + GNUNET_assert (NULL != (dwh->json_enc = + json_dumps (deposit_wtid_obj, + JSON_COMPACT))); + json_decref (deposit_wtid_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + dwh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (dwh->json_enc))); + ctx = TEAH_handle_to_context (exchange); + dwh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_deposit_wtid_finished, + dwh); + return dwh; +} + + +/** + * Cancel deposit wtid request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param dwh the wire deposits request handle + */ +void +TALER_EXCHANGE_track_transaction_cancel (struct TALER_EXCHANGE_TrackTransactionHandle *dwh) +{ + if (NULL != dwh->job) + { + GNUNET_CURL_job_cancel (dwh->job); + dwh->job = NULL; + } + GNUNET_free (dwh->url); + GNUNET_free (dwh->json_enc); + GNUNET_free (dwh); +} + + +/* end of exchange_api_deposit_wtid.c */ diff --git a/src/lib/exchange_api_track_transfer.c b/src/lib/exchange_api_track_transfer.c new file mode 100644 index 000000000..88043b7ba --- /dev/null +++ b/src/lib/exchange_api_track_transfer.c @@ -0,0 +1,388 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_track_transfer.c + * @brief Implementation of the /track/transfer request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /track/transfer Handle + */ +struct TALER_EXCHANGE_TrackTransferHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_TrackTransferCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * We got a #MHD_HTTP_OK response for the /track/transfer request. + * Check that the response is well-formed and if it is, call the + * callback. If not, return an error code. + * + * This code is very similar to + * merchant_api_track_transfer.c::check_track_transfer_response_ok. + * Any changes should likely be reflected there as well. + * + * @param wdh handle to the operation + * @param json response we got + * @return #GNUNET_OK if we are done and all is well, + * #GNUNET_SYSERR if the response was bogus + */ +static int +check_track_transfer_response_ok (struct TALER_EXCHANGE_TrackTransferHandle *wdh, + const json_t *json) +{ + json_t *details_j; + struct GNUNET_HashCode h_wire; + struct GNUNET_TIME_Absolute exec_time; + struct TALER_Amount total_amount; + struct TALER_Amount total_expected; + struct TALER_Amount wire_fee; + struct TALER_MerchantPublicKeyP merchant_pub; + unsigned int num_details; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("total", &total_amount), + TALER_JSON_spec_amount ("wire_fee", &wire_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), + GNUNET_JSON_spec_fixed_auto ("H_wire", &h_wire), + GNUNET_JSON_spec_absolute_time ("execution_time", &exec_time), + GNUNET_JSON_spec_json ("deposits", &details_j), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_get_zero (total_amount.currency, + &total_expected)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + num_details = json_array_size (details_j); + { + struct TALER_TrackTransferDetails details[num_details]; + unsigned int i; + struct GNUNET_HashContext *hash_context; + struct TALER_WireDepositDetailP dd; + struct TALER_WireDepositDataPS wdp; + + hash_context = GNUNET_CRYPTO_hash_context_start (); + for (i=0;i<num_details;i++) + { + struct TALER_TrackTransferDetails *detail = &details[i]; + struct json_t *detail_j = json_array_get (details_j, i); + struct GNUNET_JSON_Specification spec_detail[] = { + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &detail->h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub), + TALER_JSON_spec_amount ("deposit_value", &detail->coin_value), + TALER_JSON_spec_amount ("deposit_fee", &detail->coin_fee), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (detail_j, + spec_detail, + NULL, NULL)) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_hash_context_abort (hash_context); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + /* build up big hash for signature checking later */ + dd.h_contract_terms = detail->h_contract_terms; + dd.execution_time = GNUNET_TIME_absolute_hton (exec_time); + dd.coin_pub = detail->coin_pub; + TALER_amount_hton (&dd.deposit_value, + &detail->coin_value); + TALER_amount_hton (&dd.deposit_fee, + &detail->coin_fee); + if ( (GNUNET_OK != + TALER_amount_add (&total_expected, + &total_expected, + &detail->coin_value)) || + (GNUNET_OK != + TALER_amount_subtract (&total_expected, + &total_expected, + &detail->coin_fee)) ) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_hash_context_abort (hash_context); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash_context_read (hash_context, + &dd, + sizeof (struct TALER_WireDepositDetailP)); + } + /* Check signature */ + wdp.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT); + wdp.purpose.size = htonl (sizeof (struct TALER_WireDepositDataPS)); + TALER_amount_hton (&wdp.total, + &total_amount); + TALER_amount_hton (&wdp.wire_fee, + &wire_fee); + wdp.merchant_pub = merchant_pub; + wdp.h_wire = h_wire; + GNUNET_CRYPTO_hash_context_finish (hash_context, + &wdp.h_details); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (TALER_EXCHANGE_get_keys (wdh->exchange), + &exchange_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify + (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT, + &wdp.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + TALER_amount_subtract (&total_expected, + &total_expected, + &wire_fee)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + if (0 != + TALER_amount_cmp (&total_expected, + &total_amount)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + wdh->cb (wdh->cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + &exchange_pub, + json, + &h_wire, + exec_time, + &total_amount, + &wire_fee, + num_details, + details); + } + GNUNET_JSON_parse_free (spec); + TALER_EXCHANGE_track_transfer_cancel (wdh); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /track/transfer request. + * + * @param cls the `struct TALER_EXCHANGE_TrackTransferHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_track_transfer_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_TrackTransferHandle *wdh = cls; + const json_t *j = response; + + wdh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK == + check_track_transfer_response_ok (wdh, + j)) + return; + GNUNET_break_op (0); + response_code = 0; + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Exchange does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + wdh->cb (wdh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + NULL, + j, + NULL, + GNUNET_TIME_UNIT_ZERO_ABS, + NULL, + NULL, + 0, NULL); + TALER_EXCHANGE_track_transfer_cancel (wdh); +} + + +/** + * Query the exchange about which transactions were combined + * to create a wire transfer. + * + * @param exchange exchange to query + * @param wtid raw wire transfer identifier to get information about + * @param cb callback to call + * @param cb_cls closure for @a cb + * @return handle to cancel operation + */ +struct TALER_EXCHANGE_TrackTransferHandle * +TALER_EXCHANGE_track_transfer (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_EXCHANGE_TrackTransferCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_TrackTransferHandle *wdh; + struct GNUNET_CURL_Context *ctx; + char *buf; + char *path; + CURL *eh; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + + wdh = GNUNET_new (struct TALER_EXCHANGE_TrackTransferHandle); + wdh->exchange = exchange; + wdh->cb = cb; + wdh->cb_cls = cb_cls; + + buf = GNUNET_STRINGS_data_to_string_alloc (wtid, + sizeof (struct TALER_WireTransferIdentifierRawP)); + GNUNET_asprintf (&path, + "/track/transfer?wtid=%s", + buf); + wdh->url = TEAH_path_to_url (wdh->exchange, + path); + GNUNET_free (buf); + GNUNET_free (path); + + eh = TEL_curl_easy_get (wdh->url); + ctx = TEAH_handle_to_context (exchange); + wdh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_track_transfer_finished, + wdh); + return wdh; +} + + +/** + * Cancel wire deposits request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param wdh the wire deposits request handle + */ +void +TALER_EXCHANGE_track_transfer_cancel (struct TALER_EXCHANGE_TrackTransferHandle *wdh) +{ + if (NULL != wdh->job) + { + GNUNET_CURL_job_cancel (wdh->job); + wdh->job = NULL; + } + GNUNET_free (wdh->url); + GNUNET_free (wdh); +} + + +/* end of exchange_api_wire_deposits.c */ diff --git a/src/lib/exchange_api_wire.c b/src/lib/exchange_api_wire.c new file mode 100644 index 000000000..85d1835cf --- /dev/null +++ b/src/lib/exchange_api_wire.c @@ -0,0 +1,442 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_wire.c + * @brief Implementation of the /wire request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "taler_wire_lib.h" +#include "taler_signatures.h" +#include "taler_wire_plugin.h" +#include "exchange_api_handle.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Wire Handle + */ +struct TALER_EXCHANGE_WireHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_WireResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * List of wire fees by method. + */ +struct FeeMap +{ + /** + * Next entry in list. + */ + struct FeeMap *next; + + /** + * Wire method this fee structure is for. + */ + char *method; + + /** + * Array of wire fees, also linked list, but allocated + * only once. + */ + struct TALER_EXCHANGE_WireAggregateFees *fee_list; +}; + + +/** + * Frees @a fm. + * + * @param fm memory to release + */ +static void +free_fees (struct FeeMap *fm) +{ + while (NULL != fm) + { + struct FeeMap *fe = fm->next; + + GNUNET_free (fm->fee_list); + GNUNET_free (fm->method); + GNUNET_free (fm); + fm = fe; + } +} + + +/** + * Parse wire @a fees and return map. + * + * @param fees json AggregateTransferFee to parse + * @return NULL on error + */ +static struct FeeMap * +parse_fees (json_t *fees) +{ + struct FeeMap *fm = NULL; + const char *key; + json_t *fee_array; + + json_object_foreach (fees, key, fee_array) { + struct FeeMap *fe = GNUNET_new (struct FeeMap); + int len; + unsigned int idx; + json_t *fee; + + if (0 == (len = json_array_size (fee_array))) + { + GNUNET_break_op (0); + GNUNET_free (fe); + continue; /* skip */ + } + fe->method = GNUNET_strdup (key); + fe->next = fm; + fe->fee_list = GNUNET_new_array (len, + struct TALER_EXCHANGE_WireAggregateFees); + fm = fe; + json_array_foreach (fee_array, idx, fee) + { + struct TALER_EXCHANGE_WireAggregateFees *wa = &fe->fee_list[idx]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("sig", + &wa->master_sig), + TALER_JSON_spec_amount ("wire_fee", + &wa->wire_fee), + TALER_JSON_spec_amount ("closing_fee", + &wa->closing_fee), + GNUNET_JSON_spec_absolute_time ("start_date", + &wa->start_date), + GNUNET_JSON_spec_absolute_time ("end_date", + &wa->end_date), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (fee, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + free_fees (fm); + return NULL; + } + if (idx + 1 < len) + wa->next = &fe->fee_list[idx + 1]; + else + wa->next = NULL; + } + } + return fm; +} + + +/** + * Find fee by @a method. + * + * @param fm map to look in + * @param method key to look for + * @return NULL if fee is not specified in @a fm + */ +static const struct TALER_EXCHANGE_WireAggregateFees * +lookup_fee (const struct FeeMap *fm, + const char *method) +{ + for (;NULL != fm; fm = fm->next) + if (0 == strcasecmp (fm->method, + method)) + return fm->fee_list; + return NULL; +} + + +/** + * Function called when we're done processing the + * HTTP /wire request. + * + * @param cls the `struct TALER_EXCHANGE_WireHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_wire_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_WireHandle *wh = cls; + enum TALER_ErrorCode ec; + const json_t *j = response; + + TALER_LOG_DEBUG ("Checking raw /wire response\n"); + wh->job = NULL; + ec = TALER_EC_NONE; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + json_t *accounts; + json_t *fees; + int num_accounts; + struct FeeMap *fm; + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("accounts", &accounts), + GNUNET_JSON_spec_json ("fees", &fees), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + /* bogus reply */ + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + break; + } + if (0 == (num_accounts = json_array_size (accounts))) + { + /* bogus reply */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + break; + } + if (NULL == (fm = parse_fees (fees))) + { + /* bogus reply */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + break; + } + + key_state = TALER_EXCHANGE_get_keys (wh->exchange); + /* parse accounts */ + { + struct TALER_EXCHANGE_WireAccount was[num_accounts]; + + for (unsigned int i=0;i<num_accounts;i++) + { + struct TALER_EXCHANGE_WireAccount *wa = &was[i]; + json_t *account; + struct GNUNET_JSON_Specification spec_account[] = { + GNUNET_JSON_spec_string ("url", &wa->url), + GNUNET_JSON_spec_fixed_auto ("master_sig", &wa->master_sig), + GNUNET_JSON_spec_end() + }; + char *method; + + account = json_array_get (accounts, + i); + if (GNUNET_OK != + TALER_JSON_exchange_wire_signature_check (account, + &key_state->master_pub)) + { + /* bogus reply */ + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_SERVER_SIGNATURE_INVALID; + break; + } + if (GNUNET_OK != + GNUNET_JSON_parse (account, + spec_account, + NULL, NULL)) + { + /* bogus reply */ + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + break; + } + if (NULL == (method = TALER_WIRE_payto_get_method (wa->url))) + { + /* bogus reply */ + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + break; + } + if (NULL == (wa->fees = lookup_fee (fm, + method))) + { + /* bogus reply */ + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + GNUNET_free (method); + break; + } + GNUNET_free (method); + } /* end 'for all accounts */ + if ( (0 != response_code) && + (NULL != wh->cb) ) + { + wh->cb (wh->cb_cls, + response_code, + TALER_EC_NONE, + num_accounts, + was); + wh->cb = NULL; + } + } /* end of 'parse accounts */ + free_fees (fm); + GNUNET_JSON_parse_free (spec); + } /* end of MHD_HTTP_OK */ + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != wh->cb) + wh->cb (wh->cb_cls, + response_code, + (0 == response_code) ? ec : TALER_JSON_get_error_code (j), + 0, + NULL); + TALER_EXCHANGE_wire_cancel (wh); +} + + +/** + * Obtain information about a exchange's wire instructions. + * A exchange may provide wire instructions for creating + * a reserve. The wire instructions also indicate + * which wire formats merchants may use with the exchange. + * This API is typically used by a wallet for wiring + * funds, and possibly by a merchant to determine + * supported wire formats. + * + * Note that while we return the (main) response verbatim to the + * caller for further processing, we do already verify that the + * response is well-formed (i.e. that signatures included in the + * response are all valid). If the exchange's reply is not well-formed, + * we return an HTTP status code of zero to @a cb. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param wire_cb the callback to call when a reply for this request is available + * @param wire_cb_cls closure for the above callback + * @return a handle for this request + */ +struct TALER_EXCHANGE_WireHandle * +TALER_EXCHANGE_wire (struct TALER_EXCHANGE_Handle *exchange, + TALER_EXCHANGE_WireResultCallback wire_cb, + void *wire_cb_cls) +{ + struct TALER_EXCHANGE_WireHandle *wh; + struct GNUNET_CURL_Context *ctx; + CURL *eh; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + wh = GNUNET_new (struct TALER_EXCHANGE_WireHandle); + wh->exchange = exchange; + wh->cb = wire_cb; + wh->cb_cls = wire_cb_cls; + wh->url = TEAH_path_to_url (exchange, "/wire"); + + eh = TEL_curl_easy_get (wh->url); + ctx = TEAH_handle_to_context (exchange); + wh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_wire_finished, + wh); + return wh; +} + + +/** + * Cancel a wire information request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param wh the wire information request handle + */ +void +TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh) +{ + if (NULL != wh->job) + { + GNUNET_CURL_job_cancel (wh->job); + wh->job = NULL; + } + GNUNET_free (wh->url); + GNUNET_free (wh); +} + + +/* end of exchange_api_wire.c */ diff --git a/src/lib/test_auditor_api.c b/src/lib/test_auditor_api.c new file mode 100644 index 000000000..cddd21197 --- /dev/null +++ b/src/lib/test_auditor_api.c @@ -0,0 +1,549 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file auditor-lib/test_auditor_api.c + * @brief testcase to test auditor's HTTP API interface + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_auditor_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" +#include "taler_testing_auditor_lib.h" + +/** + * Configuration file we use. One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE "test_auditor_api.conf" + +#define CONFIG_FILE_EXPIRE_RESERVE_NOW "test_auditor_api_expire_reserve_now.conf" + +/** + * URL of the fakebank. Obtained from CONFIG_FILE's + * "exchange-wire-test:BANK_URI" option. + */ +static char *fakebank_url; + +/** + * Auditor base URL as it appears in the configuration. Note + * that it might differ from the one where the exchange actually + * listens from. + */ +static char *auditor_url; + +/** + * Exchange base URL as it appears in the configuration. Note + * that it might differ from the one where the exchange actually + * listens from. + */ +static char *exchange_url; + +/** + * Account number of the exchange at the bank. + */ +#define EXCHANGE_ACCOUNT_NO 2 + +/** + * Account number of some user. + */ +#define USER_ACCOUNT_NO 42 + +/** + * User name. Never checked by fakebank. + */ +#define USER_LOGIN_NAME "user42" + +/** + * User password. Never checked by fakebank. + */ +#define USER_LOGIN_PASS "pass42" + +/** + * Execute the taler-exchange-wirewatch command with + * our configuration file. + * + * @param label label to use for the command. + */ +#define CMD_EXEC_WIREWATCH(label) \ + TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE) + +/** + * Execute the taler-exchange-aggregator command with + * our configuration file. + * + * @param label label to use for the command. + */ +#define CMD_EXEC_AGGREGATOR(label) \ + TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE) + +/** + * Run wire transfer of funds from some user's account to the + * exchange. + * + * @param label label to use for the command. + * @param amount amount to transfer, i.e. "EUR:1" + */ +#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \ + TALER_TESTING_cmd_fakebank_transfer (label, amount, \ + fakebank_url, USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO, \ + USER_LOGIN_NAME, USER_LOGIN_PASS, exchange_url) + +/** + * Run wire transfer of funds from some user's account to the + * exchange. + * + * @param label label to use for the command. + * @param amount amount to transfer, i.e. "EUR:1" + */ +#define CMD_TRANSFER_TO_EXCHANGE_SUBJECT(label,amount,subject) \ + TALER_TESTING_cmd_fakebank_transfer_with_subject \ + (label, amount, fakebank_url, USER_ACCOUNT_NO, \ + EXCHANGE_ACCOUNT_NO, USER_LOGIN_NAME, USER_LOGIN_PASS, \ + subject, exchange_url) + +/** + * Run the taler-auditor. + * + * @param label label to use for the command. + */ +#define CMD_RUN_AUDITOR(label) \ + TALER_TESTING_cmd_exec_auditor (label, CONFIG_FILE) + +/** + * Run the taler-wire-auditor. + * + * @param label label to use for the command. + */ +#define CMD_RUN_WIRE_AUDITOR(label) \ + TALER_TESTING_cmd_exec_wire_auditor (label, CONFIG_FILE) + + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, + struct TALER_TESTING_Interpreter *is) +{ + /** + * Test withdraw. + */ + struct TALER_TESTING_Command withdraw[] = { + /** + * Move money to the exchange's bank account. + */ + CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1", + "EUR:5.01"), + /** + * Make a reserve exist, according to the previous transfer. + */ + CMD_EXEC_WIREWATCH ("wirewatch-1"), + /** + * Withdraw EUR:5. + */ + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", + "create-reserve-1", + "EUR:5", + MHD_HTTP_OK), + TALER_TESTING_cmd_end () + }; + + struct TALER_TESTING_Command spend[] = { + /** + * Spend the coin. + */ + TALER_TESTING_cmd_deposit ("deposit-simple", + "withdraw-coin-1", + 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", + GNUNET_TIME_UNIT_ZERO, + "EUR:5", + MHD_HTTP_OK), + TALER_TESTING_cmd_end () + }; + + struct TALER_TESTING_Command refresh[] = { + /** + * Fill reserve with EUR:5, 1ct is for fees. NOTE: the old + * test-suite gave a account number of _424_ to the user at + * this step; to type less, here the _42_ number is reused. + * Does this change the tests semantics? + */ + CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-1", + "EUR:5.01"), + /** + * Make previous command effective. + */ + CMD_EXEC_WIREWATCH ("wirewatch-2"), + /** + * Withdraw EUR:5. + */ + TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1", + "refresh-create-reserve-1", + "EUR:5", + MHD_HTTP_OK), + /** + * Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in + * full) Merchant receives EUR:0.99 due to 1 ct deposit fee. + */ + TALER_TESTING_cmd_deposit ("refresh-deposit-partial", + "refresh-withdraw-coin-1", + 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"ice\",\"value\":\"EUR:1\"}]}", + GNUNET_TIME_UNIT_ZERO, + "EUR:1", + MHD_HTTP_OK), + /** + * Melt the rest of the coin's value (EUR:4.00 = 3x EUR:1.03 + 7x + * EUR:0.13) */ + TALER_TESTING_cmd_refresh_melt_double ("refresh-melt-1", + "EUR:4", + "refresh-withdraw-coin-1", + MHD_HTTP_OK), + /** + * Complete (successful) melt operation, and withdraw the coins + */ + TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-1", + "refresh-melt-1", + MHD_HTTP_OK), + /** + * Try to spend a refreshed EUR:0.1 coin + */ + TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1b", + "refresh-reveal-1", + 3, + TALER_TESTING_make_wire_details (43, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\"value\":3}]}", + GNUNET_TIME_UNIT_ZERO, + "EUR:0.1", + MHD_HTTP_OK), + TALER_TESTING_cmd_end () + }; + + struct TALER_TESTING_Command track[] = { + /** + * Run transfers. Note that _actual_ aggregation will NOT + * happen here, as each deposit operation is run with a + * fresh merchant public key! NOTE: this comment comes + * "verbatim" from the old test-suite, and IMO does not explain + * a lot! + */ + CMD_EXEC_AGGREGATOR ("run-aggregator"), + + /** + * Check all the transfers took place. + */ + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-499c", exchange_url, + "EUR:4.98", 2, 42), + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-99c1", exchange_url, + "EUR:0.98", 2, 42), + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-99c", exchange_url, + "EUR:0.08", 2, 43), + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-aai-1", exchange_url, + "EUR:5.01", 42, 2), + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-aai-2", exchange_url, + "EUR:5.01", 42, 2), + + TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"), + TALER_TESTING_cmd_end () + }; + + /** + * This block checks whether a wire deadline + * very far in the future does NOT get aggregated now. + */ + struct TALER_TESTING_Command unaggregation[] = { + TALER_TESTING_cmd_check_bank_empty ("far-future-aggregation-a"), + CMD_TRANSFER_TO_EXCHANGE ("create-reserve-unaggregated", + "EUR:5.01"), + CMD_EXEC_WIREWATCH ("wirewatch-unaggregated"), + /* "consume" reserve creation transfer. */ + TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-unaggregated", + exchange_url, + "EUR:5.01", + 42, + 2), + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated", + "create-reserve-unaggregated", + "EUR:5", + MHD_HTTP_OK), + TALER_TESTING_cmd_deposit ("deposit-unaggregated", + "withdraw-coin-unaggregated", + 0, + TALER_TESTING_make_wire_details + (43, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", + GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_YEARS, + 3000), + "EUR:5", + MHD_HTTP_OK), + CMD_EXEC_AGGREGATOR ("aggregation-attempt"), + TALER_TESTING_cmd_check_bank_empty ("far-future-aggregation-b"), + TALER_TESTING_cmd_end () + }; + + struct TALER_TESTING_Command refund[] = { + /** + * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config. + */ + CMD_TRANSFER_TO_EXCHANGE ("create-reserve-r1", + "EUR:5.01"), + /** + * Run wire-watch to trigger the reserve creation. + */ + CMD_EXEC_WIREWATCH ("wirewatch-3"), + /** + * Withdraw a 5 EUR coin, at fee of 1 ct + */ + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1", + "create-reserve-r1", + "EUR:5", + MHD_HTTP_OK), + /** + * Spend 5 EUR of the 5 EUR coin (in full). Merchant would + * receive EUR:4.99 due to 1 ct deposit fee. + */ + TALER_TESTING_cmd_deposit ("deposit-refund-1", + "withdraw-coin-r1", + 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"ice\",\"value\":\"EUR:5\"}]}", + GNUNET_TIME_UNIT_MINUTES, + "EUR:5", + MHD_HTTP_OK), + TALER_TESTING_cmd_refund ("refund-ok", + MHD_HTTP_OK, + "EUR:5", + "EUR:0.01", + "deposit-refund-1"), + /** + * Spend 4.99 EUR of the refunded 4.99 EUR coin (1ct gone + * due to refund) (merchant would receive EUR:4.98 due to + * 1 ct deposit fee) */ + TALER_TESTING_cmd_deposit ("deposit-refund-2", + "withdraw-coin-r1", + 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"more\",\"value\":\"EUR:5\"}]}", + GNUNET_TIME_UNIT_ZERO, + "EUR:4.99", + MHD_HTTP_OK), + /** + * Run transfers. This will do the transfer as refund deadline was + * 0. + */ + CMD_EXEC_AGGREGATOR ("run-aggregator-3"), + TALER_TESTING_cmd_end () + }; + + struct TALER_TESTING_Command payback[] = { + /** + * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per + * config. + */ + CMD_TRANSFER_TO_EXCHANGE ("payback-create-reserve-1", + "EUR:5.01"), + /** + * Run wire-watch to trigger the reserve creation. + */ + CMD_EXEC_WIREWATCH ("wirewatch-4"), + /** + * Withdraw a 5 EUR coin, at fee of 1 ct + */ + TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-1", + "payback-create-reserve-1", + "EUR:5", + MHD_HTTP_OK), + TALER_TESTING_cmd_revoke ("revoke-1", + MHD_HTTP_OK, + "payback-withdraw-coin-1", + CONFIG_FILE), + TALER_TESTING_cmd_payback ("payback-1", + MHD_HTTP_OK, + "payback-withdraw-coin-1", + "EUR:5"), + /** + * Re-withdraw from this reserve + */ + TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2", + "payback-create-reserve-1", + "EUR:1", + MHD_HTTP_OK), + /** + * These commands should close the reserve because the aggregator + * is given a config file that ovverrides the reserve expiration + * time (making it now-ish) + */ + CMD_TRANSFER_TO_EXCHANGE ("short-lived-reserve", + "EUR:5.01"), + TALER_TESTING_cmd_exec_wirewatch ("short-lived-aggregation", + CONFIG_FILE_EXPIRE_RESERVE_NOW), + TALER_TESTING_cmd_exec_aggregator ("close-reserves", + CONFIG_FILE_EXPIRE_RESERVE_NOW), + /** + * Fill reserve with EUR:2.02, as withdraw fee is 1 ct per + * config, then withdraw two coin, partially spend one, and + * then have the rest paid back. Check deposit of other coin + * fails. (Do not use EUR:5 here as the EUR:5 coin was + * revoked and we did not bother to create a new one...) + */ + CMD_TRANSFER_TO_EXCHANGE ("payback-create-reserve-2", + "EUR:2.02"), + /** + * Make previous command effective. + */ + CMD_EXEC_WIREWATCH ("wirewatch-5"), + /** + * Withdraw a 1 EUR coin, at fee of 1 ct + */ + TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2a", + "payback-create-reserve-2", + "EUR:1", + MHD_HTTP_OK), + /** + * Withdraw a 1 EUR coin, at fee of 1 ct + */ + TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2b", + "payback-create-reserve-2", + "EUR:1", + MHD_HTTP_OK), + TALER_TESTING_cmd_deposit ("payback-deposit-partial", + "payback-withdraw-coin-2a", + 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}", + GNUNET_TIME_UNIT_ZERO, + "EUR:0.5", + MHD_HTTP_OK), + TALER_TESTING_cmd_revoke ("revoke-2", + MHD_HTTP_OK, + "payback-withdraw-coin-2a", + CONFIG_FILE), + TALER_TESTING_cmd_payback ("payback-2", + MHD_HTTP_OK, + "payback-withdraw-coin-2a", + "EUR:0.5"), + TALER_TESTING_cmd_end () + }; + + struct TALER_TESTING_Command commands[] = { + CMD_RUN_AUDITOR("virgin-auditor"), + CMD_RUN_WIRE_AUDITOR("virgin-wire-auditor"), + TALER_TESTING_cmd_batch ("withdraw", + withdraw), + TALER_TESTING_cmd_batch ("spend", + spend), + TALER_TESTING_cmd_batch ("refresh", + refresh), + TALER_TESTING_cmd_batch ("track", + track), + TALER_TESTING_cmd_batch ("unaggregation", + unaggregation), + TALER_TESTING_cmd_batch ("refund", + refund), + TALER_TESTING_cmd_batch ("payback", + payback), + CMD_RUN_AUDITOR("normal-auditor"), + CMD_RUN_WIRE_AUDITOR("normal-wire-auditor"), + TALER_TESTING_cmd_end () + }; + + TALER_TESTING_run_with_fakebank (is, + commands, + fakebank_url); +} + + +int +main (int argc, + char * const *argv) +{ + + /* These environment variables get in the way... */ + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test-auditor-api", + "INFO", + NULL); + if (NULL == (fakebank_url + /* Check fakebank port is available and config cares + * about bank url. */ + = TALER_TESTING_prepare_fakebank (CONFIG_FILE, + "account-2"))) + return 77; + TALER_TESTING_cleanup_files (CONFIG_FILE); + /* @helpers. Run keyup, create tables, ... Note: it + * fetches the port number from config in order to see + * if it's available. */ + switch (TALER_TESTING_prepare_exchange (CONFIG_FILE, + &auditor_url, + &exchange_url)) + { + case GNUNET_SYSERR: + GNUNET_break (0); + return 1; + case GNUNET_NO: + return 77; + case GNUNET_OK: + if (GNUNET_OK != + /* Set up event loop and reschedule context, plus + * start/stop the exchange. It calls TALER_TESTING_setup + * which creates the 'is' object. + */ + TALER_TESTING_AUDITOR_setup (&run, + NULL, + CONFIG_FILE)) + return 1; + break; + default: + GNUNET_break (0); + return 1; + } + return 0; +} + +/* end of test_auditor_api.c */ diff --git a/src/lib/test_auditor_api.conf b/src/lib/test_auditor_api.conf new file mode 100644 index 000000000..532f9c932 --- /dev/null +++ b/src/lib/test_auditor_api.conf @@ -0,0 +1,202 @@ + +# This file is in the public domain. +# +[PATHS] +# Persistant data storage for the testcase +TALER_TEST_HOME = test_exchange_api_home/ + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + +[auditor] +BASE_URL = "http://localhost:8083/" + +# HTTP port the auditor listens to +PORT = 8083 + +[exchange] + +# how long is one signkey valid? +signkey_duration = 4 weeks + +# how long are the signatures with the signkey valid? +legal_duration = 2 years + +# how long do we provide to clients denomination and signing keys +# ahead of time? +lookahead_provide = 4 weeks 1 day + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# How to access our database +DB = postgres + +# Base URL of the exchange. Must be set to a URL where the +# exchange (or the twister) is actually listening. +BASE_URL = "http://localhost:8081/" + +# Keep it short so the test runs fast. +LOOKAHEAD_SIGN = 12 h + +[exchangedb-postgres] +CONFIG = "postgres:///talercheck" + +[auditordb-postgres] +CONFIG = "postgres:///talercheck" + +# Sections starting with "account-" configure the bank accounts +# of the exchange. The "URL" specifies the account in +# payto://-format, while the WIRE_JSON specifies the +# (possibly offline) signed version to be returned in /wire. +# WIRE_JSON is optional, as not all accounts must be +# advertised in /wire. +[account-1] +# What is the URL of our account? +URL = "payto://sepa/CH9300762011623852957" +# This is the response we give out for the /wire request. It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json +# Which wire plugin should we used to access the account? +PLUGIN = ebics + +# ENABLE_CREDIT = YES + +[account-2] +# What is the bank account (with the "Taler Bank" demo system)? +URL = "payto://x-taler-bank/localhost:8082/2" + +# This is the response we give out for the /wire request. It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json + +# Which wire plugin should we used to access the account? +PLUGIN = taler_bank + +# Authentication information for basic authentication +TALER_BANK_AUTH_METHOD = "basic" +USERNAME = user +PASSWORD = pass + +ENABLE_DEBIT = YES + +ENABLE_CREDIT = YES + + +# Sections starting with "fee-" configure the wire fee for the +# respective wire method. +[fees-sepa] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + +[fees-x-taler-bank] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + +# Sections starting with "coin_" specify which denominations +# the exchange should support (and their respective fee structure) +[coin_eur_ct_1] +value = EUR:0.01 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.00 +fee_deposit = EUR:0.00 +fee_refresh = EUR:0.01 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_ct_10] +value = EUR:0.10 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_1] +value = EUR:1 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_5] +value = EUR:5 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_10] +value = EUR:10 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 diff --git a/src/lib/test_auditor_api_expire_reserve_now.conf b/src/lib/test_auditor_api_expire_reserve_now.conf new file mode 100644 index 000000000..05bca956b --- /dev/null +++ b/src/lib/test_auditor_api_expire_reserve_now.conf @@ -0,0 +1,4 @@ +@INLINE@ test_exchange_api.conf + +[exchangedb] +IDLE_RESERVE_EXPIRATION_TIME = 0 s diff --git a/src/lib/test_exchange_api.conf b/src/lib/test_exchange_api.conf new file mode 100644 index 000000000..a44bab3ae --- /dev/null +++ b/src/lib/test_exchange_api.conf @@ -0,0 +1,203 @@ + +# This file is in the public domain. +# +[PATHS] +# Persistant data storage for the testcase +TALER_TEST_HOME = test_exchange_api_home/ + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + +[auditor] +BASE_URL = "http://localhost:8083/" + +# HTTP port the auditor listens to +PORT = 8083 + + +[exchange] + +# how long is one signkey valid? +signkey_duration = 4 weeks + +# how long are the signatures with the signkey valid? +legal_duration = 2 years + +# how long do we provide to clients denomination and signing keys +# ahead of time? +lookahead_provide = 4 weeks 1 day + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# How to access our database +DB = postgres + +# Base URL of the exchange. Must be set to a URL where the +# exchange (or the twister) is actually listening. +BASE_URL = "http://localhost:8081/" + +# Keep it short so the test runs fast. +LOOKAHEAD_SIGN = 12 h + +[exchangedb-postgres] +CONFIG = "postgres:///talercheck" + +[auditordb-postgres] +CONFIG = "postgres:///talercheck" + +# Sections starting with "account-" configure the bank accounts +# of the exchange. The "URL" specifies the account in +# payto://-format, while the WIRE_JSON specifies the +# (possibly offline) signed version to be returned in /wire. +# WIRE_JSON is optional, as not all accounts must be +# advertised in /wire. +[account-1] +# What is the URL of our account? +URL = "payto://sepa/CH9300762011623852957" +# This is the response we give out for the /wire request. It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json +# Which wire plugin should we used to access the account? +PLUGIN = ebics + +# ENABLE_CREDIT = YES + +[account-2] +# What is the bank account (with the "Taler Bank" demo system)? +URL = "payto://x-taler-bank/localhost:8082/2" + +# This is the response we give out for the /wire request. It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json + +# Which wire plugin should we used to access the account? +PLUGIN = taler_bank + +# Authentication information for basic authentication +TALER_BANK_AUTH_METHOD = "basic" +USERNAME = user +PASSWORD = pass + +ENABLE_DEBIT = YES + +ENABLE_CREDIT = YES + + +# Sections starting with "fee-" configure the wire fee for the +# respective wire method. +[fees-sepa] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + +[fees-x-taler-bank] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + +# Sections starting with "coin_" specify which denominations +# the exchange should support (and their respective fee structure) +[coin_eur_ct_1] +value = EUR:0.01 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.00 +fee_deposit = EUR:0.00 +fee_refresh = EUR:0.01 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_ct_10] +value = EUR:0.10 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_1] +value = EUR:1 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_5] +value = EUR:5 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_10] +value = EUR:10 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 diff --git a/src/lib/test_exchange_api_expire_reserve_now.conf b/src/lib/test_exchange_api_expire_reserve_now.conf new file mode 100644 index 000000000..05bca956b --- /dev/null +++ b/src/lib/test_exchange_api_expire_reserve_now.conf @@ -0,0 +1,4 @@ +@INLINE@ test_exchange_api.conf + +[exchangedb] +IDLE_RESERVE_EXPIRATION_TIME = 0 s diff --git a/src/lib/test_exchange_api_home/.config/taler/account-1.json b/src/lib/test_exchange_api_home/.config/taler/account-1.json new file mode 100644 index 000000000..48093f2aa --- /dev/null +++ b/src/lib/test_exchange_api_home/.config/taler/account-1.json @@ -0,0 +1,5 @@ +{ + "url": "payto://sepa/CH9300762011623852957", + "salt": "N83T9J9202WCC8TQFDMJDWEGZNBEKA33C1ZM241VNYH88RZNTHPW509Y1M2YF7Y098R8VRESWQ05H03BK1SPAZCWE54KARDCKT5N8AG", + "master_sig": "D4V5GJ998YK7D6N0N56AD0J6MZNFEW6MRZT2CFPVQ5ME3NMQ59AA2007CXYESSFGRN70CNCFM06858QSSENCWTZM8VHEJ93YQ20ZJ1R" +}
\ No newline at end of file diff --git a/src/lib/test_exchange_api_home/.config/taler/account-2.json b/src/lib/test_exchange_api_home/.config/taler/account-2.json new file mode 100644 index 000000000..159e03171 --- /dev/null +++ b/src/lib/test_exchange_api_home/.config/taler/account-2.json @@ -0,0 +1,4 @@ +{ + "url": "payto://x-taler-bank/localhost:8082/2", + "master_sig": "HC47BZN3C0KJ2VPMJ5EJWD2FXJ72AET0NWFE6JGSGK5CXS4GSKJJ6Z7BTS56JWM7B40SD61Z5GYYMRRE3X9JTJBVMWE0X7XHNXQ9P38" +}
\ No newline at end of file diff --git a/src/lib/test_exchange_api_home/.config/taler/sepa.json b/src/lib/test_exchange_api_home/.config/taler/sepa.json new file mode 100644 index 000000000..b435ce86b --- /dev/null +++ b/src/lib/test_exchange_api_home/.config/taler/sepa.json @@ -0,0 +1,9 @@ +{ + "name": "Max Musterman", + "bic": "COBADEFF370", + "type": "sepa", + "sig": "4EVRC2MCJPXQC8MC00831DNWEXMZAP4JQDDE1A7R6KR3MANG24RC1VQ55AX5A2E35S58VW1VSTENFTPHG5MWG9BSN8B8WXSV21KKW20", + "address": "Musterstadt", + "salt": "3KTM1ZRMWGEQPQ254S4R5R4Q8XM0ZYWTCTE01TZ76MVBSQ6RX7A5DR08WXVH1DCHR1R7ACRB7X0EVC2XDW1CBZM9WFSD9TRMZ90BR98", + "iban": "DE89370400440532013000" +}
\ No newline at end of file diff --git a/src/lib/test_exchange_api_home/.config/taler/test.json b/src/lib/test_exchange_api_home/.config/taler/test.json new file mode 100644 index 000000000..eca394241 --- /dev/null +++ b/src/lib/test_exchange_api_home/.config/taler/test.json @@ -0,0 +1,8 @@ +{ + "salt": "AZPRFVJ58NM6M7J5CZQPJAH3EW5DYM52AEZ9Y1C1ER3W94QV8D8TQKF6CK8MYQRA9QMSKDQTGZ306ZS9GQ0M6R01CJ20KPP49WFDZK8", + "name": "The exchange", + "account_number": 3, + "bank_url": "http://localhost:8082/", + "type": "test", + "sig": "RPQXP9S4P8PQP7HEZQNRSZCT0ATNEP8GW0P5TPM34V5RX86FCD670V44R9NETSYDDKB8SZV7TKY9PAJYTY51D3VDWY9XXQ5BPFRXR28" +} diff --git a/src/lib/test_exchange_api_home/.config/taler/x-taler-bank.json b/src/lib/test_exchange_api_home/.config/taler/x-taler-bank.json new file mode 100644 index 000000000..a15df27ca --- /dev/null +++ b/src/lib/test_exchange_api_home/.config/taler/x-taler-bank.json @@ -0,0 +1,4 @@ +{ + "url": "payto://x-taler-bank/http://localhost:8082/2", + "master_sig": "KQ0BWSCNVR7HGGSAMCYK8ZM30RBS1MHMXT3QBN01PZWC9TV72FEE5RJ7T84C8134EPV6WEBXXY2MTFNE8ZXST6JEJQKR8HX6FQPVY10" +}
\ No newline at end of file diff --git a/src/lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv b/src/lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv new file mode 100644 index 000000000..394926938 --- /dev/null +++ b/src/lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv @@ -0,0 +1 @@ +pÚ^ó-Ú33ˆ€XXÁ!ˆ\0qúýµmUþ_‰ˆ
\ No newline at end of file diff --git a/src/lib/test_exchange_api_keys_cherry_picking.conf b/src/lib/test_exchange_api_keys_cherry_picking.conf new file mode 100644 index 000000000..8d5585e3a --- /dev/null +++ b/src/lib/test_exchange_api_keys_cherry_picking.conf @@ -0,0 +1,161 @@ +# This file is in the public domain. +# +[PATHS] +# Persistent data storage for the testcase +TALER_TEST_HOME = test_exchange_api_home/ + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + +[auditor] +BASE_URL = "http://localhost:8083/" + +# HTTP port the auditor listens to +PORT = 8083 + +[exchange] + +# how long is one signkey valid? +signkey_duration = 5 seconds + +# how long are the signatures with the signkey valid? +legal_duration = 2 years + +# how long do we provide to clients denomination and signing keys +# ahead of time? +lookahead_provide = 30 seconds + +# Keep it short so we can prolong later! +LOOKAHEAD_SIGN = 60 s + + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# How to access our database +DB = postgres + +# Base URL of the exchange. Must be set to a URL where the +# exchange (or the twister) is actually listening. +BASE_URL = "http://localhost:8081/" + + +[exchangedb-postgres] +CONFIG = "postgres:///talercheck" + +[auditordb-postgres] +CONFIG = "postgres:///talercheck" + + +[account-1] +# This is the response we give out for the /wire request. It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/iban.json + +# What is the URL of our bank account? Must match WIRE_RESPONSE above! +URL = payto://sepa/FIXME + +# Which plugin implements access for this account? +PLUGIN = "ebics" + + +[account-2] +# This is the response we give out for the /wire request. It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/x-taler-bank.json + +# What is the URL of our bank account? Must match WIRE_RESPONSE above! +URL = payto://x-taler-bank/http://localhost:8082/2 + +# Which plugin implements access for this account? +PLUGIN = "taler_bank" + +# Authentication information for basic authentication +TALER_BANK_AUTH_METHOD = "basic" +USERNAME = user +PASSWORD = pass + +ENABLE_DEBIT = YES + +ENABLE_CREDIT = YES + + + + +[fees-x-taler-bank] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2017 = EUR:0.01 +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 + +CLOSING-FEE-2017 = EUR:0.01 +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 + +[fees-sepa] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2017 = EUR:0.01 +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 + +CLOSING-FEE-2017 = EUR:0.01 +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 + +[coin_eur_ct_1] +value = EUR:0.01 +duration_overlap = 1 s +duration_withdraw = 25 s +duration_spend = 40 s +duration_legal = 60 s +fee_withdraw = EUR:0.00 +fee_deposit = EUR:0.00 +fee_refresh = EUR:0.01 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_ct_2] +value = EUR:0.02 +duration_overlap = 1 s +duration_withdraw = 25 s +duration_spend = 40 s +duration_legal = 60 s +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.01 +fee_refund = EUR:0.01 +rsa_keysize = 1024 diff --git a/src/lib/test_exchange_api_keys_cherry_picking_extended.conf b/src/lib/test_exchange_api_keys_cherry_picking_extended.conf new file mode 100644 index 000000000..29290c99c --- /dev/null +++ b/src/lib/test_exchange_api_keys_cherry_picking_extended.conf @@ -0,0 +1,5 @@ +@INLINE@ test_exchange_api_keys_cherry_picking.conf + +[exchange] +# Lengthen over original value (60 s) +LOOKAHEAD_SIGN = 100 s diff --git a/src/lib/test_exchange_api_keys_cherry_picking_extended_2.conf b/src/lib/test_exchange_api_keys_cherry_picking_extended_2.conf new file mode 100644 index 000000000..cfa8b1347 --- /dev/null +++ b/src/lib/test_exchange_api_keys_cherry_picking_extended_2.conf @@ -0,0 +1,5 @@ +@INLINE@ test_exchange_api_keys_cherry_picking_extended.conf + +[exchange] +# Lengthen over firstly extended value (100 s) +LOOKAHEAD_SIGN = 1500 s diff --git a/src/lib/test_exchange_api_keys_cherry_picking_new.c b/src/lib/test_exchange_api_keys_cherry_picking_new.c new file mode 100644 index 000000000..45222a66c --- /dev/null +++ b/src/lib/test_exchange_api_keys_cherry_picking_new.c @@ -0,0 +1,233 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/test_exchange_api_keys_cherry_picking_new.c + * @brief testcase to test exchange's /keys cherry picking ability + * @author Marcello Stanisci + * @author Christian Grothoff + */ + +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + +/** + * Configuration file we use. One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE "test_exchange_api_keys_cherry_picking.conf" + +/** + * Used to increase the number of denomination keys. + */ +#define CONFIG_FILE_EXTENDED \ + "test_exchange_api_keys_cherry_picking_extended.conf" + +/** + * Used to increase the number of denomination keys. + */ +#define CONFIG_FILE_EXTENDED_2 \ + "test_exchange_api_keys_cherry_picking_extended_2.conf" + +/** + * Exchange base URL; mainly purpose is to make the compiler happy. + */ +static char *exchange_url; + +/** + * Auditor base URL; mainly purpose is to make the compiler happy. + */ +static char *auditor_url; + + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, + struct TALER_TESTING_Interpreter *is) +{ + + struct TALER_TESTING_Command keys_serialization[] = { + + /** + * Serialize keys, and disconnect from the exchange. + */ + TALER_TESTING_cmd_serialize_keys ("serialize-keys"), + + /** + * Reconnect to the exchange using the serialized keys. + */ + TALER_TESTING_cmd_connect_with_state ("reconnect-with-state", + "serialize-keys"), + + TALER_TESTING_cmd_wire ("verify-/wire-with-serialized-keys", + "x-taler-bank", + NULL, + MHD_HTTP_OK), + + TALER_TESTING_cmd_exec_keyup ("keyup-serialization", + CONFIG_FILE_EXTENDED_2), + + TALER_TESTING_cmd_exec_auditor_sign + ("auditor-sign-serialization", + CONFIG_FILE_EXTENDED_2), + + TALER_TESTING_cmd_sleep ("sleep-serialization", + 3), + + TALER_TESTING_cmd_signal ("reload-keys-serialization", + is->exchanged, + SIGUSR1), + + TALER_TESTING_cmd_sleep ("sleep-serialization", + 3), + + /** + * XXX. + * + * Current bug: this CMD here uses the "reconnect cert_cb", + * that has its 'consumed' field already set to GNUNET_YES. + * This way, there is no way to pass control to the next + * CMD making therefore the interpreter stuck. + * + * Doable solution: adapt the global "cert_cb" to handle + * "reconnect situations", or even provide some method to + * switch the 'consumed' field back to GNUNET_NO. + */ + TALER_TESTING_cmd_check_keys ("check-freshest-keys", + 4, + 10), + + TALER_TESTING_cmd_wire ("verify-/wire-with-fresh-keys", + "x-taler-bank", + NULL, + MHD_HTTP_OK), + TALER_TESTING_cmd_end () + }; + + struct TALER_TESTING_Command commands[] = { + /* Trigger keys reloading from disk. */ + TALER_TESTING_cmd_signal ("signal-reaction-1", + is->exchanged, + SIGUSR1), + + TALER_TESTING_cmd_check_keys ("check-keys-1", + 1, + 4), + /* sleep a bit */ + TALER_TESTING_cmd_sleep ("sleep", + 10), + + /* 1st keyup happens at start-up */ + TALER_TESTING_cmd_exec_keyup ("keyup-2", + CONFIG_FILE_EXTENDED), + + TALER_TESTING_cmd_exec_auditor_sign ("sign-keys-1", + CONFIG_FILE_EXTENDED), + + /* Cause exchange to reload (new) keys */ + TALER_TESTING_cmd_signal ("trigger-keys-reload-1", + is->exchanged, + SIGUSR1), + + TALER_TESTING_cmd_check_keys ("check-keys-2", + 2, + 6), + /* sleep a bit */ + TALER_TESTING_cmd_sleep ("sleep", + 20), + + /* Do 2nd keyup */ + TALER_TESTING_cmd_exec_keyup ("keyup-3", + CONFIG_FILE_EXTENDED), + + TALER_TESTING_cmd_exec_auditor_sign ("sign-keys-2", + CONFIG_FILE), + + TALER_TESTING_cmd_signal ("trigger-keys-reload-2", + is->exchanged, + SIGUSR1), + + TALER_TESTING_cmd_check_keys ("check-keys-3", + 3, + 8), + + TALER_TESTING_cmd_batch ("keys-serialization", + keys_serialization), + TALER_TESTING_cmd_end () + }; + + TALER_TESTING_run (is, + commands); +} + + +int +main (int argc, + char * const *argv) +{ + /* These environment variables get in the way... */ + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test-exchange-api-cherry-picking-new", + "DEBUG", NULL); + TALER_TESTING_cleanup_files (CONFIG_FILE); + /* @helpers. Run keyup, create tables, ... Note: it + * fetches the port number from config in order to see + * if it's available. */ + switch (TALER_TESTING_prepare_exchange (CONFIG_FILE, + &auditor_url, + &exchange_url)) + { + case GNUNET_SYSERR: + GNUNET_break (0); + return 1; + case GNUNET_NO: + return 77; + case GNUNET_OK: + if (GNUNET_OK != + /* Set up event loop and reschedule context, plus + * start/stop the exchange. It calls TALER_TESTING_setup + * which creates the 'is' object. + */ + TALER_TESTING_setup_with_exchange (&run, + NULL, + CONFIG_FILE)) + return 1; + break; + default: + GNUNET_break (0); + return 1; + } + return 0; +} + +/* end of test_exchange_api_keys_cherry_picking_new.c */ diff --git a/src/lib/test_exchange_api_new.c b/src/lib/test_exchange_api_new.c new file mode 100644 index 000000000..bcbdb5ea4 --- /dev/null +++ b/src/lib/test_exchange_api_new.c @@ -0,0 +1,1001 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange/test_exchange_api_new.c + * @brief testcase to test exchange's HTTP API interface + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + +/** + * Configuration file we use. One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE "test_exchange_api.conf" + +#define CONFIG_FILE_EXPIRE_RESERVE_NOW "test_exchange_api_expire_reserve_now.conf" + +/** + * Is the configuration file is set to include wire format 'ebics'? + * Requires that EBICS /history function is implemented, which it + * is currently not. Once it is, set ENABLE_CREDIT to YES in the + * configuration and then set this option to 1. + */ +#define WIRE_EBICS 0 + +/** + * URL of the fakebank. Obtained from CONFIG_FILE's + * "exchange-wire-test:BANK_URI" option. + */ +static char *fakebank_url; + +/** + * Exchange base URL as it appears in the configuration. Note + * that it might differ from the one where the exchange actually + * listens from. + */ +static char *exchange_url; + +/** + * Auditor base URL as it appears in the configuration. Note + * that it might differ from the one where the auditor actually + * listens from. + */ +static char *auditor_url; + +/** + * Account number of the exchange at the bank. + */ +#define EXCHANGE_ACCOUNT_NO 2 + +/** + * Account number of some user. + */ +#define USER_ACCOUNT_NO 42 + +/** + * User name. Never checked by fakebank. + */ +#define USER_LOGIN_NAME "user42" + +/** + * User password. Never checked by fakebank. + */ +#define USER_LOGIN_PASS "pass42" + +/** + * Execute the taler-exchange-wirewatch command with + * our configuration file. + * + * @param label label to use for the command. + */ +#define CMD_EXEC_WIREWATCH(label) \ + TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE) + +/** + * Execute the taler-exchange-aggregator command with + * our configuration file. + * + * @param label label to use for the command. + */ +#define CMD_EXEC_AGGREGATOR(label) \ + TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE) + +/** + * Run wire transfer of funds from some user's account to the + * exchange. + * + * @param label label to use for the command. + * @param amount amount to transfer, i.e. "EUR:1" + */ +#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \ + TALER_TESTING_cmd_fakebank_transfer (label, amount, \ + fakebank_url, USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO, \ + USER_LOGIN_NAME, USER_LOGIN_PASS, exchange_url) + +/** + * Run wire transfer of funds from some user's account to the + * exchange. + * + * @param label label to use for the command. + * @param amount amount to transfer, i.e. "EUR:1" + */ +#define CMD_TRANSFER_TO_EXCHANGE_SUBJECT(label,amount,subject) \ + TALER_TESTING_cmd_fakebank_transfer_with_subject \ + (label, amount, fakebank_url, USER_ACCOUNT_NO, \ + EXCHANGE_ACCOUNT_NO, USER_LOGIN_NAME, USER_LOGIN_PASS, \ + subject, exchange_url) + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, + struct TALER_TESTING_Interpreter *is) +{ + + /** + * Checks made against /wire response. + */ + struct TALER_TESTING_Command wire[] = { + /** + * Check if 'x-taler-bank' wire method is offered + * by the exchange. + */ + TALER_TESTING_cmd_wire ("wire-taler-bank-1", + "x-taler-bank", + NULL, + MHD_HTTP_OK), + #if WIRE_EBICS + /** + * Check if 'ebics' wire method is offered by the exchange. + */ + TALER_TESTING_cmd_wire ("wire-sepa-1", + "ebics", + NULL, + MHD_HTTP_OK), + #endif + + TALER_TESTING_cmd_end () + }; + + /** + * Test withdrawal plus spending. + */ + struct TALER_TESTING_Command withdraw[] = { + + /** + * Move money to the exchange's bank account. + */ + CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1", + "EUR:5.01"), + + /** + * Make a reserve exist, according to the previous + * transfer. + */ + CMD_EXEC_WIREWATCH ("wirewatch-1"), + + + /** + * Withdraw EUR:5. + */ + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", + "create-reserve-1", + "EUR:5", + MHD_HTTP_OK), + + /** + * Check the reserve is depleted. + */ + TALER_TESTING_cmd_status ("status-1", + "create-reserve-1", + "EUR:0", + MHD_HTTP_OK), + + TALER_TESTING_cmd_end () + }; + + struct TALER_TESTING_Command spend[] = { + /** + * Spend the coin. + */ + TALER_TESTING_cmd_deposit + ("deposit-simple", "withdraw-coin-1", 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_OK), + + /** + * Try to overdraw. + */ + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2", + "create-reserve-1", + "EUR:5", + MHD_HTTP_FORBIDDEN), + + /** + * Try to double spend using different wire details. + */ + TALER_TESTING_cmd_deposit + ("deposit-double-1", "withdraw-coin-1", 0, + TALER_TESTING_make_wire_details (43, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_FORBIDDEN), + + /** + * Try to double spend using a different transaction id. + * (copied verbatim from old exchange-lib tests.) + * FIXME: how can it get a different transaction id? There + * isn't such a thing actually, the exchange only knows about + * contract terms' hashes. So since the contract terms are + * exactly the same as the previous command, + * how can a different id be generated? + */ + TALER_TESTING_cmd_deposit + ("deposit-double-1", "withdraw-coin-1", 0, + TALER_TESTING_make_wire_details (43, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_FORBIDDEN), + + /** + * Try to double spend with different proposal. + */ + TALER_TESTING_cmd_deposit + ("deposit-double-2", "withdraw-coin-1", 0, + TALER_TESTING_make_wire_details (43, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\"value\":2}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_FORBIDDEN), + + TALER_TESTING_cmd_end () + }; + + + struct TALER_TESTING_Command refresh[] = { + + /** + * Fill reserve with EUR:5, 1ct is for fees. NOTE: the old + * test-suite gave a account number of _424_ to the user at + * this step; to type less, here the _42_ number is reused. + * Does this change the tests semantics? + */ + CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-1", + "EUR:5.01"), + + /** + * Make previous command effective. + */ + CMD_EXEC_WIREWATCH ("wirewatch-2"), + + /** + * Withdraw EUR:5. + */ + TALER_TESTING_cmd_withdraw_amount + ("refresh-withdraw-coin-1", + "refresh-create-reserve-1", + "EUR:5", + MHD_HTTP_OK), + /** + * 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) + */ + TALER_TESTING_cmd_deposit + ("refresh-deposit-partial", + "refresh-withdraw-coin-1", 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\ + \"value\":\"EUR:1\"}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:1", MHD_HTTP_OK), + + /** + * Melt the rest of the coin's value + * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */ + TALER_TESTING_cmd_refresh_melt_double + ("refresh-melt-1", "EUR:4", + "refresh-withdraw-coin-1", MHD_HTTP_OK), + /** + * Complete (successful) melt operation, and + * withdraw the coins + */ + TALER_TESTING_cmd_refresh_reveal + ("refresh-reveal-1", + "refresh-melt-1", MHD_HTTP_OK), + + /** + * Do it again to check idempotency + */ + TALER_TESTING_cmd_refresh_reveal + ("refresh-reveal-1-idempotency", + "refresh-melt-1", MHD_HTTP_OK), + + /** + * Test that /refresh/link works + */ + TALER_TESTING_cmd_refresh_link + ("refresh-link-1", + "refresh-reveal-1", MHD_HTTP_OK), + + /** + * Try to spend a refreshed EUR:1 coin + */ + TALER_TESTING_cmd_deposit + ("refresh-deposit-refreshed-1a", + "refresh-reveal-1-idempotency", 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\ + \"value\":3}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:1", MHD_HTTP_OK), + + /** + * Try to spend a refreshed EUR:0.1 coin + */ + TALER_TESTING_cmd_deposit + ("refresh-deposit-refreshed-1b", + "refresh-reveal-1", 3, + TALER_TESTING_make_wire_details (43, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\ + \"value\":3}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:0.1", MHD_HTTP_OK), + + /* Test running a failing melt operation (same operation + * again must fail) */ + TALER_TESTING_cmd_refresh_melt + ("refresh-melt-failing", "EUR:4", + "refresh-withdraw-coin-1", MHD_HTTP_FORBIDDEN), + + /* FIXME: also test with coin that was already melted + * (signature differs from coin that was deposited...) */ + + TALER_TESTING_cmd_end () + }; + + struct TALER_TESTING_Command track[] = { + /** + * Try resolving a deposit's WTID, as we never triggered + * execution of transactions, the answer should be that + * the exchange knows about the deposit, but has no WTID yet. + */ + TALER_TESTING_cmd_track_transaction + ("deposit-wtid-found", + "deposit-simple", 0, MHD_HTTP_ACCEPTED, NULL), + + /** + * Try resolving a deposit's WTID for a failed deposit. + * As the deposit failed, the answer should be that the + * exchange does NOT know about the deposit. + */ + TALER_TESTING_cmd_track_transaction + ("deposit-wtid-failing", + "deposit-double-2", 0, MHD_HTTP_NOT_FOUND, NULL), + + /** + * Try resolving an undefined (all zeros) WTID; this + * should fail as obviously the exchange didn't use that + * WTID value for any transaction. + */ + TALER_TESTING_cmd_track_transfer_empty + ("wire-deposit-failing", + NULL, 0, MHD_HTTP_NOT_FOUND), + + /** + * Run transfers. Note that _actual_ aggregation will NOT + * happen here, as each deposit operation is run with a + * fresh merchant public key! NOTE: this comment comes + * "verbatim" from the old test-suite, and IMO does not explain + * a lot! + */ + CMD_EXEC_AGGREGATOR ("run-aggregator"), + + /** + * Check all the transfers took place. + */ + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-499c", exchange_url, + "EUR:4.98", 2, 42), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-99c1", exchange_url, + "EUR:0.98", 2, 42), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-99c2", exchange_url, + "EUR:0.98", 2, 42), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-99c", exchange_url, + "EUR:0.08", 2, 43), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-aai-1", exchange_url, + "EUR:5.01", 42, 2), + + /** + * NOTE: the old test-suite had this "check bank transfer" + * command with debit account == 424. + */ + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-aai-2", exchange_url, + "EUR:5.01", 42, 2), + + TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"), + + TALER_TESTING_cmd_track_transaction + ("deposit-wtid-ok", + "deposit-simple", 0, MHD_HTTP_OK, "check_bank_transfer-499c"), + + TALER_TESTING_cmd_track_transfer + ("wire-deposit-success-bank", + "check_bank_transfer-99c1", 0, MHD_HTTP_OK, "EUR:0.98", + "EUR:0.01"), + + TALER_TESTING_cmd_track_transfer + ("wire-deposits-success-wtid", + "deposit-wtid-ok", 0, MHD_HTTP_OK, "EUR:4.98", + "EUR:0.01"), + + TALER_TESTING_cmd_end () + }; + + + /** + * This block checks whether a wire deadline + * very far in the future does NOT get aggregated now. + */ + struct TALER_TESTING_Command unaggregation[] = { + + TALER_TESTING_cmd_check_bank_empty + ("far-future-aggregation-a"), + + CMD_TRANSFER_TO_EXCHANGE ("create-reserve-unaggregated", + "EUR:5.01"), + + CMD_EXEC_WIREWATCH ("wirewatch-unaggregated"), + + /* "consume" reserve creation transfer. */ + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-unaggregated", + exchange_url, + "EUR:5.01", + 42, + 2), + + TALER_TESTING_cmd_withdraw_amount + ("withdraw-coin-unaggregated", + "create-reserve-unaggregated", + "EUR:5", + MHD_HTTP_OK), + + TALER_TESTING_cmd_deposit + ("deposit-unaggregated", + "withdraw-coin-unaggregated", + 0, + TALER_TESTING_make_wire_details + (43, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", + GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_YEARS, + 3000), + "EUR:5", + MHD_HTTP_OK), + + CMD_EXEC_AGGREGATOR ("aggregation-attempt"), + + TALER_TESTING_cmd_check_bank_empty + ("far-future-aggregation-b"), + + TALER_TESTING_cmd_end () + }; + + + struct TALER_TESTING_Command refund[] = { + + /** + * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per + * config. + */ + CMD_TRANSFER_TO_EXCHANGE ("create-reserve-r1", + "EUR:5.01"), + + + /** + * Run wire-watch to trigger the reserve creation. + */ + CMD_EXEC_WIREWATCH ("wirewatch-3"), + + /* Withdraw a 5 EUR coin, at fee of 1 ct */ + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1", + "create-reserve-r1", + "EUR:5", + MHD_HTTP_OK), + /** + * Spend 5 EUR of the 5 EUR coin (in full) (merchant would + * receive EUR:4.99 due to 1 ct deposit fee) + */ + TALER_TESTING_cmd_deposit + ("deposit-refund-1", "withdraw-coin-r1", 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\"," + "\"value\":\"EUR:5\"}]}", + GNUNET_TIME_UNIT_MINUTES, "EUR:5", MHD_HTTP_OK), + + + /** + * Run transfers. Should do nothing as refund deadline blocks + * it + */ + CMD_EXEC_AGGREGATOR ("run-aggregator-refund"), + + /** + * Check that aggregator didn't do anything, as expected. + * Note, this operation takes two commands: one to "flush" + * the preliminary transfer (used to withdraw) from the + * fakebank and the second to actually check there are not + * other transfers around. + */ + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-pre-refund", exchange_url, + "EUR:5.01", 42, 2), + + TALER_TESTING_cmd_check_bank_empty + ("check_bank_transfer-pre-refund"), + + TALER_TESTING_cmd_refund + ("refund-ok", MHD_HTTP_OK, + "EUR:5", "EUR:0.01", "deposit-refund-1"), + + TALER_TESTING_cmd_refund + ("refund-ok-double", MHD_HTTP_OK, + "EUR:5", "EUR:0.01", "deposit-refund-1"), + + /* Previous /refund(s) had id == 0. */ + TALER_TESTING_cmd_refund_with_id + ("refund-conflicting", MHD_HTTP_CONFLICT, + "EUR:5", "EUR:0.01", "deposit-refund-1", 1), + + /** + * Spend 4.99 EUR of the refunded 4.99 EUR coin (1ct gone + * due to refund) (merchant would receive EUR:4.98 due to + * 1 ct deposit fee) */ + TALER_TESTING_cmd_deposit + ("deposit-refund-2", "withdraw-coin-r1", 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"more ice cream\"," + "\"value\":\"EUR:5\"}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:4.99", MHD_HTTP_OK), + + + /** + * Run transfers. This will do the transfer as refund deadline + * was 0 + */ + CMD_EXEC_AGGREGATOR ("run-aggregator-3"), + + /** + * Check that deposit did run. + */ + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-pre-refund", exchange_url, + "EUR:4.97", 2, 42), + + /** + * Run failing refund, as past deadline & aggregation. + */ + TALER_TESTING_cmd_refund + ("refund-fail", MHD_HTTP_GONE, + "EUR:4.99", "EUR:0.01", "deposit-refund-2"), + + TALER_TESTING_cmd_check_bank_empty + ("check-empty-after-refund"), + + /** + * Test refunded coins are never executed, even past + * refund deadline + */ + CMD_TRANSFER_TO_EXCHANGE ("create-reserve-rb", + "EUR:5.01"), + + CMD_EXEC_WIREWATCH ("wirewatch-rb"), + + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-rb", + "create-reserve-rb", + "EUR:5", + MHD_HTTP_OK), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-aai-3b", exchange_url, + "EUR:5.01", 42, 2), + + + TALER_TESTING_cmd_deposit + ("deposit-refund-1b", "withdraw-coin-rb", 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\"," + "\"value\":\"EUR:5\"}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_OK), + + /** + * Trigger refund (before aggregator had a chance to execute + * deposit, even though refund deadline was zero). + */ + TALER_TESTING_cmd_refund + ("refund-ok-fast", MHD_HTTP_OK, + "EUR:5", "EUR:0.01", "deposit-refund-1b"), + + /** + * Run transfers. This will do the transfer as refund deadline + * was 0, except of course because the refund succeeded, the + * transfer should no longer be done. + */ + CMD_EXEC_AGGREGATOR ("run-aggregator-3b"), + + /* check that aggregator didn't do anything, as expected */ + TALER_TESTING_cmd_check_bank_empty + ("check-refund-fast-not-run"), + + TALER_TESTING_cmd_end () + }; + + struct TALER_TESTING_Command payback[] = { + /** + * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per + * config. + */ + CMD_TRANSFER_TO_EXCHANGE ("payback-create-reserve-1", + "EUR:5.01"), + + /** + * Run wire-watch to trigger the reserve creation. + */ + CMD_EXEC_WIREWATCH ("wirewatch-4"), + + /* Withdraw a 5 EUR coin, at fee of 1 ct */ + TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-1", + "payback-create-reserve-1", + "EUR:5", + MHD_HTTP_OK), + /* Make coin invalid */ + TALER_TESTING_cmd_revoke ("revoke-1", + MHD_HTTP_OK, + "payback-withdraw-coin-1", + CONFIG_FILE), + + /* Refund coin to bank account */ + TALER_TESTING_cmd_payback ("payback-1", + MHD_HTTP_OK, + "payback-withdraw-coin-1", + "EUR:5"), + + /* Check the money is back with the reserve */ + TALER_TESTING_cmd_status ("payback-reserve-status-1", + "payback-create-reserve-1", + "EUR:5.0", + MHD_HTTP_OK), + + /* Re-withdraw from this reserve */ + TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2", + "payback-create-reserve-1", + "EUR:1", + MHD_HTTP_OK), + + /** + * This withdrawal will test the logic to create a "payback" + * element to insert into the reserve's history. + */ + TALER_TESTING_cmd_withdraw_amount + ("payback-withdraw-coin-2-over", + "payback-create-reserve-1", + "EUR:10", + MHD_HTTP_FORBIDDEN), + + TALER_TESTING_cmd_status ("payback-reserve-status-2", + "payback-create-reserve-1", + "EUR:3.99", + MHD_HTTP_OK), + + /** + * These commands should close the reserve because + * the aggregator is given a config file that ovverrides + * the reserve expiration time (making it now-ish) + */ + CMD_TRANSFER_TO_EXCHANGE + ("short-lived-reserve", + "EUR:5.01"), + + TALER_TESTING_cmd_exec_wirewatch + ("short-lived-aggregation", + CONFIG_FILE_EXPIRE_RESERVE_NOW), + + TALER_TESTING_cmd_exec_aggregator + ("close-reserves", + CONFIG_FILE_EXPIRE_RESERVE_NOW), + + TALER_TESTING_cmd_status ("short-lived-status", + "short-lived-reserve", + "EUR:0", + MHD_HTTP_OK), + + TALER_TESTING_cmd_withdraw_amount + ("expired-withdraw", + "short-lived-reserve", + "EUR:1", + MHD_HTTP_FORBIDDEN), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_short-lived_transfer", + exchange_url, + "EUR:5.01", + 42, + 2), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_short-lived_reimburse", + exchange_url, + "EUR:5", + 2, + 42), + + /** + * Fill reserve with EUR:2.02, as withdraw fee is 1 ct per + * config, then withdraw two coin, partially spend one, and + * then have the rest paid back. Check deposit of other coin + * fails. (Do not use EUR:5 here as the EUR:5 coin was + * revoked and we did not bother to create a new one...) + */ + CMD_TRANSFER_TO_EXCHANGE ("payback-create-reserve-2", + "EUR:2.02"), + + /* Make previous command effective. */ + CMD_EXEC_WIREWATCH ("wirewatch-5"), + + /* Withdraw a 1 EUR coin, at fee of 1 ct */ + TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2a", + "payback-create-reserve-2", + "EUR:1", + MHD_HTTP_OK), + + /* Withdraw a 1 EUR coin, at fee of 1 ct */ + TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2b", + "payback-create-reserve-2", + "EUR:1", + MHD_HTTP_OK), + + TALER_TESTING_cmd_deposit + ("payback-deposit-partial", + "payback-withdraw-coin-2a", 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:0.5", MHD_HTTP_OK), + + + TALER_TESTING_cmd_revoke ("revoke-2", MHD_HTTP_OK, + "payback-withdraw-coin-2a", + CONFIG_FILE), + + TALER_TESTING_cmd_payback ("payback-2", MHD_HTTP_OK, + "payback-withdraw-coin-2a", + "EUR:0.5"), + + TALER_TESTING_cmd_payback ("payback-2b", MHD_HTTP_FORBIDDEN, + "payback-withdraw-coin-2a", + "EUR:0.5"), + + TALER_TESTING_cmd_deposit + ("payback-deposit-revoked", + "payback-withdraw-coin-2b", 0, + TALER_TESTING_make_wire_details (42, + fakebank_url), + "{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:1", MHD_HTTP_NOT_FOUND), + + + /* Test deposit fails after payback, with proof in payback */ + + /* FIXME: #3887: right now, the exchange will never return the + * coin's transaction history with payback data, as we get a + * 404 on the DK! */ + TALER_TESTING_cmd_deposit + ("payback-deposit-partial-after-payback", + "payback-withdraw-coin-2a", + 0, + TALER_TESTING_make_wire_details + (42, + fakebank_url), + "{\"items\":[{\"name\":\"extra ice cream\",\"value\":1}]}", + GNUNET_TIME_UNIT_ZERO, + "EUR:0.5", + MHD_HTTP_NOT_FOUND), + + /* Test that revoked coins cannot be withdrawn */ + CMD_TRANSFER_TO_EXCHANGE ("payback-create-reserve-3", + "EUR:1.01"), + + CMD_EXEC_WIREWATCH ("wirewatch-6"), + + TALER_TESTING_cmd_withdraw_amount + ("payback-withdraw-coin-3-revoked", + "payback-create-reserve-3", + "EUR:1", + MHD_HTTP_NOT_FOUND), + + /* check that we are empty before the rejection test */ + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-pr1", exchange_url, + "EUR:5.01", 42, 2), + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-pr2", exchange_url, + "EUR:2.02", 42, 2), + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-pr3", exchange_url, + "EUR:1.01", 42, 2), + + TALER_TESTING_cmd_check_bank_empty + ("check-empty-again"), + + /* Test rejection of bogus wire transfers */ + CMD_TRANSFER_TO_EXCHANGE_SUBJECT + ("bogus-subject", + "EUR:1.01", + "not a reserve public key"), + + CMD_EXEC_WIREWATCH ("wirewatch-7"), + + TALER_TESTING_cmd_check_bank_empty + ("check-empty-from-reject"), + + TALER_TESTING_cmd_end () + }; + + #define RESERVE_OPEN_CLOSE_CHUNK 4 + #define RESERVE_OPEN_CLOSE_ITERATIONS 3 + #define CONSTANT_KEY \ + "09QGYPEKNHBACK135BNXZFHA0YTQXT1KJDRVXF4J822G99AYNQ8G" + + struct TALER_TESTING_Command reserve_open_close + [(RESERVE_OPEN_CLOSE_ITERATIONS + * RESERVE_OPEN_CLOSE_CHUNK) + 1]; + + for (unsigned int i = 0; + i < RESERVE_OPEN_CLOSE_ITERATIONS; + i++) + { + reserve_open_close[i * RESERVE_OPEN_CLOSE_CHUNK] + = CMD_TRANSFER_TO_EXCHANGE_SUBJECT + ("reserve-open-close-key", + "EUR:20", + CONSTANT_KEY); + + reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 1] + = TALER_TESTING_cmd_exec_wirewatch + ("reserve-open-close-wirewatch", + CONFIG_FILE_EXPIRE_RESERVE_NOW); + + reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 2] + = TALER_TESTING_cmd_exec_aggregator + ("reserve-open-close-aggregation", + CONFIG_FILE_EXPIRE_RESERVE_NOW); + + reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 3] + = TALER_TESTING_cmd_status ("reserve-open-close-status", + "reserve-open-close-key", + "EUR:0", + MHD_HTTP_OK); + } + reserve_open_close + [RESERVE_OPEN_CLOSE_ITERATIONS * RESERVE_OPEN_CLOSE_CHUNK] + = TALER_TESTING_cmd_end (); + + struct TALER_TESTING_Command commands[] = { + + TALER_TESTING_cmd_batch ("wire", + wire), + + TALER_TESTING_cmd_batch ("withdraw", + withdraw), + + TALER_TESTING_cmd_batch ("spend", + spend), + + TALER_TESTING_cmd_batch ("refresh", + refresh), + + TALER_TESTING_cmd_batch ("track", + track), + + TALER_TESTING_cmd_batch ("unaggregation", + unaggregation), + + TALER_TESTING_cmd_batch ("refund", + refund), + + TALER_TESTING_cmd_batch ("payback", + payback), + /* Fix #5462. */ + TALER_TESTING_cmd_batch ("reserve-open-close", + reserve_open_close), + /** + * End the suite. Fixme: better to have a label for this + * too, as it shows as "(null)" on logs. + */ + TALER_TESTING_cmd_end () + }; + + TALER_TESTING_run_with_fakebank (is, + commands, + fakebank_url); +} + + +int +main (int argc, + char * const *argv) +{ + /* These environment variables get in the way... */ + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test-exchange-api-new", + "INFO", + NULL); + if (NULL == (fakebank_url + /* Check fakebank port is available and config cares + * about bank url. */ + = TALER_TESTING_prepare_fakebank (CONFIG_FILE, + "account-2"))) + return 77; + TALER_TESTING_cleanup_files (CONFIG_FILE); + /* @helpers. Run keyup, create tables, ... Note: it + * fetches the port number from config in order to see + * if it's available. */ + switch (TALER_TESTING_prepare_exchange (CONFIG_FILE, + &auditor_url, + &exchange_url)) + { + case GNUNET_SYSERR: + GNUNET_break (0); + return 1; + case GNUNET_NO: + return 77; + case GNUNET_OK: + if (GNUNET_OK != + /* Set up event loop and reschedule context, plus + * start/stop the exchange. It calls TALER_TESTING_setup + * which creates the 'is' object. + */ + TALER_TESTING_setup_with_exchange (&run, + NULL, + CONFIG_FILE)) + return 1; + break; + default: + GNUNET_break (0); + return 1; + } + return 0; +} + +/* end of test_exchange_api_new.c */ diff --git a/src/lib/test_exchange_api_overlapping_keys_bug.c b/src/lib/test_exchange_api_overlapping_keys_bug.c new file mode 100755 index 000000000..d1fd7123b --- /dev/null +++ b/src/lib/test_exchange_api_overlapping_keys_bug.c @@ -0,0 +1,135 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/test_exchange_api_keys_cherry_picking_new.c + * @brief testcase to test exchange's /keys cherry picking ability + * @author Marcello Stanisci + * @author Christian Grothoff + */ + +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + +/** + * Configuration file we use. One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE "test_exchange_api_keys_cherry_picking.conf" + +/** + * Used to increase the number of denomination keys. + */ +#define CONFIG_FILE_EXTENDED \ + "test_exchange_api_keys_cherry_picking_extended.conf" + +/** + * Used to increase the number of denomination keys. + */ +#define CONFIG_FILE_EXTENDED_2 \ + "test_exchange_api_keys_cherry_picking_extended_2.conf" + +/** + * Exchange base URL; mainly purpose is to make the compiler happy. + */ +static char *exchange_url; + +/** + * Auditor base URL; mainly purpose is to make the compiler happy. + */ +static char *auditor_url; + + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, + struct TALER_TESTING_Interpreter *is) +{ + + struct TALER_TESTING_Command commands[] = { + + TALER_TESTING_cmd_check_keys ("first-download", + 1, + 4), + + TALER_TESTING_cmd_check_keys ("second-download", + 2, + 6), + TALER_TESTING_cmd_end () + }; + + TALER_TESTING_run (is, + commands); +} + + +int +main (int argc, + char * const *argv) +{ + /* These environment variables get in the way... */ + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test-exchange-api-cherry-picking-new", + "DEBUG", NULL); + TALER_TESTING_cleanup_files (CONFIG_FILE); + /* @helpers. Run keyup, create tables, ... Note: it + * fetches the port number from config in order to see + * if it's available. */ + switch (TALER_TESTING_prepare_exchange (CONFIG_FILE, + &auditor_url, + &exchange_url)) + { + case GNUNET_SYSERR: + GNUNET_break (0); + return 1; + case GNUNET_NO: + return 77; + case GNUNET_OK: + if (GNUNET_OK != + /* Set up event loop and reschedule context, plus + * start/stop the exchange. It calls TALER_TESTING_setup + * which creates the 'is' object. + */ + TALER_TESTING_setup_with_exchange (&run, + NULL, + CONFIG_FILE)) + return 1; + break; + default: + GNUNET_break (0); + return 1; + } + return 0; +} + +/* end of test_exchange_api_keys_cherry_picking_new.c */ diff --git a/src/lib/test_exchange_api_twisted.c b/src/lib/test_exchange_api_twisted.c new file mode 100644 index 000000000..1c5300586 --- /dev/null +++ b/src/lib/test_exchange_api_twisted.c @@ -0,0 +1,399 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange/test_exchange_api_twister.c + * @brief testcase to test exchange's HTTP API interface + * @author Marcello Stanisci + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ + +#include "platform.h" +#include <taler/taler_util.h> +#include <taler/taler_signatures.h> +#include <taler/taler_exchange_service.h> +#include <taler/taler_json_lib.h> +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include <taler/taler_bank_service.h> +#include <taler/taler_fakebank_lib.h> +#include <taler/taler_testing_lib.h> +#include <taler/taler_twister_testing_lib.h> +#include <taler/taler_twister_service.h> + +/** + * Configuration file we use. One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE "test_exchange_api_twisted.conf" + +/** + * (real) Twister URL. Used at startup time to check if it runs. + */ +static char *twister_url; + +/** + * URL of the fakebank. Obtained from CONFIG_FILE's + * "exchange-wire-test:BANK_URI" option. + */ +static char *fakebank_url; + +/** + * Exchange base URL. + */ +static char *exchange_url; + +/** + * Auditor URL, unused but needed to achieve compilation. + */ +static char *auditor_url; + +/** + * Twister process. + */ +static struct GNUNET_OS_Process *twisterd; + +/** + * Account number of the exchange at the bank. + */ +#define EXCHANGE_ACCOUNT_NO 2 + +/** + * Account number of some user. + */ +#define USER_ACCOUNT_NO 62 + +/** + * User name. Never checked by fakebank. + */ +#define USER_LOGIN_NAME "user42" + +/** + * User password. Never checked by fakebank. + */ +#define USER_LOGIN_PASS "pass42" + +/** + * Execute the taler-exchange-wirewatch command with + * our configuration file. + * + * @param label label to use for the command. + */ +#define CMD_EXEC_WIREWATCH(label) \ + TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE) + +/** + * Execute the taler-exchange-aggregator command with + * our configuration file. + * + * @param label label to use for the command. + */ +#define CMD_EXEC_AGGREGATOR(label) \ + TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE) + +/** + * Run wire transfer of funds from some user's account to the + * exchange. + * + * @param label label to use for the command. + * @param amount amount to transfer, i.e. "EUR:1" + * @param url exchange_url + */ +#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \ + TALER_TESTING_cmd_fakebank_transfer (label, amount, \ + fakebank_url, USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO, \ + USER_LOGIN_NAME, USER_LOGIN_PASS, exchange_url) + +/** + * Run wire transfer of funds from some user's account to the + * exchange. + * + * @param label label to use for the command. + * @param amount amount to transfer, i.e. "EUR:1" + */ +#define CMD_TRANSFER_TO_EXCHANGE_SUBJECT(label,amount,subject) \ + TALER_TESTING_cmd_fakebank_transfer_with_subject \ + (label, amount, fakebank_url, USER_ACCOUNT_NO, \ + EXCHANGE_ACCOUNT_NO, USER_LOGIN_NAME, USER_LOGIN_PASS, \ + subject) + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, + struct TALER_TESTING_Interpreter *is) +{ + + + /** + * This batch aims to trigger the 409 Conflict + * response from a refresh-reveal operation. + */ + struct TALER_TESTING_Command refresh_409_conflict[] = { + + CMD_TRANSFER_TO_EXCHANGE + ("refresh-create-reserve", + "EUR:5.01"), + + /** + * Make previous command effective. + */ + CMD_EXEC_WIREWATCH + ("wirewatch"), + + /** + * Withdraw EUR:5. + */ + TALER_TESTING_cmd_withdraw_amount + ("refresh-withdraw-coin", + "refresh-create-reserve", + "EUR:5", + MHD_HTTP_OK), + + TALER_TESTING_cmd_deposit + ("refresh-deposit-partial", + "refresh-withdraw-coin", + 0, + TALER_TESTING_make_wire_details + (42, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\ + \"value\":\"EUR:1\"}]}", + GNUNET_TIME_UNIT_ZERO, + "EUR:1", + MHD_HTTP_OK), + + /** + * Melt the rest of the coin's value + * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */ + TALER_TESTING_cmd_refresh_melt + ("refresh-melt", + "EUR:4", + "refresh-withdraw-coin", + MHD_HTTP_OK), + + /* Trigger 409 Conflict. */ + TALER_TESTING_cmd_flip_upload + ("flip-upload", + CONFIG_FILE, + "transfer_privs.0"), + + TALER_TESTING_cmd_refresh_reveal + ("refresh-(flipped-)reveal", + "refresh-melt", + MHD_HTTP_CONFLICT), + + TALER_TESTING_cmd_end () + + }; + + + /** + * NOTE: not all CMDs actually need the twister, + * so it may be better to move those into the "main" + * lib test suite. + */ + struct TALER_TESTING_Command refund[] = { + + CMD_TRANSFER_TO_EXCHANGE + ("create-reserve-r1", + "EUR:5.01"), + + CMD_EXEC_WIREWATCH + ("wirewatch-r1"), + + TALER_TESTING_cmd_withdraw_amount + ("withdraw-coin-r1", + "create-reserve-r1", + "EUR:5", + MHD_HTTP_OK), + + TALER_TESTING_cmd_deposit + ("deposit-refund-1", + "withdraw-coin-r1", + 0, + TALER_TESTING_make_wire_details + (42, + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\"," + "\"value\":\"EUR:5\"}]}", + GNUNET_TIME_UNIT_MINUTES, + "EUR:5", + MHD_HTTP_OK), + + TALER_TESTING_cmd_refund + ("refund-currency-missmatch", + MHD_HTTP_PRECONDITION_FAILED, + "USD:5", + "USD:0.01", + "deposit-refund-1"), + + TALER_TESTING_cmd_refund + ("refund-fee-above-amount", + MHD_HTTP_BAD_REQUEST, + "EUR:5", + "EUR:10", + "deposit-refund-1"), + + TALER_TESTING_cmd_flip_upload + ("flip-upload", + CONFIG_FILE, + "merchant_sig"), + + TALER_TESTING_cmd_refund + ("refund-bad-sig", + MHD_HTTP_UNAUTHORIZED, + "EUR:5", + "EUR:0.01", + "deposit-refund-1"), + + /* This next deposit CMD is only used to provide a + * good merchant signature to the next (failing) refund + * operations. */ + + TALER_TESTING_cmd_deposit + ("deposit-refund-to-fail", + "withdraw-coin-r1", + 0, /* coin index. */ + TALER_TESTING_make_wire_details + (42, + fakebank_url), + /* This parameter will make any comparison about + h_contract_terms fail, when /refund will be handled. + So in other words, this is h_contract missmatch. */ + "{\"items\":[{\"name\":\"ice skate\"," + "\"value\":\"EUR:5\"}]}", + GNUNET_TIME_UNIT_MINUTES, + "EUR:5", + MHD_HTTP_FORBIDDEN), + + TALER_TESTING_cmd_refund + ("refund-deposit-not-found", + MHD_HTTP_NOT_FOUND, + "EUR:5", + "EUR:0.01", + "deposit-refund-to-fail"), + + TALER_TESTING_cmd_refund + ("refund-insufficient-funds", + MHD_HTTP_PRECONDITION_FAILED, + "EUR:50", + "EUR:0.01", + "deposit-refund-1"), + + TALER_TESTING_cmd_refund + ("refund-fee-too-low", + MHD_HTTP_BAD_REQUEST, + "EUR:5", + "EUR:0.000001", + "deposit-refund-1"), + + TALER_TESTING_cmd_end () + + }; + + struct TALER_TESTING_Command commands[] = { + + TALER_TESTING_cmd_batch ("refresh-reveal-409-conflict", + refresh_409_conflict), + + TALER_TESTING_cmd_batch ("refund", + refund), + + TALER_TESTING_cmd_end () + }; + + TALER_TESTING_run_with_fakebank (is, + commands, + fakebank_url); +} + +/** + * Kill, wait, and destroy convenience function. + * + * @param process process to purge. + */ +static void +purge_process (struct GNUNET_OS_Process *process) +{ + GNUNET_OS_process_kill (process, SIGINT); + GNUNET_OS_process_wait (process); + GNUNET_OS_process_destroy (process); +} + +int +main (int argc, + char * const *argv) +{ + unsigned int ret; + /* These environment variables get in the way... */ + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test-exchange-api-new-twisted", + "DEBUG", NULL); + + if (NULL == (fakebank_url = TALER_TESTING_prepare_fakebank + (CONFIG_FILE, + "account-2"))) + return 77; + + if (NULL == (twister_url = TALER_TESTING_prepare_twister + (CONFIG_FILE))) + return 77; + + TALER_TESTING_cleanup_files (CONFIG_FILE); + + switch (TALER_TESTING_prepare_exchange (CONFIG_FILE, + &auditor_url, + &exchange_url)) + { + case GNUNET_SYSERR: + GNUNET_break (0); + return 1; + case GNUNET_NO: + return 77; + + case GNUNET_OK: + + if (NULL == (twisterd = TALER_TESTING_run_twister + (CONFIG_FILE))) + return 77; + + ret = TALER_TESTING_setup_with_exchange (&run, + NULL, + CONFIG_FILE); + purge_process (twisterd); + GNUNET_free (twister_url); + + if (GNUNET_OK != ret) + return 1; + break; + default: + GNUNET_break (0); + return 1; + } + return 0; +} + +/* end of test_exchange_api_twisted.c */ diff --git a/src/lib/test_exchange_api_twisted.conf b/src/lib/test_exchange_api_twisted.conf new file mode 100644 index 000000000..ba59b5a60 --- /dev/null +++ b/src/lib/test_exchange_api_twisted.conf @@ -0,0 +1,169 @@ +# This file is in the public domain. +# + +[twister] +# HTTP listen port for twister +HTTP_PORT = 8888 + +# HTTP Destination for twister. The test-Webserver needs +# to listen on the port used here. Note: no trailing '/'! +DESTINATION_BASE_URL = "http://localhost:8081" + +# Control port for TCP +# PORT = 8889 +HOSTNAME = localhost +ACCEPT_FROM = 127.0.0.1; +ACCEPT_FROM6 = ::1; + +# Control port for UNIX +UNIXPATH = /tmp/taler-service-twister.sock +UNIX_MATCH_UID = NO +UNIX_MATCH_GID = YES + +# Launching of twister by ARM +# BINARY = taler-service-twister +# AUTOSTART = NO +# FORCESTART = NO + +[PATHS] +# Persistant data storage for the testcase +TALER_TEST_HOME = test_exchange_api_home/ + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + +[exchange] + +# how long is one signkey valid? +SIGNKEY_DURATION = 4 weeks + +# how long are the signatures with the signkey valid? +LEGAL_DURATION = 2 years + +# how long do we provide to clients denomination and signing keys +# ahead of time? +LOOKAHEAD_PROVIDE = 4 weeks 1 day + +# Keep it short so the test runs fast. +LOOKAHEAD_SIGN = 12 h + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# How to access our database +DB = postgres + +# Base URL of the exchange ('S PROXY). This URL is where the +# twister listens at, so that it will be able to get all the +# connection addressed to the exchange. In fact, the presence +# of the twister is 100% transparent to the test case, as it +# only seeks the exchange/BASE_URL URL to connect to the exchange. +BASE_URL = "http://localhost:8888/" + +[exchangedb-postgres] +DB_CONN_STR = "postgres:///talercheck" + +[auditor] +BASE_URL = "http://the.auditor/" + +[auditordb-postgres] +CONFIG = "postgres:///talercheck" + +[account-2] +URL = payto://x-taler-bank/localhost:8082/2 +PLUGIN = taler_bank +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json +TALER_BANK_AUTH_METHOD = "basic" +USERNAME = user +PASSWORD = pass +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES + +[fees-x-taler-bank] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + +[coin_eur_ct_1] +value = EUR:0.01 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.00 +fee_deposit = EUR:0.00 +fee_refresh = EUR:0.01 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_ct_10] +value = EUR:0.10 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_1] +value = EUR:1 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_5] +value = EUR:5 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_10] +value = EUR:10 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 diff --git a/src/lib/testing_api_cmd_bank_check.c b/src/lib/testing_api_cmd_bank_check.c new file mode 100644 index 000000000..265cba176 --- /dev/null +++ b/src/lib/testing_api_cmd_bank_check.c @@ -0,0 +1,379 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_cmd_bank_check.c + * @brief command to check if a particular wire transfer took + * place. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" +#include "taler_fakebank_lib.h" + + +/** + * State for a "bank check" CMD. + */ +struct BankCheckState +{ + + /** + * Base URL of the exchange supposed to be + * involved in the bank transaction. + */ + const char *exchange_base_url; + + /** + * Expected transferred amount. + */ + const char *amount; + + /** + * Expected debit bank account. + */ + uint64_t debit_account; + + /** + * Expected credit bank account. + */ + uint64_t credit_account; + + /** + * Wire transfer subject (set by fakebank-lib). + */ + char *subject; + + /** + * Binary form of the wire transfer subject. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Reference to a CMD that provides all the data + * needed to issue the bank check. If NULL, that data + * must exist here in the state. + */ + const char *deposit_reference; +}; + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +check_bank_transfer_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct BankCheckState *bcs = cls; + + struct TALER_Amount amount; + const uint64_t *debit_account; + const uint64_t *credit_account; + const char *exchange_base_url; + + + if (NULL == bcs->deposit_reference) + { + TALER_LOG_INFO ("Deposit reference NOT given\n"); + debit_account = &bcs->debit_account; + credit_account = &bcs->credit_account; + exchange_base_url = bcs->exchange_base_url; + + if (GNUNET_OK != + TALER_string_to_amount (bcs->amount, + &amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u\n", + bcs->amount, + is->ip); + TALER_TESTING_interpreter_fail (is); + return; + } + } + + if (NULL != bcs->deposit_reference) + { + const struct TALER_TESTING_Command *deposit_cmd; + const struct TALER_Amount *amount_ptr; + + TALER_LOG_INFO ("`%s' uses reference (%s/%p)\n", + TALER_TESTING_interpreter_get_current_label + (is), + bcs->deposit_reference, + bcs->deposit_reference); + deposit_cmd = TALER_TESTING_interpreter_lookup_command + (is, bcs->deposit_reference); + + if (NULL == deposit_cmd) + TALER_TESTING_FAIL (is); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_get_trait_amount_obj + (deposit_cmd, 0, &amount_ptr)); + amount = *amount_ptr; + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT + (deposit_cmd, &debit_account)); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT + (deposit_cmd, &credit_account)); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_get_trait_url + (deposit_cmd, 0, &exchange_base_url)); // check 0 works! + + } + + if (GNUNET_OK != + TALER_FAKEBANK_check (is->fakebank, + &amount, + *debit_account, + *credit_account, + exchange_base_url, + &bcs->subject)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_interpreter_next (is); +} + +/** + * Free the state of a "bank check" CMD. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +check_bank_transfer_cleanup + (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct BankCheckState *bcs = cls; + + GNUNET_free_non_null (bcs->subject); + GNUNET_free (bcs); +} + +/** + * Offer internal data from a "bank check" CMD state. + * + * @param cls closure. + * @param ret[out] result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +check_bank_transfer_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct BankCheckState *bcs = cls; + struct TALER_WireTransferIdentifierRawP *wtid_ptr; + + if (GNUNET_OK != GNUNET_STRINGS_string_to_data + (bcs->subject, + strlen (bcs->subject), + &bcs->wtid, + sizeof (struct TALER_WireTransferIdentifierRawP))) + wtid_ptr = NULL; + else + wtid_ptr = &bcs->wtid; + + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_transfer_subject (0, bcs->subject), + TALER_TESTING_make_trait_wtid (0, wtid_ptr), + TALER_TESTING_make_trait_url (0, bcs->exchange_base_url), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + + +/** + * Make a "bank check" CMD. It checks whether a + * particular wire transfer has been made or not. + * + * @param label the command label. + * @param exchange_base_url base url of the exchange involved in + * the wire transfer. + * @param amount the amount expected to be transferred. + * @param debit_account the account that gave money. + * @param credit_account the account that received money. + * + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_check_bank_transfer + (const char *label, + const char *exchange_base_url, + const char *amount, + uint64_t debit_account, + uint64_t credit_account) +{ + struct BankCheckState *bcs; + + bcs = GNUNET_new (struct BankCheckState); + bcs->exchange_base_url = exchange_base_url; + bcs->amount = amount; + bcs->debit_account = debit_account; + bcs->credit_account = credit_account; + + bcs->deposit_reference = NULL; + + struct TALER_TESTING_Command cmd = { + .label = label, + .cls = bcs, + .run = &check_bank_transfer_run, + .cleanup = &check_bank_transfer_cleanup, + .traits = &check_bank_transfer_traits + }; + + return cmd; +} + +/** + * Cleanup the state, only defined to respect the API. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +check_bank_empty_cleanup + (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + return; +} + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +check_bank_empty_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + + if (GNUNET_OK != TALER_FAKEBANK_check_empty (is->fakebank)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_interpreter_next (is); +} + + +/** + * Some commands (notably "bank history") could randomly + * look for traits; this way makes sure we don't segfault. + */ +static int +check_bank_empty_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + return GNUNET_SYSERR; +} + + +/** + * Checks wheter all the wire transfers got "checked" + * by the "bank check" CMD. + * + * @param label command label. + * + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_check_bank_empty (const char *label) +{ + struct TALER_TESTING_Command cmd; + + cmd.label = label; + cmd.run = &check_bank_empty_run; + cmd.cleanup = &check_bank_empty_cleanup; + cmd.traits = &check_bank_empty_traits; + + return cmd; +} + + +/** + * Define a "bank check" CMD that takes the input + * data from another CMD that offers it. + * + * @param label command label. + * @param deposit_reference reference to a CMD that is + * able to provide the "check bank transfer" operation + * input data. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_check_bank_transfer_with_ref + (const char *label, + const char *deposit_reference) +{ + + struct BankCheckState *bcs; + struct TALER_TESTING_Command cmd; + + bcs = GNUNET_new (struct BankCheckState); + bcs->deposit_reference = deposit_reference; + + cmd.label = label; + cmd.cls = bcs; + cmd.run = &check_bank_transfer_run; + cmd.cleanup = &check_bank_transfer_cleanup; + cmd.traits = &check_bank_transfer_traits; + + return cmd; +} diff --git a/src/lib/testing_api_cmd_batch.c b/src/lib/testing_api_cmd_batch.c new file mode 100644 index 000000000..a56c959a0 --- /dev/null +++ b/src/lib/testing_api_cmd_batch.c @@ -0,0 +1,208 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange/testing_api_cmd_batch.c + * @brief Implement batch-execution of CMDs. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" + + +/** + * State for a "batch" CMD. + */ +struct BatchState +{ + /** + * CMDs batch. + */ + struct TALER_TESTING_Command *batch; + + /** + * Internal comand pointer. + */ + int batch_ip; +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command being executed. + * @param is the interpreter state. + */ +static void +batch_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct BatchState *bs = cls; + + bs->batch_ip++; + if (NULL != bs->batch[bs->batch_ip].label) + TALER_LOG_DEBUG ("Running batched command: %s\n", + bs->batch[bs->batch_ip].label); + + /* hit end command, leap to next top-level command. */ + if (NULL == bs->batch[bs->batch_ip].label) + { + TALER_LOG_INFO ("Exiting from batch: %s\n", + cmd->label); + TALER_TESTING_interpreter_next (is); + return; + } + + bs->batch[bs->batch_ip].run (bs->batch[bs->batch_ip].cls, + &bs->batch[bs->batch_ip], + is); +} + + +/** + * Cleanup the state from a "reserve status" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +batch_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct BatchState *bs = cls; + + for (unsigned int i=0; + NULL != bs->batch[i].label; + i++) + bs->batch[i].cleanup (bs->batch[i].cls, + &bs->batch[i]); + GNUNET_free_non_null (bs->batch); + GNUNET_free (bs); +} + + +/** + * Offer internal data from a "batch" CMD, to other commands. + * + * @param cls closure. + * @param ret[out] result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +batch_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ +#define CURRENT_CMD_INDEX 0 +#define BATCH_INDEX 1 + + struct BatchState *bs = cls; + + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_cmd + (CURRENT_CMD_INDEX, &bs->batch[bs->batch_ip]), + TALER_TESTING_make_trait_cmd + (BATCH_INDEX, bs->batch), + TALER_TESTING_trait_end () + }; + + /* Always return current command. */ + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + +/** + * Create a "batch" command. Such command takes a + * end_CMD-terminated array of CMDs and executed them. + * Once it hits the end CMD, it passes the control + * to the next top-level CMD, regardless of it being + * another batch or ordinary CMD. + * + * @param label the command label. + * @param batch array of CMDs to execute. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_batch (const char *label, + struct TALER_TESTING_Command *batch) +{ + struct BatchState *bs; + unsigned int i; + + bs = GNUNET_new (struct BatchState); + bs->batch_ip = -1; + + /* Get number of commands. */ + for (i=0;NULL != batch[i].label;i++) + /* noop */ + ; + + bs->batch = GNUNET_new_array (i + 1, + struct TALER_TESTING_Command); + memcpy (bs->batch, + batch, + sizeof (struct TALER_TESTING_Command) * i); + + struct TALER_TESTING_Command cmd = { + .cls = bs, + .label = label, + .run = &batch_run, + .cleanup = &batch_cleanup, + .traits = &batch_traits + }; + + return cmd; +} + + +/** + * Test if this command is a batch command. + * + * @return false if not, true if it is a batch command + */ +int +TALER_TESTING_cmd_is_batch (const struct TALER_TESTING_Command *cmd) +{ + return cmd->run == &batch_run; +} + + +/** + * Obtain what command the batch is at. + * + * @return cmd current batch command + */ +struct TALER_TESTING_Command * +TALER_TESTING_cmd_batch_get_current (const struct TALER_TESTING_Command *cmd) +{ + struct BatchState *bs = cmd->cls; + + return &bs->batch[bs->batch_ip]; +} diff --git a/src/lib/testing_api_cmd_check_keys.c b/src/lib/testing_api_cmd_check_keys.c new file mode 100644 index 000000000..d329f31a4 --- /dev/null +++ b/src/lib/testing_api_cmd_check_keys.c @@ -0,0 +1,165 @@ +/* + This file is part of TALER + (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_cmd_check_keys.c + * @brief Implementation of "check keys" test command. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" + + +/** + * State for a "check keys" CMD. + */ +struct CheckKeysState +{ + /** + * This number will instruct the CMD interpreter to + * make sure that /keys was downloaded `generation` times + * _before_ running the very CMD logic. + */ + unsigned int generation; + + /** + * How many denomination keys the exchange is + * supposed to have. + */ + unsigned int num_denom_keys; +}; + + +/** + * Run the "check keys" command. + * + * @param cls closure. + * @param cmd the command currently being executed. + * @param is the interpreter state. + */ +static void +check_keys_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct CheckKeysState *cks = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "cmd `%s', key generation: %d\n", + cmd->label, + is->key_generation); + + if (is->key_generation < cks->generation) + { + is->working = GNUNET_NO; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Triggering GET /keys, cmd `%s'\n", + cmd->label); + + /* Means re-download /keys. */ + GNUNET_break (0 == TALER_EXCHANGE_check_keys_current + (is->exchange, GNUNET_YES).abs_value_us); + return; + } + if (is->key_generation > cks->generation) + { + /* We got /keys too often, strange. Fatal. May theoretically + happen if somehow we were really unlucky and /keys expired + "naturally", but obviously with a sane configuration this + should also not be. */ + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + /* /keys was updated, let's check they were OK! */ + if (cks->num_denom_keys != is->keys->num_denom_keys) + { + /* Did not get the expected number of denomination keys! */ + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Got %u keys in step %s, expected %u\n", + is->keys->num_denom_keys, + cmd->label, + cks->num_denom_keys); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_interpreter_next (is); +} + + +/** + * Cleanup the state. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +check_keys_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct CheckKeysState *cks = cls; + + GNUNET_free (cks); +} + + +/** + * Make a "check keys" command. This type of command + * checks whether the number of denomination keys from + * @a exchange matches @a num_denom_keys. + * + * @param label command label + * @param generation when this command is run, exactly @a + * generation /keys downloads took place. If the number + * of downloads is less than @a generation, the logic will + * first make sure that @a generation downloads are done, + * and _then_ execute the rest of the command. + * @param num_denom_keys expected number of denomination keys. + * @param exchange connection handle to the exchange to test. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_check_keys + (const char *label, + unsigned int generation, + unsigned int num_denom_keys) +{ + struct CheckKeysState *cks; + + cks = GNUNET_new (struct CheckKeysState); + cks->generation = generation; + cks->num_denom_keys = num_denom_keys; + + struct TALER_TESTING_Command cmd = { + .cls = cks, + .label = label, + .run = &check_keys_run, + .cleanup = &check_keys_cleanup + }; + + return cmd; +} + +/* end of testing_api_cmd_check_keys.c */ diff --git a/src/lib/testing_api_cmd_deposit.c b/src/lib/testing_api_cmd_deposit.c new file mode 100644 index 000000000..6fa2310d3 --- /dev/null +++ b/src/lib/testing_api_cmd_deposit.c @@ -0,0 +1,576 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_cmd_deposit.c + * @brief command for testing /deposit. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" +#include "taler_signatures.h" +#include "backoff.h" + + +/** + * State for a "deposit" CMD. + */ +struct DepositState +{ + + /** + * Amount to deposit. + */ + const char *amount; + + /** + * Reference to any command that is able to provide a coin. + */ + const char *coin_reference; + + /** + * If @e coin_reference refers to an operation that generated + * an array of coins, this value determines which coin to pick. + */ + unsigned int coin_index; + + /** + * Wire details of who is depositing -- this would be merchant + * wire details in a normal scenario. + */ + json_t *wire_details; + + /** + * JSON string describing what a proposal is about. + */ + json_t *contract_terms; + + /** + * 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. This + * key will be used to sign the deposit request. + */ + struct TALER_MerchantPrivateKeyP merchant_priv; + + /** + * Deposit handle while operation is running. + */ + struct TALER_EXCHANGE_DepositHandle *dh; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Should we retry on (transient) failures? + */ + int do_retry; + + /** + * Set to #GNUNET_YES if the /deposit succeeded + * and we now can provide the resulting traits. + */ + int traits_ready; + + /** + * Signing key used by the exchange to sign the + * deposit confirmation. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + /** + * Signature from the exchange on the + * deposit confirmation. + */ + struct TALER_ExchangeSignatureP exchange_sig; +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +deposit_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #deposit_run. + * + * @param cls a `struct DepositState` + */ +static void +do_retry (void *cls) +{ + struct DepositState *ds = cls; + + ds->retry_task = NULL; + deposit_run (ds, + NULL, + ds->is); +} + + +/** + * Callback to analyze the /deposit response, just used to + * check if the response code is acceptable. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param exchange_sig signature provided by the exchange + * (NULL on errors) + * @param exchange_pub public key of the exchange, + * used for signing the response. + * @param obj raw response from the exchange. + */ +static void +deposit_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_ExchangeSignatureP *exchange_sig, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const json_t *obj) +{ + struct DepositState *ds = cls; + + ds->dh = NULL; + if (ds->expected_response_code != http_status) + { + if (GNUNET_YES == ds->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying deposit failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + ds->backoff = GNUNET_TIME_UNIT_ZERO; + else + ds->backoff = EXCHANGE_LIB_BACKOFF (ds->backoff); + ds->retry_task + = GNUNET_SCHEDULER_add_delayed (ds->backoff, + &do_retry, + ds); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s in %s:%u\n", + http_status, + ds->is->commands[ds->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (obj, stderr, 0); + TALER_TESTING_interpreter_fail (ds->is); + return; + } + if (MHD_HTTP_OK == http_status) + { + ds->traits_ready = GNUNET_YES; + ds->exchange_pub = *exchange_pub; + ds->exchange_sig = *exchange_sig; + } + TALER_TESTING_interpreter_next (ds->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +deposit_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct DepositState *ds = cls; + const struct TALER_TESTING_Command *coin_cmd; + struct TALER_TESTING_Command *this_cmd; + const struct TALER_CoinSpendPrivateKeyP *coin_priv; + struct TALER_CoinSpendPublicKeyP coin_pub; + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; + const struct TALER_DenominationSignature *denom_pub_sig; + 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 *merchant_priv; + struct TALER_MerchantPublicKeyP merchant_pub; + struct GNUNET_HashCode h_contract_terms; + struct TALER_Amount amount; + + ds->is = is; + this_cmd = &is->commands[is->ip]; + + GNUNET_assert (ds->coin_reference); + coin_cmd = TALER_TESTING_interpreter_lookup_command + (is, + ds->coin_reference); + if (NULL == coin_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + /* Fixme: do prefer "interpreter fail" over assertions, + * as the former takes care of shutting down processes too */ + GNUNET_assert (NULL != coin_cmd); + + GNUNET_assert (GNUNET_OK + == TALER_TESTING_get_trait_coin_priv (coin_cmd, + ds->coin_index, + &coin_priv)); + + GNUNET_assert (GNUNET_OK + == TALER_TESTING_get_trait_denom_pub (coin_cmd, + ds->coin_index, + &denom_pub)); + + GNUNET_assert (GNUNET_OK + == TALER_TESTING_get_trait_denom_sig (coin_cmd, + ds->coin_index, + &denom_pub_sig)); + if (GNUNET_OK != + TALER_string_to_amount (ds->amount, + &amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at '%u/%s'\n", + ds->amount, is->ip, this_cmd->label); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_assert (GNUNET_OK == + TALER_JSON_hash (ds->contract_terms, + &h_contract_terms)); + + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &coin_pub.eddsa_pub); + + merchant_priv = GNUNET_CRYPTO_eddsa_key_create (); + ds->merchant_priv.eddsa_priv = *merchant_priv; + GNUNET_free (merchant_priv); + + if (0 != ds->refund_deadline.rel_value_us) + { + refund_deadline = GNUNET_TIME_relative_to_absolute + (ds->refund_deadline); + wire_deadline = GNUNET_TIME_relative_to_absolute + (GNUNET_TIME_relative_multiply + (ds->refund_deadline, 2)); + } + else + { + refund_deadline = GNUNET_TIME_UNIT_ZERO_ABS; + wire_deadline = GNUNET_TIME_relative_to_absolute + (GNUNET_TIME_UNIT_ZERO); + } + GNUNET_CRYPTO_eddsa_key_get_public + (&ds->merchant_priv.eddsa_priv, + &merchant_pub.eddsa_pub); + + timestamp = GNUNET_TIME_absolute_get (); + GNUNET_TIME_round_abs (×tamp); + GNUNET_TIME_round_abs (&refund_deadline); + GNUNET_TIME_round_abs (&wire_deadline); + + { + struct TALER_DepositRequestPS dr; + + memset (&dr, 0, sizeof (dr)); + dr.purpose.size = htonl + (sizeof (struct TALER_DepositRequestPS)); + dr.purpose.purpose = htonl + (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); + dr.h_contract_terms = h_contract_terms; + GNUNET_assert + (GNUNET_OK == + TALER_JSON_merchant_wire_signature_hash (ds->wire_details, + &dr.h_wire)); + dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); + dr.refund_deadline = GNUNET_TIME_absolute_hton + (refund_deadline); + TALER_amount_hton (&dr.amount_with_fee, &amount); + TALER_amount_hton + (&dr.deposit_fee, &denom_pub->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)); + } + ds->dh = TALER_EXCHANGE_deposit + (is->exchange, + &amount, + wire_deadline, + ds->wire_details, + &h_contract_terms, + &coin_pub, + denom_pub_sig, + &denom_pub->key, + timestamp, + &merchant_pub, + refund_deadline, + &coin_sig, + &deposit_cb, + ds); + + if (NULL == ds->dh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + return; +} + + +/** + * Free the state of a "deposit" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command which is being cleaned up. + */ +static void +deposit_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct DepositState *ds = cls; + + if (NULL != ds->dh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + ds->is->ip, + cmd->label); + TALER_EXCHANGE_deposit_cancel (ds->dh); + ds->dh = NULL; + } + if (NULL != ds->retry_task) + { + GNUNET_SCHEDULER_cancel (ds->retry_task); + ds->retry_task = NULL; + } + json_decref (ds->wire_details); + json_decref (ds->contract_terms); + GNUNET_free (ds); +} + + +/** + * Offer internal data from a "deposit" CMD, to other commands. + * + * @param cls closure. + * @param ret[out] result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * + * @return #GNUNET_OK on success. + */ +static int +deposit_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct DepositState *ds = cls; + const struct TALER_TESTING_Command *coin_cmd; + /* Will point to coin cmd internals. */ + const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv; + + coin_cmd = TALER_TESTING_interpreter_lookup_command + (ds->is, + ds->coin_reference); + + if (NULL == coin_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ds->is); + return GNUNET_NO; + } + + if (GNUNET_OK != + TALER_TESTING_get_trait_coin_priv (coin_cmd, + ds->coin_index, + &coin_spent_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ds->is); + return GNUNET_NO; + } + + struct TALER_TESTING_Trait traits[] = { + /* First two traits are only available if + ds->traits is #GNUNET_YES */ + TALER_TESTING_make_trait_exchange_pub (0, + &ds->exchange_pub), + TALER_TESTING_make_trait_exchange_sig (0, + &ds->exchange_sig), + /* These traits are always available */ + TALER_TESTING_make_trait_coin_priv (0, + coin_spent_priv), + TALER_TESTING_make_trait_wire_details (0, + ds->wire_details), + TALER_TESTING_make_trait_contract_terms (0, + ds->contract_terms), + TALER_TESTING_make_trait_peer_key (0, + &ds->merchant_priv.eddsa_priv), + TALER_TESTING_make_trait_amount (0, + ds->amount), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait ((ds->traits_ready) + ? traits + : &traits[2], + ret, + trait, + index); +} + +/** + * Create a "deposit" command. + * + * @param label command label. + * @param coin_reference reference to any operation that can + * provide a coin. + * @param coin_index if @a withdraw_reference offers an array of + * coins, this parameter selects which one in that array. + * This value is currently ignored, as only one-coin + * withdrawals are implemented. + * @param wire_details wire details associated with the "deposit" + * request. + * @param contract_terms contract terms to be signed over by the + * coin. + * @param refund_deadline refund deadline, zero means 'no refunds'. + * Note, if time were absolute, then it would have come + * one day and disrupt tests meaning. + * @param amount how much is going to be deposited. + * @param expected_response_code expected HTTP response code. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_deposit + (const char *label, + const char *coin_reference, + unsigned int coin_index, + json_t *wire_details, + const char *contract_terms, + struct GNUNET_TIME_Relative refund_deadline, + const char *amount, + unsigned int expected_response_code) +{ + struct DepositState *ds; + + ds = GNUNET_new (struct DepositState); + ds->coin_reference = coin_reference; + ds->coin_index = coin_index; + ds->wire_details = wire_details; + ds->contract_terms = json_loads (contract_terms, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == ds->contract_terms) + { + GNUNET_log + (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse contract terms `%s' for CMD `%s'\n", + contract_terms, + label); + GNUNET_assert (0); + } + + ds->refund_deadline = refund_deadline; + ds->amount = amount; + ds->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .cls = ds, + .label = label, + .run = &deposit_run, + .cleanup = &deposit_cleanup, + .traits = &deposit_traits + }; + + return cmd; +} + + +/** + * Modify a deposit command to enable retries when we get transient + * errors from the exchange. + * + * @param cmd a deposit command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_deposit_with_retry (struct TALER_TESTING_Command cmd) +{ + struct DepositState *ds; + + GNUNET_assert (&deposit_run == cmd.run); + ds = cmd.cls; + ds->do_retry = GNUNET_YES; + return cmd; +} + + +/* end of testing_api_cmd_deposit.c */ diff --git a/src/lib/testing_api_cmd_exec_aggregator.c b/src/lib/testing_api_cmd_exec_aggregator.c new file mode 100644 index 000000000..c51d44989 --- /dev/null +++ b/src/lib/testing_api_cmd_exec_aggregator.c @@ -0,0 +1,166 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_cmd_exec_aggregator.c + * @brief run the taler-exchange-aggregator command + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "aggregator" CMD. + */ +struct AggregatorState +{ + + /** + * Aggregator process. + */ + struct GNUNET_OS_Process *aggregator_proc; + + /** + * Configuration file used by the aggregator. + */ + const char *config_filename; +}; + + +/** + * Run the command. Use the `taler-exchange-aggregator' program. + * + * @param cls closure. + * @param cmd command being run. + * @param is interpreter state. + */ +static void +aggregator_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct AggregatorState *as = cls; + + as->aggregator_proc + = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-aggregator", + "taler-exchange-aggregator", + "-c", as->config_filename, + "-t", /* exit when done */ + NULL); + if (NULL == as->aggregator_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "aggregator" CMD, and possibly kill its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +aggregator_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct AggregatorState *as = cls; + + if (NULL != as->aggregator_proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (as->aggregator_proc, + SIGKILL)); + GNUNET_OS_process_wait (as->aggregator_proc); + GNUNET_OS_process_destroy (as->aggregator_proc); + as->aggregator_proc = NULL; + } + GNUNET_free (as); +} + + +/** + * Offer "aggregator" CMD internal data to other commands. + * + * @param cls closure. + * @param ret[out] result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success + */ +static int +aggregator_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct AggregatorState *as = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_process (0, &as->aggregator_proc), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Make a "aggregator" CMD. + * + * @param label command label. + * @param config_filename configuration file for the + * aggregator to use. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_aggregator (const char *label, + const char *config_filename) +{ + struct AggregatorState *as; + + as = GNUNET_new (struct AggregatorState); + as->config_filename = config_filename; + + struct TALER_TESTING_Command cmd = { + .cls = as, + .label = label, + .run = &aggregator_run, + .cleanup = &aggregator_cleanup, + .traits = &aggregator_traits + }; + + return cmd; +} + +/* end of testing_api_cmd_exec_aggregator.c */ diff --git a/src/lib/testing_api_cmd_exec_auditor-sign.c b/src/lib/testing_api_cmd_exec_auditor-sign.c new file mode 100644 index 000000000..90a1654ce --- /dev/null +++ b/src/lib/testing_api_cmd_exec_auditor-sign.c @@ -0,0 +1,232 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_cmd_exec_auditor-sign.c + * @brief run the taler-exchange-aggregator command + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + + +/** + * State for a "auditor sign" CMD. + */ +struct AuditorSignState +{ + + /** + * Handle to the process making the signature. + */ + struct GNUNET_OS_Process *auditor_sign_proc; + + /** + * Configuration file used by the command. + */ + const char *config_filename; + + /** + * File name of signed blob. + */ + char *signed_keys_out; +}; + + +/** + * Run the command; calls the `taler-auditor-sign' program. + * + * @param cls closure. + * @param cmd the command. + * @param is interpreter state. + */ +static void +auditor_sign_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct AuditorSignState *ass = cls; + + struct GNUNET_CONFIGURATION_Handle *cfg; + char *test_home_dir; + char *exchange_master_pub; + struct GNUNET_TIME_Absolute now; + + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != GNUNET_CONFIGURATION_load + (cfg, ass->config_filename)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + "paths", + "TALER_TEST_HOME", + &test_home_dir)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "paths", + "TALER_TEST_HOME"); + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + now = GNUNET_TIME_absolute_get (); + GNUNET_asprintf + (&ass->signed_keys_out, + "%s/.local/share/taler/auditors/auditor-%llu.out", + test_home_dir, + (unsigned long long) now.abs_value_us); + GNUNET_free (test_home_dir); + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "exchange", + "MASTER_PUBLIC_KEY", + &exchange_master_pub)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "MASTER_PUBLIC_KEY"); + GNUNET_CONFIGURATION_destroy (cfg); + + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_CONFIGURATION_destroy (cfg); + + ass->auditor_sign_proc = GNUNET_OS_start_process + (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-auditor-sign", + "taler-auditor-sign", + "-c", ass->config_filename, + "-u", "http://auditor/", + "-m", exchange_master_pub, + "-r", "auditor.in", + "-o", ass->signed_keys_out, + NULL); + GNUNET_free (exchange_master_pub); + if (NULL == ass->auditor_sign_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "auditor sign" CMD, and possibly + * kill its process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +auditor_sign_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct AuditorSignState *ass = cls; + + if (NULL != ass->auditor_sign_proc) + { + GNUNET_break (0 == GNUNET_OS_process_kill + (ass->auditor_sign_proc, SIGKILL)); + GNUNET_OS_process_wait (ass->auditor_sign_proc); + GNUNET_OS_process_destroy (ass->auditor_sign_proc); + ass->auditor_sign_proc = NULL; + } + GNUNET_free_non_null (ass->signed_keys_out); + GNUNET_free (ass); +} + + +/** + * Offer "auditor sign" CMD internal data to other commands. + * + * @param cls closure. + * @param ret[out] result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +auditor_sign_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct AuditorSignState *ass = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_process (0, &ass->auditor_sign_proc), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Make a "auditor sign" CMD. + * + * @param label command label + * @param config_filename configuration filename + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_auditor_sign (const char *label, + const char *config_filename) +{ + struct AuditorSignState *ass; + + ass = GNUNET_new (struct AuditorSignState); + ass->config_filename = config_filename; + + + struct TALER_TESTING_Command cmd = { + .cls = ass, + .label = label, + .run = &auditor_sign_run, + .cleanup = &auditor_sign_cleanup, + .traits = &auditor_sign_traits + }; + + return cmd; +} + +/* end of testing_api_cmd_exec_auditor-sign.c */ diff --git a/src/lib/testing_api_cmd_exec_keyup.c b/src/lib/testing_api_cmd_exec_keyup.c new file mode 100644 index 000000000..576aab3c7 --- /dev/null +++ b/src/lib/testing_api_cmd_exec_keyup.c @@ -0,0 +1,169 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_cmd_exec_keyup.c + * @brief run the taler-exchange-keyup command + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "keyup" CMD. + */ +struct KeyupState +{ + + /** + * Process for the "keyup" command. + */ + struct GNUNET_OS_Process *keyup_proc; + + /** + * Configuration file used by the command. + */ + const char *config_filename; +}; + + +/** + * Run the command; calls the `taler-exchange-keyup' program. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +keyup_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct KeyupState *ks = cls; + + ks->keyup_proc = GNUNET_OS_start_process + (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-keyup", + "taler-exchange-keyup", + "-c", ks->config_filename, + "-o", "auditor.in", + NULL); + + if (NULL == ks->keyup_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "keyup" CMD, and possibly kills its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +keyup_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct KeyupState *ks = cls; + + if (NULL != ks->keyup_proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (ks->keyup_proc, + SIGKILL)); + GNUNET_OS_process_wait (ks->keyup_proc); + GNUNET_OS_process_destroy (ks->keyup_proc); + ks->keyup_proc = NULL; + } + GNUNET_free (ks); +} + + +/** + * Offer "keyup" CMD internal data to other commands. + * + * @param cls closure. + * @param ret[out] result + * @param trait name of the trait. + * @param index index number of the object to offer. + * + * @return #GNUNET_OK on success. + */ +static int +keyup_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct KeyupState *ks = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_process (0, &ks->keyup_proc), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Make the "keyup" CMD. + * + * @param label command label. + * @param config_filename configuration filename. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_keyup (const char *label, + const char *config_filename) +{ + struct KeyupState *ks; + + ks = GNUNET_new (struct KeyupState); + ks->config_filename = config_filename; + + struct TALER_TESTING_Command cmd = { + .cls = ks, + .label = label, + .run = &keyup_run, + .cleanup = &keyup_cleanup, + .traits = &keyup_traits + }; + + return cmd; +} + +/* end of testing_api_cmd_exec_keyup.c */ diff --git a/src/lib/testing_api_cmd_exec_wirewatch.c b/src/lib/testing_api_cmd_exec_wirewatch.c new file mode 100644 index 000000000..a81bf5c2b --- /dev/null +++ b/src/lib/testing_api_cmd_exec_wirewatch.c @@ -0,0 +1,168 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_cmd_exec_wirewatch.c + * @brief run the taler-exchange-wirewatch command + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + + +/** + * State for a "wirewatch" CMD. + */ +struct WirewatchState +{ + + /** + * Process for the wirewatcher. + */ + struct GNUNET_OS_Process *wirewatch_proc; + + /** + * Configuration file used by the wirewatcher. + */ + const char *config_filename; +}; + +/** + * Run the command; use the `taler-exchange-wirewatch' program. + * + * @param cls closure. + * @param cmd command currently being executed. + * @param is interpreter state. + */ +static void +wirewatch_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct WirewatchState *ws = cls; + + ws->wirewatch_proc + = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-wirewatch", + "taler-exchange-wirewatch", + "-c", ws->config_filename, + "-T", /* exit when done */ + NULL); + if (NULL == ws->wirewatch_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "wirewatch" CMD, and possibly + * kills its process if it did not terminate regularly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +wirewatch_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct WirewatchState *ws = cls; + + if (NULL != ws->wirewatch_proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (ws->wirewatch_proc, + SIGKILL)); + GNUNET_OS_process_wait (ws->wirewatch_proc); + GNUNET_OS_process_destroy (ws->wirewatch_proc); + ws->wirewatch_proc = NULL; + } + GNUNET_free (ws); +} + + +/** + * Offer "wirewatch" CMD internal data to other commands. + * + * @param cls closure. + * @param ret[out] result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +wirewatch_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct WirewatchState *ws = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_process (0, + &ws->wirewatch_proc), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Make a "wirewatch" CMD. + * + * @param label command label. + * @param config_filename configuration filename. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_wirewatch (const char *label, + const char *config_filename) +{ + struct WirewatchState *ws; + + ws = GNUNET_new (struct WirewatchState); + ws->config_filename = config_filename; + + + struct TALER_TESTING_Command cmd = { + .cls = ws, + .label = label, + .run = &wirewatch_run, + .cleanup = &wirewatch_cleanup, + .traits = &wirewatch_traits + }; + + return cmd; +} + +/* end of testing_api_cmd_exec_wirewatch.c */ diff --git a/src/lib/testing_api_cmd_fakebank_transfer.c b/src/lib/testing_api_cmd_fakebank_transfer.c new file mode 100644 index 000000000..43f72573b --- /dev/null +++ b/src/lib/testing_api_cmd_fakebank_transfer.c @@ -0,0 +1,756 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_cmd_fakebank_transfer.c + * @brief implementation of a fakebank wire transfer command + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" +#include "taler_testing_bank_lib.h" +#include "backoff.h" + +/** + * State for a "fakebank transfer" CMD. + */ +struct FakebankTransferState +{ + + /** + * Label of any command that can trait-offer a reserve priv. + */ + const char *reserve_reference; + + /** + * Wire transfer amount. + */ + struct TALER_Amount amount; + + /** + * Wire transfer subject. + */ + const char *subject; + + /** + * Base URL of the bank serving the request. + */ + const char *bank_url; + + /** + * Money sender account number. + */ + uint64_t debit_account_no; + + /** + * Money receiver account number. + */ + uint64_t credit_account_no; + + /** + * Username to use for authentication. + */ + const char *auth_username; + + /** + * Password to use for authentication. + */ + const char *auth_password; + + /** + * Set (by the interpreter) to the reserve's private key + * we used to make a wire transfer subject line with. + */ + struct TALER_ReservePrivateKeyP reserve_priv; + + /** + * Handle to the pending request at the fakebank. + */ + struct TALER_BANK_AdminAddIncomingHandle *aih; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Set to the wire transfer's unique ID. + */ + uint64_t serial_id; + + /** + * Exchange URL. This value is fed to the bank when requesting + * the wire transfer; note: the bank needs it because a merchant + * might want to know which exchange performed a wire transfer to + * them, just by looking at bank records. + */ + const char *exchange_url; + + /** + * Merchant instance. Sometimes used to get the tip reserve + * private key by reading the appropriate config section. + */ + const char *instance; + + /** + * Configuration filename. Used to get the tip reserve key + * filename (used to obtain a public key to write in the + * transfer subject). + */ + const char *config_filename; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Was this command modified via + * #TALER_TESTING_cmd_fakebank_transfer_with_retry to + * enable retries? + */ + int do_retry; +}; + + +/** + * Run the "fakebank transfer" CMD. + * + * @param cls closure. + * @param cmd CMD being run. + * @param is interpreter state. + */ +static void +fakebank_transfer_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #fakebank_transfer_run. + * + * @param cls a `struct FakebankTransferState` + */ +static void +do_retry (void *cls) +{ + struct FakebankTransferState *fts = cls; + + fts->retry_task = NULL; + fakebank_transfer_run (fts, + NULL, + fts->is); +} + + +/** + * This callback will process the fakebank response to the wire + * transfer. It just checks whether the HTTP response code is + * acceptable. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for + * successful status request; 0 if the exchange's reply is + * bogus (fails to follow the protocol) + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param serial_id unique ID of the wire transfer + * @param full_response full response from the exchange (for + * logging, in case of errors) + */ +static void +add_incoming_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint64_t serial_id, + const json_t *full_response) +{ + struct FakebankTransferState *fts = cls; + struct TALER_TESTING_Interpreter *is = fts->is; + + fts->aih = NULL; + fts->serial_id = serial_id; + if (MHD_HTTP_OK != http_status) + { + if (GNUNET_YES == fts->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying fakebank transfer failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + fts->backoff = GNUNET_TIME_UNIT_ZERO; + else + fts->backoff = EXCHANGE_LIB_BACKOFF (fts->backoff); + fts->retry_task = GNUNET_SCHEDULER_add_delayed (fts->backoff, + &do_retry, + fts); + return; + } + } + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Fakebank returned HTTP status %u/%d\n", + http_status, + (int) ec); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the "fakebank transfer" CMD. + * + * @param cls closure. + * @param cmd CMD being run. + * @param is interpreter state. + */ +static void +fakebank_transfer_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct FakebankTransferState *fts = cls; + char *subject; + struct TALER_BANK_AuthenticationData auth; + struct TALER_ReservePublicKeyP reserve_pub; + + if (NULL != fts->subject) + { + subject = GNUNET_strdup (fts->subject); + } + else + { + /* Use reserve public key as subject */ + if (NULL != fts->reserve_reference) + { + const struct TALER_TESTING_Command *ref; + const struct TALER_ReservePrivateKeyP *reserve_priv; + + ref = TALER_TESTING_interpreter_lookup_command + (is, fts->reserve_reference); + if (NULL == ref) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (GNUNET_OK != + TALER_TESTING_get_trait_reserve_priv (ref, + 0, + &reserve_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + fts->reserve_priv.eddsa_priv = reserve_priv->eddsa_priv; + } + else + { + if (NULL != fts->instance) + { + char *section; + char *keys; + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + struct GNUNET_CONFIGURATION_Handle *cfg; + + GNUNET_assert (NULL != fts->config_filename); + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_load (cfg, + fts->config_filename)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_asprintf (§ion, + "instance-%s", + fts->instance); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename + (cfg, + section, + "TIP_RESERVE_PRIV_FILENAME", + &keys)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Configuration fails to specify reserve" + " private key filename in section %s\n", + section); + GNUNET_free (section); + TALER_TESTING_interpreter_fail (is); + return; + } + priv = GNUNET_CRYPTO_eddsa_key_create_from_file (keys); + if (NULL == priv) + { + GNUNET_log_config_invalid + (GNUNET_ERROR_TYPE_ERROR, + section, + "TIP_RESERVE_PRIV_FILENAME", + "Failed to read private key"); + GNUNET_free (keys); + GNUNET_free (section); + TALER_TESTING_interpreter_fail (is); + return; + } + fts->reserve_priv.eddsa_priv = *priv; + GNUNET_free (priv); + GNUNET_CONFIGURATION_destroy (cfg); + } + else + { + /* No referenced reserve, no instance to take priv + * from, no explicit subject given: create new key! */ + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + + priv = GNUNET_CRYPTO_eddsa_key_create (); + fts->reserve_priv.eddsa_priv = *priv; + GNUNET_free (priv); + } + } + GNUNET_CRYPTO_eddsa_key_get_public + (&fts->reserve_priv.eddsa_priv, &reserve_pub.eddsa_pub); + subject = GNUNET_STRINGS_data_to_string_alloc + (&reserve_pub, sizeof (reserve_pub)); + } + + auth.method = TALER_BANK_AUTH_BASIC; + auth.details.basic.username = (char *) fts->auth_username; + auth.details.basic.password = (char *) fts->auth_password; + fts->is = is; + fts->aih = TALER_BANK_admin_add_incoming + (TALER_TESTING_interpreter_get_context (is), + fts->bank_url, + &auth, + fts->exchange_url, + subject, + &fts->amount, + fts->debit_account_no, + fts->credit_account_no, + &add_incoming_cb, + fts); + GNUNET_free (subject); + if (NULL == fts->aih) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } +} + + +/** + * Free the state of a "fakebank transfer" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure + * @param cmd current CMD being cleaned up. + */ +static void +fakebank_transfer_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct FakebankTransferState *fts = cls; + + if (NULL != fts->aih) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %s did not complete\n", + cmd->label); + TALER_BANK_admin_add_incoming_cancel (fts->aih); + } + if (NULL != fts->retry_task) + { + GNUNET_SCHEDULER_cancel (fts->retry_task); + fts->retry_task = NULL; + } + GNUNET_free (fts); +} + +/** + * Offer internal data from a "fakebank transfer" CMD to other + * commands. + * + * @param cls closure. + * @param ret[out] result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +fakebank_transfer_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct FakebankTransferState *fts = cls; + #define MANDATORY 6 + struct TALER_TESTING_Trait traits[MANDATORY + 1] = { + TALER_TESTING_MAKE_TRAIT_DEBIT_ACCOUNT + (&fts->debit_account_no), + TALER_TESTING_MAKE_TRAIT_CREDIT_ACCOUNT + (&fts->credit_account_no), + TALER_TESTING_make_trait_url (0, fts->exchange_url), + TALER_TESTING_MAKE_TRAIT_ROW_ID (&fts->serial_id), + TALER_TESTING_make_trait_amount_obj (0, &fts->amount), + }; + + /** + * The user gave explicit subject, + * there must be NO reserve priv. */ + if (NULL != fts->subject) + traits[MANDATORY - 1] = + TALER_TESTING_make_trait_transfer_subject (0, + fts->subject); + /* A reserve priv must exist if no subject was given. */ + else + traits[MANDATORY - 1] = TALER_TESTING_make_trait_reserve_priv + (0, &fts->reserve_priv), + + traits[MANDATORY] = TALER_TESTING_trait_end (); + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Create fakebank_transfer command, the subject line will be + * derived from a randomly created reserve priv. Note that that + * reserve priv will then be offered as trait. + * + * @param label command label. + * @param amount amount to transfer. + * @param bank_url base URL of the bank that implements this + * wire transer. For simplicity, both credit and debit + * bank account exist at the same bank. + * @param debit_account_no which account (expressed as a number) + * gives money. + * @param credit_account_no which account (expressed as a number) + * receives money. + * @param auth_username username identifying the @a + * debit_account_no at the bank. + * @param auth_password password for @a auth_username. + * @param exchange_url which exchange is involved in this transfer. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_fakebank_transfer + (const char *label, + const char *amount, + const char *bank_url, + uint64_t debit_account_no, + uint64_t credit_account_no, + const char *auth_username, + const char *auth_password, + const char *exchange_url) +{ + struct FakebankTransferState *fts; + + fts = GNUNET_new (struct FakebankTransferState); + fts->bank_url = bank_url; + fts->credit_account_no = credit_account_no; + fts->debit_account_no = debit_account_no; + fts->auth_username = auth_username; + fts->auth_password = auth_password; + fts->exchange_url = exchange_url; + if (GNUNET_OK != + TALER_string_to_amount (amount, + &fts->amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + amount, + label); + GNUNET_assert (0); + } + + struct TALER_TESTING_Command cmd = { + .cls = fts, + .label = label, + .run = &fakebank_transfer_run, + .cleanup = &fakebank_transfer_cleanup, + .traits = &fakebank_transfer_traits + }; + + return cmd; +} + + +/** + * Create "fakebank transfer" CMD, letting the caller specifying + * the subject line. + * + * @param label command label. + * @param amount amount to transfer. + * @param bank_url base URL of the bank that implements this + * wire transer. For simplicity, both credit and debit + * bank account exist at the same bank. + * @param debit_account_no which account (expressed as a number) + * gives money. + * @param credit_account_no which account (expressed as a number) + * receives money. + * + * @param auth_username username identifying the @a + * debit_account_no at the bank. + * @param auth_password password for @a auth_username. + * @param subject wire transfer's subject line. + * @param exchange_url which exchange is involved in this transfer. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_fakebank_transfer_with_subject + (const char *label, + const char *amount, + const char *bank_url, + uint64_t debit_account_no, + uint64_t credit_account_no, + const char *auth_username, + const char *auth_password, + const char *subject, + const char *exchange_url) +{ + struct FakebankTransferState *fts; + + fts = GNUNET_new (struct FakebankTransferState); + + TALER_LOG_DEBUG ("%s:FTS@%p\n", + label, + fts); + + fts->bank_url = bank_url; + fts->credit_account_no = credit_account_no; + fts->debit_account_no = debit_account_no; + fts->auth_username = auth_username; + fts->auth_password = auth_password; + fts->subject = subject; + fts->exchange_url = exchange_url; + if (GNUNET_OK != + TALER_string_to_amount (amount, + &fts->amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + amount, + label); + GNUNET_assert (0); + } + + struct TALER_TESTING_Command cmd = { + .cls = fts, + .label = label, + .run = &fakebank_transfer_run, + .cleanup = &fakebank_transfer_cleanup, + .traits = &fakebank_transfer_traits + }; + + return cmd; +} + + +/** + * Create "fakebank transfer" CMD, letting the caller specify + * a reference to a command that can offer a reserve private key. + * This private key will then be used to construct the subject line + * of the wire transfer. + * + * @param label command label. + * @param amount the amount to transfer. + * @param bank_url base URL of the bank running the transfer. + * @param debit_account_no which account (expressed as a number) + * gives money. + * @param credit_account_no which account (expressed as a number) + * receives money. + * @param auth_username username identifying the @a + * debit_account_no at the bank. + * @param auth_password password for @a auth_username. + * @param ref reference to a command that can offer a reserve + * private key. + * @param exchange_url the exchage involved in the transfer, + * tipically receiving the money in order to fuel a reserve. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_fakebank_transfer_with_ref + (const char *label, + const char *amount, + const char *bank_url, + uint64_t debit_account_no, + uint64_t credit_account_no, + const char *auth_username, + const char *auth_password, + const char *ref, + const char *exchange_url) +{ + struct FakebankTransferState *fts; + + fts = GNUNET_new (struct FakebankTransferState); + fts->bank_url = bank_url; + fts->credit_account_no = credit_account_no; + fts->debit_account_no = debit_account_no; + fts->auth_username = auth_username; + fts->auth_password = auth_password; + fts->reserve_reference = ref; + fts->exchange_url = exchange_url; + if (GNUNET_OK != + TALER_string_to_amount (amount, + &fts->amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + amount, + label); + GNUNET_assert (0); + } + + struct TALER_TESTING_Command cmd = { + .cls = fts, + .label = label, + .run = &fakebank_transfer_run, + .cleanup = &fakebank_transfer_cleanup, + .traits = &fakebank_transfer_traits + }; + + return cmd; +} + + +/** + * Create "fakebank transfer" CMD, letting the caller specifying + * the merchant instance. This version is useful when a tip + * reserve should be topped up, in fact the interpreter will need + * the "tipping instance" in order to get the instance public key + * and make a wire transfer subject out of it. + * + * @param label command label. + * @param amount amount to transfer. + * @param bank_url base URL of the bank that implements this + * wire transer. For simplicity, both credit and debit + * bank account exist at the same bank. + * @param debit_account_no which account (expressed as a number) + * gives money. + * @param credit_account_no which account (expressed as a number) + * receives money. + * + * @param auth_username username identifying the @a + * debit_account_no at the bank. + * @param auth_password password for @a auth_username. + * @param instance the instance that runs the tipping. Under this + * instance, the configuration file will provide the private + * key of the tipping reserve. This data will then used to + * construct the wire transfer subject line. + * @param exchange_url which exchange is involved in this transfer. + * @param config_filename configuration file to use. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_fakebank_transfer_with_instance + (const char *label, + const char *amount, + const char *bank_url, + uint64_t debit_account_no, + uint64_t credit_account_no, + const char *auth_username, + const char *auth_password, + const char *instance, + const char *exchange_url, + const char *config_filename) +{ + struct FakebankTransferState *fts; + + fts = GNUNET_new (struct FakebankTransferState); + fts->bank_url = bank_url; + fts->credit_account_no = credit_account_no; + fts->debit_account_no = debit_account_no; + fts->auth_username = auth_username; + fts->auth_password = auth_password; + fts->instance = instance; + fts->exchange_url = exchange_url; + fts->config_filename = config_filename; + if (GNUNET_OK != + TALER_string_to_amount (amount, + &fts->amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + amount, + label); + GNUNET_assert (0); + } + + struct TALER_TESTING_Command cmd = { + .cls = fts, + .label = label, + .run = &fakebank_transfer_run, + .cleanup = &fakebank_transfer_cleanup, + .traits = &fakebank_transfer_traits + }; + + return cmd; +} + + +/** + * Modify a fakebank transfer command to enable retries when the + * reserve is not yet full or we get other transient errors from the + * fakebank. + * + * @param cmd a fakebank transfer command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_fakebank_transfer_retry (struct TALER_TESTING_Command cmd) +{ + struct FakebankTransferState *fts; + + GNUNET_assert (&fakebank_transfer_run == cmd.run); + fts = cmd.cls; + fts->do_retry = GNUNET_YES; + return cmd; +} + +/* end of testing_api_cmd_fakebank_transfer.c */ diff --git a/src/lib/testing_api_cmd_payback.c b/src/lib/testing_api_cmd_payback.c new file mode 100644 index 000000000..a4a3aeff1 --- /dev/null +++ b/src/lib/testing_api_cmd_payback.c @@ -0,0 +1,498 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange/testing_api_cmd_payback.c + * @brief Implement the /revoke and /payback test commands. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" + + +/** + * State for a "revoke" CMD. + */ +struct RevokeState +{ + /** + * Expected HTTP status code. + */ + unsigned int expected_response_code; + + /** + * Command that offers a denomination to revoke. + */ + const char *coin_reference; + + /** + * The interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * The revoke process handle. + */ + struct GNUNET_OS_Process *revoke_proc; + + /** + * Configuration file name. + */ + const char *config_filename; + + /** + * Encoding of the denomination (to revoke) public key hash. + */ + char *dhks; + +}; + + +/** + * State for a "pay back" CMD. + */ +struct PaybackState +{ + /** + * Expected HTTP status code. + */ + unsigned int expected_response_code; + + /** + * Command that offers a reserve private key, + * plus a coin to be paid back. + */ + const char *coin_reference; + + /** + * The interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Amount expected to be paid back. + */ + const char *amount; + + /** + * Handle to the ongoing operation. + */ + struct TALER_EXCHANGE_PaybackHandle *ph; +}; + +/** + * Check the result of the payback request: checks whether + * the HTTP response code is good, and that the coin that + * was paid back belonged to the right reserve. + * + * @param cls closure + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param amount amount the exchange will wire back for this coin. + * @param timestamp what time did the exchange receive the + * /payback request + * @param reserve_pub public key of the reserve affected by the + * payback. + * @param full_response raw response from the exchange. + */ +static void +payback_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Absolute timestamp, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *full_response) +{ + + struct PaybackState *ps = cls; + struct TALER_TESTING_Interpreter *is = ps->is; + struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + const struct TALER_TESTING_Command *reserve_cmd; + const struct TALER_ReservePrivateKeyP *reserve_priv; + struct TALER_ReservePublicKeyP rp; + struct TALER_Amount expected_amount; + + ps->ph = NULL; + if (ps->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s in %s:%u\n", + http_status, + cmd->label, + __FILE__, + __LINE__); + json_dumpf (full_response, stderr, 0); + fprintf (stderr, "\n"); + TALER_TESTING_interpreter_fail (is); + return; + } + + reserve_cmd = TALER_TESTING_interpreter_lookup_command + (is, ps->coin_reference); + + if (NULL == reserve_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_reserve_priv + (reserve_cmd, 0, &reserve_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &rp.eddsa_pub); + + switch (http_status) + { + case MHD_HTTP_OK: + if (GNUNET_OK != TALER_string_to_amount + (ps->amount, &expected_amount)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (0 != TALER_amount_cmp (amount, &expected_amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Total amount missmatch to command %s\n", + cmd->label); + json_dumpf (full_response, stderr, 0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (0 != memcmp (reserve_pub, &rp, + sizeof (struct TALER_ReservePublicKeyP))) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unmanaged HTTP status code.\n"); + break; + } + TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +payback_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct PaybackState *ps = cls; + const struct TALER_TESTING_Command *coin_cmd; + const struct TALER_CoinSpendPrivateKeyP *coin_priv; + const struct TALER_DenominationBlindingKeyP *blinding_key; + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; + const struct TALER_DenominationSignature *coin_sig; + struct TALER_PlanchetSecretsP planchet; + + ps->is = is; + coin_cmd = TALER_TESTING_interpreter_lookup_command + (is, ps->coin_reference); + + if (NULL == coin_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv + (coin_cmd, 0, &coin_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_blinding_key + (coin_cmd, 0, &blinding_key)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + planchet.coin_priv = *coin_priv; + planchet.blinding_key = *blinding_key; + + if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub + (coin_cmd, 0, &denom_pub)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_denom_sig + (coin_cmd, 0, &coin_sig)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Trying to get '%s..' paid back\n", + TALER_B2S (&denom_pub->h_key)); + + ps->ph = TALER_EXCHANGE_payback (is->exchange, + denom_pub, + coin_sig, + &planchet, + payback_cb, + ps); + GNUNET_assert (NULL != ps->ph); +} + + +/** + * Cleanup the state. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command which is being cleaned up. + */ +static void +revoke_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + + struct RevokeState *rs = cls; + + if (NULL != rs->revoke_proc) + { + GNUNET_break (0 == GNUNET_OS_process_kill + (rs->revoke_proc, SIGKILL)); + GNUNET_OS_process_wait (rs->revoke_proc); + GNUNET_OS_process_destroy (rs->revoke_proc); + rs->revoke_proc = NULL; + } + + GNUNET_free_non_null (rs->dhks); + GNUNET_free (rs); +} + + +/** + * Cleanup the "payback" CMD state, and possibly cancel + * a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +payback_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct PaybackState *ps = cls; + if (NULL != ps->ph) + { + TALER_EXCHANGE_payback_cancel (ps->ph); + ps->ph = NULL; + } + GNUNET_free (ps); +} + + +/** + * Offer internal data from a "revoke" CMD to other CMDs. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param index index number of the object to offer. + * @return #GNUNET_OK on success + */ +static int +revoke_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + + struct RevokeState *rs = cls; + struct TALER_TESTING_Trait traits[] = { + /* Needed by the handler which waits the proc' + * death and calls the next command */ + TALER_TESTING_make_trait_process (0, &rs->revoke_proc), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + +/** + * Run the "revoke" command. The core of the function + * is to call the "keyup" utility passing it the base32 + * encoding of the denomination to revoke. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +revoke_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct RevokeState *rs = cls; + const struct TALER_TESTING_Command *coin_cmd; + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; + + rs->is = is; + /* Get denom pub from trait */ + coin_cmd = TALER_TESTING_interpreter_lookup_command + (is, rs->coin_reference); + + if (NULL == coin_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_denom_pub + (coin_cmd, 0, &denom_pub)); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Trying to revoke denom '%s..'\n", + TALER_B2S (&denom_pub->h_key)); + + rs->dhks = GNUNET_STRINGS_data_to_string_alloc + (&denom_pub->h_key, sizeof (struct GNUNET_HashCode)); + + rs->revoke_proc = GNUNET_OS_start_process + (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-keyup", + "taler-exchange-keyup", + "-c", rs->config_filename, + "-r", rs->dhks, + NULL); + + if (NULL == rs->revoke_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Revoke is ongoing..\n"); + + is->reload_keys = GNUNET_OK; + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Make a "payback" command. + * + * @param label the command label + * @param expected_response_code expected HTTP status code + * @param coin_reference reference to any command which + * offers a coin & reserve private key. + * @param amount denomination to pay back. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_payback (const char *label, + unsigned int expected_response_code, + const char *coin_reference, + const char *amount) +{ + struct PaybackState *ps; + + ps = GNUNET_new (struct PaybackState); + ps->expected_response_code = expected_response_code; + ps->coin_reference = coin_reference; + ps->amount = amount; + + struct TALER_TESTING_Command cmd = { + .cls = ps, + .label = label, + .run = &payback_run, + .cleanup = &payback_cleanup + }; + + return cmd; +} + + +/** + * Make a "revoke" command. + * + * @param label the command label. + * @param expected_response_code expected HTTP status code. + * @param coin_reference reference to a CMD that will offer the + * denomination to revoke. + * @param config_filename configuration file name. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_revoke (const char *label, + unsigned int expected_response_code, + const char *coin_reference, + const char *config_filename) +{ + + struct RevokeState *rs; + + rs = GNUNET_new (struct RevokeState); + rs->expected_response_code = expected_response_code; + rs->coin_reference = coin_reference; + rs->config_filename = config_filename; + + struct TALER_TESTING_Command cmd = { + .cls = rs, + .label = label, + .run = &revoke_run, + .cleanup = &revoke_cleanup, + .traits = &revoke_traits + }; + + return cmd; +} diff --git a/src/lib/testing_api_cmd_refresh.c b/src/lib/testing_api_cmd_refresh.c new file mode 100644 index 000000000..55900cf27 --- /dev/null +++ b/src/lib/testing_api_cmd_refresh.c @@ -0,0 +1,1317 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_cmd_refresh.c + * @brief commands for testing all "refresh" features. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" +#include "taler_signatures.h" +#include "backoff.h" + +/** + * Data for a coin to be melted. + */ +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_reference; +}; + + +/** + * State for a "refresh melt" command. + */ +struct RefreshMeltState +{ + + /** + * Information about coins to be melted. + */ + struct MeltDetails melted_coin; + + /** + * "Crypto data" used in the refresh operation. + */ + char *refresh_data; + + /** + * Reference to a previous melt command. + */ + const char *melt_reference; + + /** + * Melt handle while operation is running. + */ + struct TALER_EXCHANGE_RefreshMeltHandle *rmh; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Array of the denomination public keys + * corresponding to the @e fresh_amounts. + */ + struct TALER_EXCHANGE_DenomPublicKey *fresh_pks; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Number of bytes in @e refresh_data. + */ + size_t refresh_data_length; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * if set to #GNUNET_YES, then two /refresh/melt operations + * will be performed. This is needed to trigger the logic + * that manages those already-made requests. Note: it + * is not possible to just copy-and-paste a test refresh melt + * CMD to have the same effect, because every data preparation + * generates new planchets that (in turn) make the whole "hash" + * different from any previous one, therefore NOT allowing the + * exchange to pick any previous /rerfesh/melt operation from + * the database. + */ + unsigned int double_melt; + + /** + * Should we retry on (transient) failures? + */ + int do_retry; + + /** + * Set by the melt callback as it comes from the exchange. + */ + uint16_t noreveal_index; +}; + + +/** + * State for a "refresh reveal" CMD. + */ +struct RefreshRevealState +{ + /** + * Link to a "refresh melt" command. + */ + const char *melt_reference; + + /** + * Reveal handle while operation is running. + */ + struct TALER_EXCHANGE_RefreshRevealHandle *rrh; + + /** + * Convenience struct to keep in one place all the + * data related to one fresh coin, set by the reveal callback + * as it comes from the exchange. + */ + struct FreshCoin *fresh_coins; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Number of fresh coins withdrawn, set by the + * reveal callback as it comes from the exchange, + * it is the length of the @e fresh_coins array. + */ + unsigned int num_fresh_coins; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Should we retry on (transient) failures? + */ + int do_retry; + +}; + + +/** + * State for a "refresh link" CMD. + */ +struct RefreshLinkState +{ + /** + * Link to a "refresh reveal" command. + */ + const char *reveal_reference; + + /** + * Handle to the ongoing operation. + */ + struct TALER_EXCHANGE_RefreshLinkHandle *rlh; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Should we retry on (transient) failures? + */ + int do_retry; + +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_reveal_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #refresh_reveal_run. + * + * @param cls a `struct RefreshRevealState` + */ +static void +do_reveal_retry (void *cls) +{ + struct RefreshRevealState *rrs = cls; + + rrs->retry_task = NULL; + refresh_reveal_run (rrs, + NULL, + rrs->is); +} + + +/** + * "refresh reveal" request callback; it checks that the response + * code is expected and copies into its command's state the data + * coming from the exchange, namely the fresh coins. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @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 raw exchange response. + */ +static void +reveal_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + unsigned int num_coins, + const struct TALER_CoinSpendPrivateKeyP *coin_privs, + const struct TALER_DenominationSignature *sigs, + const json_t *full_response) +{ + struct RefreshRevealState *rrs = cls; + const struct TALER_TESTING_Command *melt_cmd; + + rrs->rrh = NULL; + if (rrs->expected_response_code != http_status) + { + if (GNUNET_YES == rrs->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying refresh reveal failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + rrs->backoff = GNUNET_TIME_UNIT_ZERO; + else + rrs->backoff = EXCHANGE_LIB_BACKOFF (rrs->backoff); + rrs->retry_task = GNUNET_SCHEDULER_add_delayed (rrs->backoff, + &do_reveal_retry, + rrs); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d to command %s in %s:%u\n", + http_status, + (int) ec, + rrs->is->commands[rrs->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (full_response, stderr, 0); + TALER_TESTING_interpreter_fail (rrs->is); + return; + } + melt_cmd = TALER_TESTING_interpreter_lookup_command + (rrs->is, rrs->melt_reference); + if (NULL == melt_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rrs->is); + return; + } + rrs->num_fresh_coins = num_coins; + switch (http_status) + { + case MHD_HTTP_OK: + rrs->fresh_coins = GNUNET_new_array + (num_coins, struct FreshCoin); + + const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks; + + if (GNUNET_OK != + TALER_TESTING_get_trait_denom_pub (melt_cmd, + 0, + &fresh_pks)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rrs->is); + return; + } + + for (unsigned int i=0; i<num_coins; i++) + { + struct FreshCoin *fc = &rrs->fresh_coins[i]; + + fc->pk = &fresh_pks[i]; + fc->coin_priv = coin_privs[i]; + fc->sig.rsa_signature = GNUNET_CRYPTO_rsa_signature_dup + (sigs[i].rsa_signature); + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unknown HTTP status %d\n", + http_status); + } + TALER_TESTING_interpreter_next (rrs->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_reveal_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct RefreshRevealState *rrs = cls; + struct RefreshMeltState *rms; + const struct TALER_TESTING_Command *melt_cmd; + + rrs->is = is; + melt_cmd = TALER_TESTING_interpreter_lookup_command + (is, rrs->melt_reference); + + if (NULL == melt_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rrs->is); + return; + } + rms = melt_cmd->cls; + rrs->rrh = TALER_EXCHANGE_refresh_reveal + (is->exchange, + rms->refresh_data_length, + rms->refresh_data, + rms->noreveal_index, + &reveal_cb, rrs); + + if (NULL == rrs->rrh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } +} + + +/** + * Free the state from a "refresh reveal" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +refresh_reveal_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct RefreshRevealState *rrs = cls; + + if (NULL != rrs->rrh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + rrs->is->ip, + cmd->label); + + TALER_EXCHANGE_refresh_reveal_cancel (rrs->rrh); + rrs->rrh = NULL; + } + if (NULL != rrs->retry_task) + { + GNUNET_SCHEDULER_cancel (rrs->retry_task); + rrs->retry_task = NULL; + } + + for (unsigned int j=0; j < rrs->num_fresh_coins; j++) + GNUNET_CRYPTO_rsa_signature_free (rrs->fresh_coins[j].sig.rsa_signature); + + GNUNET_free_non_null (rrs->fresh_coins); + rrs->fresh_coins = NULL; + rrs->num_fresh_coins = 0; + GNUNET_free (rrs); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_link_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #refresh_link_run. + * + * @param cls a `struct RefreshLinkState` + */ +static void +do_link_retry (void *cls) +{ + struct RefreshLinkState *rls = cls; + + rls->retry_task = NULL; + refresh_link_run (rls, + NULL, + rls->is); +} + + +/** + * "refresh link" operation callback, checks that HTTP response + * code is expected _and_ that all the linked coins were actually + * withdrawn by the "refresh reveal" CMD. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code + * @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 raw response from the exchange. + */ +static void +link_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + unsigned int num_coins, + const struct TALER_CoinSpendPrivateKeyP *coin_privs, + const struct TALER_DenominationSignature *sigs, + const struct TALER_DenominationPublicKey *pubs, + const json_t *full_response) +{ + + struct RefreshLinkState *rls = cls; + const struct TALER_TESTING_Command *reveal_cmd; + struct TALER_TESTING_Command *link_cmd + = &rls->is->commands[rls->is->ip]; + unsigned int found; + const unsigned int *num_fresh_coins; + + rls->rlh = NULL; + if (rls->expected_response_code != http_status) + { + if (GNUNET_YES == rls->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying refresh link failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + rls->backoff = GNUNET_TIME_UNIT_ZERO; + else + rls->backoff = EXCHANGE_LIB_BACKOFF (rls->backoff); + rls->retry_task = GNUNET_SCHEDULER_add_delayed (rls->backoff, + &do_link_retry, + rls); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d to command %s in %s:%u\n", + http_status, + (int) ec, + link_cmd->label, + __FILE__, + __LINE__); + json_dumpf (full_response, stderr, 0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + reveal_cmd = TALER_TESTING_interpreter_lookup_command + (rls->is, rls->reveal_reference); + + if (NULL == reveal_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + + switch (http_status) + { + case MHD_HTTP_OK: + /* check that number of coins returned matches */ + if (GNUNET_OK != TALER_TESTING_get_trait_uint + (reveal_cmd, 0, &num_fresh_coins)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + if (num_coins != *num_fresh_coins) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected number of fresh coins: %d vs %d in %s:%u\n", + num_coins, + *num_fresh_coins, + __FILE__, + __LINE__); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + /* check that the coins match */ + for (unsigned int i=0;i<num_coins;i++) + for (unsigned int j=i+1;j<num_coins;j++) + if (0 == memcmp + (&coin_privs[i], &coin_privs[j], + sizeof (struct TALER_CoinSpendPrivateKeyP))) + GNUNET_break (0); + /* Note: coins might be legitimately permutated in here... */ + found = 0; + + /* Will point to the pointer inside the cmd state. */ + const struct FreshCoin *fc = NULL; + + if (GNUNET_OK != TALER_TESTING_get_trait_fresh_coins + (reveal_cmd, 0, &fc)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + + for (unsigned int i=0;i<num_coins;i++) + for (unsigned int j=0;j<num_coins;j++) + { + if ( (0 == memcmp + (&coin_privs[i], &fc[j].coin_priv, + sizeof (struct TALER_CoinSpendPrivateKeyP))) && + (0 == GNUNET_CRYPTO_rsa_signature_cmp + (fc[i].sig.rsa_signature, + sigs[j].rsa_signature)) && + (0 == GNUNET_CRYPTO_rsa_public_key_cmp + (fc[i].pk->key.rsa_public_key, + pubs[j].rsa_public_key)) ) + { + found++; + break; + } + } + if (found != num_coins) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Only %u/%u coins match expectations\n", + found, num_coins); + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown HTTP response code %u.\n", + http_status); + } + TALER_TESTING_interpreter_next (rls->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_link_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct RefreshLinkState *rls = cls; + struct RefreshRevealState *rrs; + struct RefreshMeltState *rms; + const struct TALER_TESTING_Command *reveal_cmd; + const struct TALER_TESTING_Command *melt_cmd; + const struct TALER_TESTING_Command *coin_cmd; + rls->is = is; + + reveal_cmd = TALER_TESTING_interpreter_lookup_command + (rls->is, rls->reveal_reference); + + if (NULL == reveal_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + rrs = reveal_cmd->cls; + melt_cmd = TALER_TESTING_interpreter_lookup_command + (rls->is, rrs->melt_reference); + + if (NULL == melt_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + + /* find reserve_withdraw command */ + { + const struct MeltDetails *md; + + rms = melt_cmd->cls; + md = &rms->melted_coin; + coin_cmd = TALER_TESTING_interpreter_lookup_command + (rls->is, md->coin_reference); + if (NULL == coin_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + } + + const struct TALER_CoinSpendPrivateKeyP *coin_priv; + if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv + (coin_cmd, 0, &coin_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + + /* finally, use private key from withdraw sign command */ + rls->rlh = TALER_EXCHANGE_refresh_link + (is->exchange, coin_priv, &link_cb, rls); + + if (NULL == rls->rlh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } +} + + +/** + * Free the state of the "refresh link" CMD, and possibly + * cancel a operation thereof. + * + * @param cls closure + * @param cmd the command which is being cleaned up. + */ +static void +refresh_link_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct RefreshLinkState *rls = cls; + + if (NULL != rls->rlh) + { + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + rls->is->ip, + cmd->label); + TALER_EXCHANGE_refresh_link_cancel (rls->rlh); + rls->rlh = NULL; + } + if (NULL != rls->retry_task) + { + GNUNET_SCHEDULER_cancel (rls->retry_task); + rls->retry_task = NULL; + } + GNUNET_free (rls); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_melt_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #refresh_melt_run. + * + * @param cls a `struct RefreshMeltState` + */ +static void +do_melt_retry (void *cls) +{ + struct RefreshMeltState *rms = cls; + + rms->retry_task = NULL; + refresh_melt_run (rms, + NULL, + rms->is); +} + + +/** + * Callback for a "refresh melt" operation; checks if the HTTP + * response code is okay and re-run the melt operation if the + * CMD was set to do so. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param noreveal_index choice by the exchange in the + * cut-and-choose protocol, UINT16_MAX on error. + * @param exchange_pub public key the exchange used for signing. + * @param full_response raw response body from the exchange. + */ +static void +melt_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint32_t noreveal_index, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const json_t *full_response) +{ + struct RefreshMeltState *rms = cls; + + rms->rmh = NULL; + if (rms->expected_response_code != http_status) + { + if (GNUNET_YES == rms->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying refresh melt failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + rms->backoff = GNUNET_TIME_UNIT_ZERO; + else + rms->backoff = EXCHANGE_LIB_BACKOFF (rms->backoff); + rms->retry_task = GNUNET_SCHEDULER_add_delayed (rms->backoff, + &do_melt_retry, + rms); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d to command %s in %s:%u\n", + http_status, + (int) ec, + rms->is->commands[rms->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (full_response, stderr, 0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + rms->noreveal_index = noreveal_index; + + if (GNUNET_YES == rms->double_melt) + { + TALER_LOG_DEBUG ("Doubling the melt (%s)\n", + rms->is->commands[rms->is->ip].label); + rms->rmh = TALER_EXCHANGE_refresh_melt + (rms->is->exchange, rms->refresh_data_length, + rms->refresh_data, &melt_cb, rms); + rms->double_melt = GNUNET_NO; + return; + } + TALER_TESTING_interpreter_next (rms->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_melt_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct RefreshMeltState *rms = cls; + unsigned int num_fresh_coins; + const struct TALER_TESTING_Command *coin_command; + /* FIXME: this should be dynamic */ + const char *melt_fresh_amounts[] = { + "EUR:1", "EUR:1", "EUR:1", "EUR:0.1", + NULL}; + const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk; + + rms->is = is; + rms->noreveal_index = UINT16_MAX; + for (num_fresh_coins=0; + NULL != melt_fresh_amounts[num_fresh_coins]; + num_fresh_coins++) ; + + rms->fresh_pks = GNUNET_new_array + (num_fresh_coins, + struct TALER_EXCHANGE_DenomPublicKey); + { + const struct TALER_CoinSpendPrivateKeyP *melt_priv; + struct TALER_Amount melt_amount; + struct TALER_Amount fresh_amount; + const struct TALER_DenominationSignature *melt_sig; + const struct TALER_EXCHANGE_DenomPublicKey *melt_denom_pub; + + const struct MeltDetails *md = &rms->melted_coin; + if (NULL == (coin_command + = TALER_TESTING_interpreter_lookup_command + (is, md->coin_reference))) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv + (coin_command, 0, &melt_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + + if (GNUNET_OK != + TALER_string_to_amount (md->amount, + &melt_amount)) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u\n", + md->amount, + is->ip); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + if (GNUNET_OK != + TALER_TESTING_get_trait_denom_sig (coin_command, + 0, + &melt_sig)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub + (coin_command, 0, &melt_denom_pub)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + + for (unsigned int i=0;i<num_fresh_coins;i++) + { + if (GNUNET_OK != TALER_string_to_amount + (melt_fresh_amounts[i], &fresh_amount)) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at index %u\n", + melt_fresh_amounts[i], i); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + fresh_pk = TALER_TESTING_find_pk + (TALER_EXCHANGE_get_keys (is->exchange), &fresh_amount); + if (NULL == fresh_pk) + { + GNUNET_break (0); + /* Subroutine logs specific error */ + TALER_TESTING_interpreter_fail (rms->is); + return; + } + + rms->fresh_pks[i] = *fresh_pk; + } + rms->refresh_data = TALER_EXCHANGE_refresh_prepare + (melt_priv, &melt_amount, melt_sig, melt_denom_pub, + GNUNET_YES, num_fresh_coins, rms->fresh_pks, + &rms->refresh_data_length); + + if (NULL == rms->refresh_data) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + rms->rmh = TALER_EXCHANGE_refresh_melt + (is->exchange, rms->refresh_data_length, + rms->refresh_data, &melt_cb, rms); + + if (NULL == rms->rmh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + } +} + + +/** + * Free the "refresh melt" CMD state, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, typically a #struct RefreshMeltState. + * @param cmd the command which is being cleaned up. + */ +static void +refresh_melt_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct RefreshMeltState *rms = cls; + + if (NULL != rms->rmh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + rms->is->ip, rms->is->commands[rms->is->ip].label); + TALER_EXCHANGE_refresh_melt_cancel (rms->rmh); + rms->rmh = NULL; + } + if (NULL != rms->retry_task) + { + GNUNET_SCHEDULER_cancel (rms->retry_task); + rms->retry_task = NULL; + } + GNUNET_free_non_null (rms->fresh_pks); + rms->fresh_pks = NULL; + GNUNET_free_non_null (rms->refresh_data); + rms->refresh_data = NULL; + rms->refresh_data_length = 0; + GNUNET_free (rms); +} + + +/** + * Offer internal data to the "refresh melt" CMD. + * + * @param cls closure. + * @param ret[out] result (could be anything). + * @param trait name of the trait. + * @param index index number of the object to offer. + * + * @return #GNUNET_OK on success. + */ +static int +refresh_melt_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct RefreshMeltState *rms = cls; + + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_denom_pub (0, rms->fresh_pks), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Create a "refresh melt" command. + * + * @param label command label. + * @param amount amount to be melted. + * @param coin_reference reference to a command + * that will provide a coin to refresh. + * @param expected_response_code expected HTTP code. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_melt + (const char *label, + const char *amount, + const char *coin_reference, + unsigned int expected_response_code) +{ + struct RefreshMeltState *rms; + struct MeltDetails md; + + md.coin_reference = coin_reference; + md.amount = amount; + + rms = GNUNET_new (struct RefreshMeltState); + rms->melted_coin = md; + rms->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .label = label, + .cls = rms, + .run = &refresh_melt_run, + .cleanup = &refresh_melt_cleanup, + .traits = &refresh_melt_traits + }; + + return cmd; +} + + +/** + * Create a "refresh melt" CMD that does TWO /refresh/melt + * requests. This was needed to test the replay of a valid melt + * request, see #5312. + * + * @param label command label + * @param exchange connection to the exchange + * @param amount amount to be melted. + * @param coin_reference reference to a command that will provide + * a coin to refresh + * @param expected_response_code expected HTTP code + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_melt_double + (const char *label, + const char *amount, + const char *coin_reference, + unsigned int expected_response_code) +{ + struct RefreshMeltState *rms; + struct MeltDetails md; + + md.coin_reference = coin_reference; + md.amount = amount; + + rms = GNUNET_new (struct RefreshMeltState); + rms->melted_coin = md; + rms->expected_response_code = expected_response_code; + rms->double_melt = GNUNET_YES; + + struct TALER_TESTING_Command cmd = { + .label = label, + .cls = rms, + .run = &refresh_melt_run, + .cleanup = &refresh_melt_cleanup, + .traits = &refresh_melt_traits + }; + + return cmd; +} + + +/** + * Modify a "refresh melt" command to enable retries. + * + * @param cmd command + * @return modified command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_melt_with_retry (struct TALER_TESTING_Command cmd) +{ + struct RefreshMeltState *rms; + + GNUNET_assert (&refresh_melt_run == cmd.run); + rms = cmd.cls; + rms->do_retry = GNUNET_YES; + return cmd; +} + + +/** + * Offer internal data from a "refresh reveal" CMD. + * + * @param cls closure. + * @param ret[out] result (could be anything). + * @param trait name of the trait. + * @param index index number of the object to offer. + * + * @return #GNUNET_OK on success. + */ +static int +refresh_reveal_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct RefreshRevealState *rrs = cls; + unsigned int num_coins = rrs->num_fresh_coins; +#define NUM_TRAITS ((num_coins * 3) + 3) + struct TALER_TESTING_Trait traits[NUM_TRAITS]; + + /* Making coin privs traits */ + for (unsigned int i=0; i<num_coins; i++) + traits[i] = TALER_TESTING_make_trait_coin_priv + (i, &rrs->fresh_coins[i].coin_priv); + + /* Making denom pubs traits */ + for (unsigned int i=0; i<num_coins; i++) + traits[num_coins + i] + = TALER_TESTING_make_trait_denom_pub + (i, rrs->fresh_coins[i].pk); + + /* Making denom sigs traits */ + for (unsigned int i=0; i<num_coins; i++) + traits[(num_coins * 2) + i] + = TALER_TESTING_make_trait_denom_sig + (i, &rrs->fresh_coins[i].sig); + + /* number of fresh coins */ + traits[(num_coins * 3)] = TALER_TESTING_make_trait_uint + (0, &rrs->num_fresh_coins); + + /* whole array of fresh coins */ + traits[(num_coins * 3) + 1] + = TALER_TESTING_make_trait_fresh_coins (0, rrs->fresh_coins), + + /* end of traits */ + traits[(num_coins * 3) + 2] = TALER_TESTING_trait_end (); + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Create a "refresh reveal" command. + * + * @param label command label. + * @param exchange connection to the exchange. + * @param melt_reference reference to a "refresh melt" command. + * @param expected_response_code expected HTTP response code. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_reveal + (const char *label, + const char *melt_reference, + unsigned int expected_response_code) +{ + struct RefreshRevealState *rrs; + + rrs = GNUNET_new (struct RefreshRevealState); + rrs->melt_reference = melt_reference; + rrs->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .cls = rrs, + .label = label, + .run = &refresh_reveal_run, + .cleanup = &refresh_reveal_cleanup, + .traits = &refresh_reveal_traits + }; + + return cmd; +} + + +/** + * Modify a "refresh reveal" command to enable retries. + * + * @param cmd command + * @return modified command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_reveal_with_retry (struct TALER_TESTING_Command cmd) +{ + struct RefreshRevealState *rrs; + + GNUNET_assert (&refresh_reveal_run == cmd.run); + rrs = cmd.cls; + rrs->do_retry = GNUNET_YES; + return cmd; +} + + +/** + * Create a "refresh link" command. + * + * @param label command label. + * @param reveal_reference reference to a "refresh reveal" CMD. + * @param expected_response_code expected HTTP response code + * + * @return the "refresh link" command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_link + (const char *label, + const char *reveal_reference, + unsigned int expected_response_code) +{ + struct RefreshLinkState *rrs; + + rrs = GNUNET_new (struct RefreshLinkState); + rrs->reveal_reference = reveal_reference; + rrs->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .cls = rrs, + .label = label, + .run = &refresh_link_run, + .cleanup = &refresh_link_cleanup + }; + + return cmd; +} + + +/** + * Modify a "refresh link" command to enable retries. + * + * @param cmd command + * @return modified command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_link_with_retry (struct TALER_TESTING_Command cmd) +{ + struct RefreshLinkState *rls; + + GNUNET_assert (&refresh_link_run == cmd.run); + rls = cmd.cls; + rls->do_retry = GNUNET_YES; + return cmd; +} diff --git a/src/lib/testing_api_cmd_refund.c b/src/lib/testing_api_cmd_refund.c new file mode 100644 index 000000000..f6640d0fc --- /dev/null +++ b/src/lib/testing_api_cmd_refund.c @@ -0,0 +1,331 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange/testing_api_cmd_refund.c + * @brief Implement the /refund test command, plus other + * corollary commands (?). + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" + + +/** + * State for a "refund" CMD. + */ +struct RefundState +{ + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Amount to be refunded. + */ + const char *refund_amount; + + /** + * Expected refund fee. + */ + const char *refund_fee; + + /** + * Reference to any command that can provide a coin to refund. + */ + const char *coin_reference; + + /** + * Refund transaction identifier. + */ + uint64_t refund_transaction_id; + + /** + * Connection to the exchange. + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * Handle to the refund operation. + */ + struct TALER_EXCHANGE_RefundHandle *rh; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; +}; + + +/** + * Check the result for the refund request, just check if the + * response code is acceptable. + * + * @param cls closure + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param exchange_pub public key the exchange + * used for signing @a obj. + * @param obj response object. + */ +static void +refund_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const json_t *obj) +{ + + struct RefundState *rs = cls; + struct TALER_TESTING_Command *refund_cmd; + + refund_cmd = &rs->is->commands[rs->is->ip]; + rs->rh = NULL; + if (rs->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s in %s:%u\n", + http_status, + refund_cmd->label, + __FILE__, + __LINE__); + json_dumpf (obj, stderr, 0); + TALER_TESTING_interpreter_fail (rs->is); + return; + } + + TALER_TESTING_interpreter_next (rs->is); +} + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refund_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct RefundState *rs = cls; + const struct TALER_CoinSpendPrivateKeyP *coin_priv; + struct TALER_CoinSpendPublicKeyP coin; + const json_t *contract_terms; + struct GNUNET_HashCode h_contract_terms; + struct TALER_Amount refund_fee; + struct TALER_Amount refund_amount; + const struct GNUNET_CRYPTO_EddsaPrivateKey *merchant_priv; + const struct TALER_TESTING_Command *coin_cmd; + + rs->exchange = is->exchange; + rs->is = is; + + if (GNUNET_OK != + TALER_string_to_amount (rs->refund_amount, + &refund_amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u/%s\n", + rs->refund_amount, + is->ip, + cmd->label); + TALER_TESTING_interpreter_fail (is); + return; + } + if (GNUNET_OK != + TALER_string_to_amount (rs->refund_fee, + &refund_fee)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u/%s\n", + rs->refund_fee, + is->ip, + cmd->label); + TALER_TESTING_interpreter_fail (is); + return; + } + + coin_cmd = TALER_TESTING_interpreter_lookup_command + (is, rs->coin_reference); + + if (NULL == coin_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_contract_terms + (coin_cmd, 0, &contract_terms)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_assert (GNUNET_OK == + TALER_JSON_hash (contract_terms, + &h_contract_terms)); + + /* Hunting for a coin .. */ + if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv + (coin_cmd, 0, &coin_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &coin.eddsa_pub); + if (GNUNET_OK != TALER_TESTING_get_trait_peer_key + (coin_cmd, 0, &merchant_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + rs->rh = TALER_EXCHANGE_refund + (rs->exchange, + &refund_amount, + &refund_fee, + &h_contract_terms, + &coin, + rs->refund_transaction_id, + (const struct TALER_MerchantPrivateKeyP *) merchant_priv, + &refund_cb, rs); + + GNUNET_assert (NULL != rs->rh); +} + + +/** + * Free the state from a "refund" CMD, and possibly cancel + * a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +refund_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct RefundState *rs = cls; + + if (NULL != rs->rh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + rs->is->ip, + cmd->label); + TALER_EXCHANGE_refund_cancel (rs->rh); + rs->rh = NULL; + } + GNUNET_free (rs); +} + +/** + * Create a "refund" command. + * + * @param label command label. + * @param expected_response_code expected HTTP status code. + * @param refund_amount the amount to ask a refund for. + * @param refund_fee expected refund fee. + * @param coin_reference reference to a command that can + * provide a coin to be refunded. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refund (const char *label, + unsigned int expected_response_code, + const char *refund_amount, + const char *refund_fee, + const char *coin_reference) +{ + struct RefundState *rs; + + rs = GNUNET_new (struct RefundState); + + rs->expected_response_code = expected_response_code; + rs->refund_amount = refund_amount; + rs->refund_fee = refund_fee; + rs->coin_reference = coin_reference; + + struct TALER_TESTING_Command cmd = { + .cls = rs, + .label = label, + .run = &refund_run, + .cleanup = &refund_cleanup + }; + + return cmd; +} + +/** + * Create a "refund" command, allow to specify refund transaction + * id. Mainly used to create conflicting requests. + * + * @param label command label. + * @param expected_response_code expected HTTP status code. + * @param refund_amount the amount to ask a refund for. + * @param refund_fee expected refund fee. + * @param coin_reference reference to a command that can + * provide a coin to be refunded. + * @param refund_transaction_id transaction id to use + * in the request. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refund_with_id + (const char *label, + unsigned int expected_response_code, + const char *refund_amount, + const char *refund_fee, + const char *coin_reference, + uint64_t refund_transaction_id) +{ + struct RefundState *rs; + + rs = GNUNET_new (struct RefundState); + + rs->expected_response_code = expected_response_code; + rs->refund_amount = refund_amount; + rs->refund_fee = refund_fee; + rs->coin_reference = coin_reference; + rs->refund_transaction_id = refund_transaction_id; + + struct TALER_TESTING_Command cmd = { + .cls = rs, + .label = label, + .run = &refund_run, + .cleanup = &refund_cleanup + }; + + return cmd; +} diff --git a/src/lib/testing_api_cmd_serialize_keys.c b/src/lib/testing_api_cmd_serialize_keys.c new file mode 100644 index 000000000..38d32eadd --- /dev/null +++ b/src/lib/testing_api_cmd_serialize_keys.c @@ -0,0 +1,315 @@ +/* + This file is part of TALER + (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_cmd_serialize_keys.c + * @brief Lets tests use the keys serialization API. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include <jansson.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" + + +/** + * Internal state for a serialize-keys CMD. + */ +struct SerializeKeysState +{ + /** + * Serialized keys. + */ + json_t *keys; + + /** + * Exchange URL. Needed because the exchange gets disconnected + * from, after keys serialization. This value is then needed by + * subsequent commands that have to reconnect to the exchagne. + */ + const char *exchange_url; +}; + + +/** + * Internal state for a connect-with-state CMD. + */ +struct ConnectWithStateState +{ + + /** + * Reference to a CMD that offers a serialized key-state + * that will be used in the reconnection. + */ + const char *state_reference; + + /** + * If set to GNUNET_YES, then the /keys callback has already + * been passed the control to the next CMD. This is necessary + * because it is not uncommon that the /keys callback gets + * invoked multiple times, and without this flag, we would keep + * going "next" CMD upon every invocation (causing impredictable + * behaviour as for the instruction pointer.) + */ + unsigned int consumed; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; +}; + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +serialize_keys_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct SerializeKeysState *sks = cls; + + sks->keys = TALER_EXCHANGE_serialize_data (is->exchange); + if (NULL == sks->keys) + TALER_TESTING_interpreter_fail (is); + + sks->exchange_url = GNUNET_strdup + (TALER_EXCHANGE_get_base_url (is->exchange)); + TALER_EXCHANGE_disconnect (is->exchange); + is->exchange = NULL; + TALER_TESTING_interpreter_next (is); +} + + +/** + * Cleanup the state of a "serialize keys" CMD. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +void +serialize_keys_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct SerializeKeysState *sks = cls; + + if (NULL != sks->keys) + { + json_decref (sks->keys); + } + + GNUNET_free ((char *) sks->exchange_url); + GNUNET_free (sks); +} + + +/** + * Offer serialized keys as trait. + * + * @param cls closure. + * @param ret[out] result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * + * @return #GNUNET_OK on success. + */ +static int +serialize_keys_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct SerializeKeysState *sks = cls; + + struct TALER_TESTING_Trait traits[] = { + + TALER_TESTING_make_trait_exchange_keys (0, sks->keys), + TALER_TESTING_make_trait_url (0, sks->exchange_url), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + +/** + * /keys callback. Just checks HTTP status is OK, + * and step forward to next command. + * + * @param cls closure + * @param keys information about the various keys used + * by the exchange, NULL if /keys failed + * @param compat protocol compatibility information* + */ +static void +cb (void *cls, + const struct TALER_EXCHANGE_Keys *keys, + enum TALER_EXCHANGE_VersionCompatibility compat) +{ + struct ConnectWithStateState *cwss = cls; + + if (GNUNET_YES == cwss->consumed) + { + TALER_LOG_DEBUG ("Reconnection /keys 'cb' invoked already," + " nothing to do\n"); + return; + } + + cwss->consumed = GNUNET_YES; + if (NULL == keys) + TALER_TESTING_interpreter_fail (cwss->is); + + TALER_LOG_DEBUG ("reconnect next CMD\n"); + TALER_TESTING_interpreter_next (cwss->is); +} + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +connect_with_state_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct ConnectWithStateState *cwss = cls; + const struct TALER_TESTING_Command *state_cmd; + const json_t *serialized_keys; + const char *exchange_url; + + cwss->is = is; + state_cmd = TALER_TESTING_interpreter_lookup_command + (is, cwss->state_reference); + + /* Command providing serialized keys not found. */ + if (NULL == state_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_get_trait_exchange_keys + (state_cmd, + 0, + &serialized_keys)); + + TALER_LOG_DEBUG ("Serialized key-state: %s\n", + json_dumps (serialized_keys, + JSON_INDENT (1))); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_get_trait_url + (state_cmd, + 0, + &exchange_url)); + + is->exchange = TALER_EXCHANGE_connect + (is->ctx, + exchange_url, + cb, + cwss, + TALER_EXCHANGE_OPTION_DATA, + serialized_keys, + TALER_EXCHANGE_OPTION_END); +} + + +/** + * Cleanup the state of a "connect with state" CMD. Just + * a placeholder to avoid jumping on an invalid address. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +void +connect_with_state_cleanup + (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct ConnectWithStateState *cwss = cls; + + GNUNET_free (cwss); +} + +/** + * Make a serialize-keys CMD. It will ask for + * keys serialization __and__ disconnect from the + * exchange. + * + * @param label CMD label + * @return the CMD. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_serialize_keys (const char *label) +{ + struct SerializeKeysState *sks; + + sks = GNUNET_new (struct SerializeKeysState); + struct TALER_TESTING_Command cmd = { + .cls = sks, + .label = label, + .run = serialize_keys_run, + .cleanup = serialize_keys_cleanup, + .traits = serialize_keys_traits + }; + + return cmd; +} + +/** + * Make a connect-with-state CMD. This command + * will use a serialized key state to reconnect + * to the exchange. + * + * @param label command label + * @param state_reference label of a CMD offering + * a serialized key state. + * @return the CMD. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_connect_with_state (const char *label, + const char *state_reference) +{ + struct ConnectWithStateState *cwss; + + cwss = GNUNET_new (struct ConnectWithStateState); + cwss->state_reference = state_reference; + cwss->consumed = GNUNET_NO; + + struct TALER_TESTING_Command cmd = { + .cls = cwss, + .label = label, + .run = connect_with_state_run, + .cleanup = connect_with_state_cleanup + }; + + return cmd; +} diff --git a/src/lib/testing_api_cmd_signal.c b/src/lib/testing_api_cmd_signal.c new file mode 100644 index 000000000..f4fd8bb76 --- /dev/null +++ b/src/lib/testing_api_cmd_signal.c @@ -0,0 +1,116 @@ +/* + This file is part of TALER + (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/testing_api_cmd_signal.c + * @brief command(s) to send signals to processes. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" + + +/** + * State for a "signal" CMD. + */ +struct SignalState +{ + /** + * The process to send the signal to. + */ + struct GNUNET_OS_Process *process; + + /** + * The signal to send to the process. + */ + int signal; +}; + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +signal_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct SignalState *ss = cls; + + GNUNET_break (0 == GNUNET_OS_process_kill + (ss->process, ss->signal)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signaling '%d'..\n", + ss->signal); + sleep (6); + TALER_TESTING_interpreter_next (is); +} + + +/** + * Cleanup the state from a "signal" CMD. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +signal_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct SignalState *ss = cls; + + GNUNET_free (ss); +} + + +/** + * Create a "signal" CMD. + * + * @param label command label. + * @param process handle to the process to signal. + * @param signal signal to send. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_signal (const char *label, + struct GNUNET_OS_Process *process, + int signal) +{ + struct SignalState *ss; + + ss = GNUNET_new (struct SignalState); + ss->process = process; + ss->signal = signal; + + + struct TALER_TESTING_Command cmd = { + .cls = ss, + .label = label, + .run = &signal_run, + .cleanup = &signal_cleanup + }; + + return cmd; +} diff --git a/src/lib/testing_api_cmd_sleep.c b/src/lib/testing_api_cmd_sleep.c new file mode 100644 index 000000000..165d0aae4 --- /dev/null +++ b/src/lib/testing_api_cmd_sleep.c @@ -0,0 +1,104 @@ +/* + This file is part of TALER + (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/testing_api_cmd_sleep.c + * @brief command(s) to sleep for a bit + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" + + +/** + * State for a "sleep" CMD. + */ +struct SleepState +{ + + /** + * How long should we sleep? + */ + unsigned int duration; +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +sleep_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct SleepState *ss = cls; + + sleep (ss->duration); + TALER_TESTING_interpreter_next (is); +} + + +/** + * Cleanup the state from a "sleep" CMD. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +sleep_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct SleepState *ss = cls; + + GNUNET_free (ss); +} + + +/** + * Sleep for @a duration_s seconds. + * + * @param label command label. + * @param duration_s number of seconds to sleep + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_sleep (const char *label, + unsigned int duration_s) +{ + struct SleepState *ss; + + ss = GNUNET_new (struct SleepState); + ss->duration = duration_s; + + + struct TALER_TESTING_Command cmd = { + .cls = ss, + .label = label, + .run = &sleep_run, + .cleanup = &sleep_cleanup + }; + + return cmd; +} diff --git a/src/lib/testing_api_cmd_status.c b/src/lib/testing_api_cmd_status.c new file mode 100644 index 000000000..8e9290f6d --- /dev/null +++ b/src/lib/testing_api_cmd_status.c @@ -0,0 +1,264 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange/testing_api_cmd_status.c + * @brief Implement the /reserve/status test command. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" + + +/** + * State for a "status" CMD. + */ +struct StatusState +{ + /** + * Label to the command which created the reserve to check, + * needed to resort the reserve key. + */ + const char *reserve_reference; + + /** + * Handle to the "reserve status" operation. + */ + struct TALER_EXCHANGE_ReserveStatusHandle *rsh; + + /** + * Expected reserve balance. + */ + const char *expected_balance; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; +}; + + +/** + * Check that the reserve balance and HTTP response code are + * both acceptable. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param balance current balance in the reserve, NULL on error. + * @param history_length number of entries in the transaction + * history, 0 on error. + * @param history detailed transaction history, NULL on error. + */ +static void +reserve_status_cb + (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const json_t *json, + const struct TALER_Amount *balance, + unsigned int history_length, + const struct TALER_EXCHANGE_ReserveHistory *history) +{ + struct StatusState *ss = cls; + struct TALER_Amount eb; + + ss->rsh = NULL; + if (ss->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected HTTP response code: %d in %s:%u\n", + http_status, + __FILE__, + __LINE__); + TALER_TESTING_interpreter_fail (ss->is); + return; + } + + GNUNET_assert (GNUNET_OK == TALER_string_to_amount + (ss->expected_balance, &eb)); + + if (0 != TALER_amount_cmp (&eb, balance)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected amount in reserve: %s\n", + TALER_amount_to_string (balance)); + TALER_TESTING_interpreter_fail (ss->is); + return; + } + +/** + * Fixme: need a way to check if reserve history is consistent. + * Every command which relates to reserve 'x' should be added in + * a linked list of all commands that relate to the same reserve + * 'x'. + * + * API-wise, any command that relates to a reserve should offer a + * method called e.g. "compare_with_history" that takes an element + * of the array returned by "/reserve/status" and checks if that + * element correspond to itself (= the command exposing the check- + * method). + * + * IDEA: Maybe realize this via another trait, some kind of + * "reserve history update trait" which returns information about + * how the command changes the history (provided only by commands + * that change reserve balances)? + */ + + TALER_TESTING_interpreter_next (ss->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command being executed. + * @param is the interpreter state. + */ +static void +status_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct StatusState *ss = cls; + const struct TALER_TESTING_Command *create_reserve; + const struct TALER_ReservePrivateKeyP *reserve_priv; + struct TALER_ReservePublicKeyP reserve_pub; + + ss->is = is; + GNUNET_assert (NULL != ss->reserve_reference); + + create_reserve + = TALER_TESTING_interpreter_lookup_command + (is, ss->reserve_reference); + + if (NULL == create_reserve) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + /* NOTE: the following line might generate a ERROR log + * statements, but it can be ignored. */ + if (GNUNET_OK == TALER_TESTING_get_trait_reserve_priv + (create_reserve, + 0, + &reserve_priv)) + { + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &reserve_pub.eddsa_pub); + } + else + { + const char *transfer_subject; + + if (GNUNET_OK != TALER_TESTING_get_trait_transfer_subject + (create_reserve, + 0, + &transfer_subject)) + { + GNUNET_break (0); + TALER_LOG_ERROR + ("The reserve has neither a priv nor a subject line..\n"); + TALER_TESTING_interpreter_fail (is); + return; + + } + + GNUNET_STRINGS_string_to_data + (transfer_subject, + strlen (transfer_subject), + &reserve_pub.eddsa_pub, + sizeof (struct TALER_ReservePublicKeyP)); + } + + ss->rsh = TALER_EXCHANGE_reserve_status (is->exchange, + &reserve_pub, + &reserve_status_cb, + ss); +} + + +/** + * Cleanup the state from a "reserve status" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +status_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct StatusState *ss = cls; + + if (NULL != ss->rsh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + ss->is->ip, + cmd->label); + TALER_EXCHANGE_reserve_status_cancel (ss->rsh); + ss->rsh = NULL; + } + GNUNET_free (ss); +} + + +/** + * Create a "reserve status" command. + * + * @param label the command label. + * @param reserve_reference reference to the reserve to check. + * @param expected_balance expected balance for the reserve. + * @param expected_response_code expected HTTP response code. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_status (const char *label, + const char *reserve_reference, + const char *expected_balance, + unsigned int expected_response_code) +{ + struct StatusState *ss; + + ss = GNUNET_new (struct StatusState); + ss->reserve_reference = reserve_reference; + ss->expected_balance = expected_balance; + ss->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .cls = ss, + .label = label, + .run = &status_run, + .cleanup = &status_cleanup + }; + + return cmd; +} diff --git a/src/lib/testing_api_cmd_track.c b/src/lib/testing_api_cmd_track.c new file mode 100644 index 000000000..4898a097c --- /dev/null +++ b/src/lib/testing_api_cmd_track.c @@ -0,0 +1,810 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange/testing_api_cmd_track.c + * @brief Implement the testing CMDs for the /track operations. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" + +/** + * State for a "track transaction" CMD. + */ +struct TrackTransactionState +{ + + /** + * If non NULL, will provide a WTID to be compared against + * the one returned by the "track transaction" operation. + */ + const char *bank_transfer_reference; + + /** + * The WTID associated by the transaction being tracked. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Reference to any operation that can provide a transaction. + * Will be the transaction to track. + */ + const char *transaction_reference; + + /** + * Index of the coin involved in the transaction. Recall: + * at the exchange, the tracking is done _per coin_. + */ + unsigned int coin_index; + + /** + * Handle to the "track transaction" pending operation. + */ + struct TALER_EXCHANGE_TrackTransactionHandle *tth; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; +}; + + +/** + * State for a "track transfer" CMD. + */ +struct TrackTransferState +{ + + /** + * Expected amount for the WTID being tracked. + */ + const char *expected_total_amount; + + /** + * Expected fee for this WTID. + */ + const char *expected_wire_fee; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Reference to any operation that can provide a WTID. + * Will be the WTID to track. + */ + const char *wtid_reference; + + /** + * Reference to any operation that can provide wire details. + * Those wire details will then be matched against the credit + * bank account of the tracked WTID. This way we can test that + * a wire transfer paid back one particular bank account. + */ + const char *wire_details_reference; + + /** + * Reference to any operation that can provide an amount. + * This way we can check that the transferred amount matches + * our expectations. + */ + const char *total_amount_reference; + + /** + * Index to the WTID to pick, in case @a wtid_reference has + * many on offer. + */ + unsigned int index; + + /** + * Handle to a pending "track transfer" operation. + */ + struct TALER_EXCHANGE_TrackTransferHandle *tth; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; +}; + + +/** + * Checks what is returned by the "track transaction" operation. + * Checks that the HTTP response code is acceptable, and - if the + * right reference is non NULL - that the wire transfer subject + * line matches our expectations. + * + * @param cls closure. + * @param http_status HTTP status code we got. + * @param ec taler-specific error code. + * @param json original json reply (may include signatures, those + * have then been validated already). + * @param wtid wire transfer identifier, NULL if exchange did not + * execute the transaction yet. + * @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 (can be NULL). + * @param total_amount total amount of the wire transfer, or NULL + * if the exchange could not provide any @a wtid (set only + * if @a http_status is #MHD_HTTP_OK). + */ +static void +deposit_wtid_cb + (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const json_t *json, + const struct TALER_WireTransferIdentifierRawP *wtid, + struct GNUNET_TIME_Absolute execution_time, + const struct TALER_Amount *coin_contribution) +{ + struct TrackTransactionState *tts = cls; + struct TALER_TESTING_Interpreter *is = tts->is; + struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + + tts->tth = NULL; + if (tts->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s in %s:%u\n", + http_status, + cmd->label, + __FILE__, + __LINE__); + json_dumpf (json, stderr, 0); + TALER_TESTING_interpreter_fail (is); + return; + } + switch (http_status) + { + case MHD_HTTP_OK: + tts->wtid = *wtid; + if (NULL != tts->bank_transfer_reference) + { + const struct TALER_TESTING_Command *bank_transfer_cmd; + char *ws; + + /* _this_ wire transfer subject line. */ + ws = GNUNET_STRINGS_data_to_string_alloc (wtid, + sizeof (*wtid)); + + bank_transfer_cmd = TALER_TESTING_interpreter_lookup_command + (is, tts->bank_transfer_reference); + + if (NULL == bank_transfer_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + /* expected wire transfer subject line. */ + const char *transfer_subject; + + if (GNUNET_OK != + TALER_TESTING_get_trait_transfer_subject + (bank_transfer_cmd, 0, &transfer_subject)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + /* Compare that expected and gotten subjects match. */ + if (0 != strcmp (ws, transfer_subject)) + { + GNUNET_break (0); + GNUNET_free (ws); + TALER_TESTING_interpreter_fail (tts->is); + return; + } + + GNUNET_free (ws); + } + break; + case MHD_HTTP_ACCEPTED: + /* allowed, nothing to check here */ + break; + case MHD_HTTP_NOT_FOUND: + /* allowed, nothing to check here */ + break; + default: + GNUNET_break (0); + break; + } + TALER_TESTING_interpreter_next (tts->is); +} + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +track_transaction_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct TrackTransactionState *tts = cls; + const struct TALER_TESTING_Command *transaction_cmd; + const struct TALER_CoinSpendPrivateKeyP *coin_priv; + struct TALER_CoinSpendPublicKeyP coin_pub; + const json_t *contract_terms; + const json_t *wire_details; + struct GNUNET_HashCode h_wire_details; + struct GNUNET_HashCode h_contract_terms; + const struct GNUNET_CRYPTO_EddsaPrivateKey *merchant_priv; + + tts->is = is; + transaction_cmd = TALER_TESTING_interpreter_lookup_command + (tts->is, tts->transaction_reference); + + if (NULL == transaction_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (tts->is); + return; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv + (transaction_cmd, tts->coin_index, &coin_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (tts->is); + return; + } + + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &coin_pub.eddsa_pub); + + /* Get the strings.. */ + if (GNUNET_OK != TALER_TESTING_get_trait_wire_details + (transaction_cmd, 0, &wire_details)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (tts->is); + return; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_contract_terms + (transaction_cmd, 0, &contract_terms)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (tts->is); + return; + } + + if ((NULL == wire_details) || (NULL == contract_terms)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (tts->is); + return; + } + + /* Should not fail here, json has been parsed already */ + GNUNET_assert + ( (GNUNET_OK == + TALER_JSON_merchant_wire_signature_hash (wire_details, + &h_wire_details)) && + (GNUNET_OK == + TALER_JSON_hash (contract_terms, + &h_contract_terms)) ); + + if (GNUNET_OK != TALER_TESTING_get_trait_peer_key + (transaction_cmd, 0, &merchant_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (tts->is); + return; + } + + tts->tth = TALER_EXCHANGE_track_transaction + (is->exchange, + (struct TALER_MerchantPrivateKeyP *) merchant_priv, + &h_wire_details, + &h_contract_terms, + &coin_pub, + &deposit_wtid_cb, + tts); + + GNUNET_assert (NULL != tts->tth); +} + +/** + * Cleanup the state from a "track transaction" CMD, and possibly + * cancel a operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +void +track_transaction_cleanup + (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct TrackTransactionState *tts = cls; + + if (NULL != tts->tth) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + tts->is->ip, + cmd->label); + TALER_EXCHANGE_track_transaction_cancel (tts->tth); + tts->tth = NULL; + } + GNUNET_free (tts); +} + + +/** + * Offer internal data from a "track transaction" CMD. + * + * @param cls closure. + * @param ret[out] result (could be anything). + * @param trait name of the trait. + * @param index index number of the object to offer. + * + * @return #GNUNET_OK on success. + */ +static int +track_transaction_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct TrackTransactionState *tts = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_wtid (0, &tts->wtid), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Create a "track transaction" command. + * + * @param label the command label. + * @param transaction_reference reference to a deposit operation, + * will be used to get the input data for the track. + * @param coin_index index of the coin involved in the transaction. + * @param expected_response_code expected HTTP response code. + * @param bank_transfer_reference reference to a command that + * can offer a WTID so as to check that against what WTID + * the tracked operation has. Set as NULL if not needed. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_track_transaction + (const char *label, + const char *transaction_reference, + unsigned int coin_index, + unsigned int expected_response_code, + const char *bank_transfer_reference) +{ + struct TrackTransactionState *tts; + + tts = GNUNET_new (struct TrackTransactionState); + tts->transaction_reference = transaction_reference; + tts->expected_response_code = expected_response_code; + tts->bank_transfer_reference = bank_transfer_reference; + tts->coin_index = coin_index; + + struct TALER_TESTING_Command cmd = { + .cls = tts, + .label = label, + .run = &track_transaction_run, + .cleanup = &track_transaction_cleanup, + .traits = &track_transaction_traits + }; + + return cmd; +} + +/** + * Cleanup the state for a "track transfer" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +void +track_transfer_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + + struct TrackTransferState *tts = cls; + + if (NULL != tts->tth) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + tts->is->ip, + cmd->label); + TALER_EXCHANGE_track_transfer_cancel (tts->tth); + tts->tth = NULL; + } + GNUNET_free (tts); + +} + +/** + * Check whether the HTTP response code from a "track transfer" + * operation is acceptable, and all other values like total amount, + * wire fees and hashed wire details as well. + * + * @param cls closure. + * @param http_status HTTP status code we got. + * @param ec taler-specific error code. + * @param exchange_pub public key the exchange used for signing + * the response. + * @param json original json reply (may include signatures, those + * have then been validated already). + * @param h_wire hash of the wire transfer address the transfer + * went to, or NULL on error. + * @param execution_time time when the exchange claims to have + * performed the wire transfer. + * @param total_amount total amount of the wire transfer, or NULL + * if the exchange could not provide any @a wtid (set only + * if @a http_status is "200 OK"). + * @param wire_fee wire fee that was charged by the exchange. + * @param details_length length of the @a details array. + * @param details array with details about the combined + * transactions. + */ +static void +track_transfer_cb + (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const json_t *json, + const struct GNUNET_HashCode *h_wire, + struct GNUNET_TIME_Absolute execution_time, + const struct TALER_Amount *total_amount, + const struct TALER_Amount *wire_fee, + unsigned int details_length, + const struct TALER_TrackTransferDetails *details) +{ + struct TrackTransferState *tts = cls; + struct TALER_TESTING_Interpreter *is = tts->is; + struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + + struct TALER_Amount expected_amount; + + tts->tth = NULL; + + if (tts->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s in %s:%u\n", + http_status, + cmd->label, + __FILE__, + __LINE__); + json_dumpf (json, stderr, 0); + TALER_TESTING_interpreter_fail (is); + return; + } + + if ( (NULL == tts->expected_total_amount) || + (NULL == tts->expected_wire_fee) ) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Expected amount and fee not specified, " + "likely to segfault...\n"); + + switch (http_status) + { + case MHD_HTTP_OK: + if (GNUNET_OK != + TALER_string_to_amount (tts->expected_total_amount, + &expected_amount)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (0 != TALER_amount_cmp (total_amount, + &expected_amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Total amount missmatch to command %s - " + "%s vs %s\n", + cmd->label, + TALER_amount_to_string (total_amount), + TALER_amount_to_string (&expected_amount)); + json_dumpf (json, stderr, 0); + fprintf (stderr, "\n"); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (GNUNET_OK != + TALER_string_to_amount (tts->expected_wire_fee, + &expected_amount)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (0 != TALER_amount_cmp (wire_fee, + &expected_amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire fee missmatch to command %s\n", + cmd->label); + json_dumpf (json, stderr, 0); + TALER_TESTING_interpreter_fail (is); + return; + } + + /** + * Optionally checking: (1) wire-details for this transfer + * match the ones from a referenced "deposit" operation - + * or any operation that could provide wire-details. (2) + * Total amount for this transfer matches the one from any + * referenced command that could provide one. + */ + + if (NULL != tts->wire_details_reference) + { + const struct TALER_TESTING_Command *wire_details_cmd; + const json_t *wire_details; + struct GNUNET_HashCode h_wire_details; + + if (NULL == (wire_details_cmd + = TALER_TESTING_interpreter_lookup_command + (is, tts->wire_details_reference))) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (GNUNET_OK != + TALER_TESTING_get_trait_wire_details (wire_details_cmd, + 0, + &wire_details)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_assert + (GNUNET_OK == + TALER_JSON_merchant_wire_signature_hash (wire_details, + &h_wire_details)); + + if (0 != memcmp (&h_wire_details, + h_wire, + sizeof (struct GNUNET_HashCode))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire hash missmath to command %s\n", + cmd->label); + json_dumpf (json, stderr, 0); + TALER_TESTING_interpreter_fail (is); + return; + } + } + if (NULL != tts->total_amount_reference) + { + const struct TALER_TESTING_Command *total_amount_cmd; + const char *total_amount_from_reference_str; + struct TALER_Amount total_amount_from_reference; + + if (NULL == (total_amount_cmd + = TALER_TESTING_interpreter_lookup_command + (is, tts->total_amount_reference))) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_amount + (total_amount_cmd, 0, &total_amount_from_reference_str)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_assert (GNUNET_OK == TALER_string_to_amount + (total_amount_from_reference_str, + &total_amount_from_reference)); + + if (0 != TALER_amount_cmp (total_amount, + &total_amount_from_reference)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Amount missmath to command %s\n", + cmd->label); + json_dumpf (json, stderr, 0); + TALER_TESTING_interpreter_fail (is); + return; + } + } + } + TALER_TESTING_interpreter_next (is); +} + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command under execution. + * @param is the interpreter state. + */ +void +track_transfer_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + /* looking for a wtid to track .. */ + struct TrackTransferState *tts = cls; + struct TALER_WireTransferIdentifierRawP wtid; + const struct TALER_WireTransferIdentifierRawP *wtid_ptr; + + /* If no reference is given, we'll use a all-zeros + * WTID */ + memset (&wtid, 0, sizeof (wtid)); + wtid_ptr = &wtid; + + tts->is = is; + if (NULL != tts->wtid_reference) + { + const struct TALER_TESTING_Command *wtid_cmd; + + wtid_cmd = TALER_TESTING_interpreter_lookup_command + (tts->is, tts->wtid_reference); + + if (NULL == wtid_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (tts->is); + return; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_wtid + (wtid_cmd, tts->index, &wtid_ptr)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (tts->is); + return; + } + GNUNET_assert (NULL != wtid_ptr); + } + tts->tth = TALER_EXCHANGE_track_transfer (is->exchange, + wtid_ptr, + &track_transfer_cb, + tts); + GNUNET_assert (NULL != tts->tth); +} + +/** + * Make a "track transfer" CMD where no "expected"-arguments, + * except the HTTP response code, are given. The best use case + * is when what matters to check is the HTTP response code, e.g. + * when a bogus WTID was passed. + * + * @param label the command label + * @param wtid_reference reference to any command which can provide + * a wtid. If NULL is given, then a all zeroed WTID is + * used that will at 99.9999% probability NOT match any + * existing WTID known to the exchange. + * @param index index number of the WTID to track, in case there + * are multiple on offer. + * @param expected_response_code expected HTTP response code. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_track_transfer_empty + (const char *label, + const char *wtid_reference, + unsigned int index, + unsigned int expected_response_code) +{ + struct TrackTransferState *tts; + + tts = GNUNET_new (struct TrackTransferState); + + tts->wtid_reference = wtid_reference; + tts->index = index; + tts->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .cls = tts, + .label = label, + .run = &track_transfer_run, + .cleanup = &track_transfer_cleanup + }; + + return cmd; +} + +/** + * Make a "track transfer" command, specifying which amount and + * wire fee are expected. + * + * @param label the command label. + * @param wtid_reference reference to any command which can provide + * a wtid. Will be the one tracked. + * @param index in case there are multiple WTID offered, this + * parameter selects a particular one. + * @param expected_response_code expected HTTP response code. + * @param expected_amount how much money we expect being moved + * with this wire-transfer. + * @param expected_wire_fee expected wire fee. + * + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_track_transfer + (const char *label, + const char *wtid_reference, + unsigned int index, + unsigned int expected_response_code, + const char *expected_total_amount, + const char *expected_wire_fee) +{ + struct TrackTransferState *tts; + + tts = GNUNET_new (struct TrackTransferState); + + tts->wtid_reference = wtid_reference; + tts->index = index; + tts->expected_response_code = expected_response_code; + tts->expected_total_amount = expected_total_amount; + tts->expected_wire_fee = expected_wire_fee; + + struct TALER_TESTING_Command cmd = { + .cls = tts, + .label = label, + .run = &track_transfer_run, + .cleanup = &track_transfer_cleanup + }; + + return cmd; +} + +/* end of testing_api_cmd_track.c */ diff --git a/src/lib/testing_api_cmd_wire.c b/src/lib/testing_api_cmd_wire.c new file mode 100644 index 000000000..113545499 --- /dev/null +++ b/src/lib/testing_api_cmd_wire.c @@ -0,0 +1,238 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_cmd_wire.c + * @brief command for testing /wire. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_wire_lib.h" +#include "taler_testing_lib.h" + + +/** + * State for a "wire" CMD. + */ +struct WireState +{ + + /** + * Handle to the /wire operation. + */ + struct TALER_EXCHANGE_WireHandle *wh; + + /** + * Which wire-method we expect is offered by the exchange. + */ + const char *expected_method; + + /** + * Flag indicating if the expected method is actually + * offered. + */ + unsigned int method_found; + + /** + * Fee we expect is charged for this wire-transfer method. + */ + const char *expected_fee; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; +}; + + +/** + * Check whether the HTTP response code is acceptable, that + * the expected wire method is offered by the exchange, and + * that the wire fee is acceptable too. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param accounts_len length of the @a accounts array. + * @param accounts list of wire accounts of the exchange, + * NULL on error. + */ +static void +wire_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + unsigned int accounts_len, + const struct TALER_EXCHANGE_WireAccount *accounts) +{ + struct WireState *ws = cls; + struct TALER_TESTING_Command *cmd = \ + &ws->is->commands[ws->is->ip]; + struct TALER_Amount expected_fee; + + TALER_LOG_DEBUG ("Checking parsed /wire response\n"); + ws->wh = NULL; + if (ws->expected_response_code != http_status) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ws->is); + return; + } + + if (MHD_HTTP_OK == http_status) + { + for (unsigned int i=0;i<accounts_len;i++) + { + char *method; + + method = TALER_WIRE_payto_get_method (accounts[i].url); + if (0 == strcmp (ws->expected_method, + method)) + { + ws->method_found = GNUNET_OK; + if (NULL != ws->expected_fee) + { + GNUNET_assert + (GNUNET_OK == + TALER_string_to_amount (ws->expected_fee, + &expected_fee)); + const struct TALER_EXCHANGE_WireAggregateFees *waf; + for (waf = accounts[i].fees; + NULL != waf; + waf = waf->next) + { + if (0 != TALER_amount_cmp (&waf->wire_fee, + &expected_fee)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire fee missmatch to command %s\n", + cmd->label); + TALER_TESTING_interpreter_fail (ws->is); + GNUNET_free (method); + return; + } + } + } + } + TALER_LOG_DEBUG ("Freeing method '%s'\n", + method); + GNUNET_free (method); + } + if (GNUNET_OK != ws->method_found) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "/wire does not offer method '%s'\n", + ws->expected_method); + TALER_TESTING_interpreter_fail (ws->is); + return; + } + } + + TALER_TESTING_interpreter_next (ws->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +wire_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct WireState *ws = cls; + ws->is = is; + ws->wh = TALER_EXCHANGE_wire (is->exchange, + &wire_cb, + ws); +} + + +/** + * Cleanup the state of a "wire" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +wire_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct WireState *ws = cls; + + if (NULL != ws->wh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + ws->is->ip, + cmd->label); + TALER_EXCHANGE_wire_cancel (ws->wh); + ws->wh = NULL; + } + GNUNET_free (ws); +} + +/** + * Create a "wire" command. + * + * @param label the command label. + * @param exchange the exchange to connect to. + * @param expected_method which wire-transfer method is expected + * to be offered by the exchange. + * @param expected_fee the fee the exchange should charge. + * @param expected_response_code the HTTP response the exchange + * should return. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_wire (const char *label, + const char *expected_method, + const char *expected_fee, + unsigned int expected_response_code) +{ + struct WireState *ws; + + ws = GNUNET_new (struct WireState); + ws->expected_method = expected_method; + ws->expected_fee = expected_fee; + ws->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .cls = ws, + .label = label, + .run = &wire_run, + .cleanup = &wire_cleanup + }; + + return cmd; +} diff --git a/src/lib/testing_api_cmd_withdraw.c b/src/lib/testing_api_cmd_withdraw.c new file mode 100644 index 000000000..7224f65e4 --- /dev/null +++ b/src/lib/testing_api_cmd_withdraw.c @@ -0,0 +1,511 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/testing_api_cmd_withdraw.c + * @brief main interpreter loop for testcases + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <microhttpd.h> +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" +#include "backoff.h" + + + +/** + * State for a "withdraw" CMD. + */ +struct WithdrawState +{ + + /** + * Which reserve should we withdraw from? + */ + const char *reserve_reference; + + /** + * String describing the denomination value we should withdraw. + * A corresponding denomination key must exist in the exchange's + * offerings. Can be NULL if @e pk is set instead. + */ + struct TALER_Amount amount; + + /** + * If @e amount is NULL, this specifies the denomination key to + * use. Otherwise, this will be set (by the interpreter) to the + * denomination PK matching @e amount. + */ + const struct TALER_EXCHANGE_DenomPublicKey *pk; + + /** + * Exchange base URL. Only used as offered trait. + */ + char *exchange_url; + + /** + * Interpreter state (during command). + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Set (by the interpreter) to the exchange's signature over the + * coin's public key. + */ + struct TALER_DenominationSignature sig; + + /** + * Private key material of the coin, set by the interpreter. + */ + struct TALER_PlanchetSecretsP ps; + + /** + * Withdraw handle (while operation is running). + */ + struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Expected HTTP response code to the request. + */ + unsigned int expected_response_code; + + /** + * Was this command modified via + * #TALER_TESTING_cmd_withdraw_with_retry to + * enable retries? + */ + int do_retry; +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +withdraw_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #withdraw_run. + * + * @param cls a `struct WithdrawState` + */ +static void +do_retry (void *cls) +{ + struct WithdrawState *ws = cls; + + ws->retry_task = NULL; + withdraw_run (ws, + NULL, + ws->is); +} + + +/** + * "reserve withdraw" operation callback; checks that the + * response code is expected and store the exchange signature + * in the state. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param sig signature over the coin, NULL on error. + * @param full_response raw response. + */ +static void +reserve_withdraw_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_DenominationSignature *sig, + const json_t *full_response) +{ + struct WithdrawState *ws = cls; + struct TALER_TESTING_Interpreter *is = ws->is; + + ws->wsh = NULL; + if (ws->expected_response_code != http_status) + { + if (GNUNET_YES == ws->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (TALER_EC_WITHDRAW_INSUFFICIENT_FUNDS == ec) || + (TALER_EC_WITHDRAW_RESERVE_UNKNOWN == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying withdraw failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + ws->backoff = GNUNET_TIME_UNIT_ZERO; + else + ws->backoff = EXCHANGE_LIB_BACKOFF (ws->backoff); + ws->retry_task = GNUNET_SCHEDULER_add_delayed (ws->backoff, + &do_retry, + ws); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d to command %s in %s:%u\n", + http_status, + (int) ec, + TALER_TESTING_interpreter_get_current_label (is), + __FILE__, + __LINE__); + json_dumpf (full_response, + stderr, + 0); + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + switch (http_status) + { + case MHD_HTTP_OK: + if (NULL == sig) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + ws->sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature); + break; + case MHD_HTTP_FORBIDDEN: + /* nothing to check */ + break; + case MHD_HTTP_NOT_FOUND: + /* nothing to check */ + break; + default: + /* Unsupported status code (by test harness) */ + GNUNET_break (0); + break; + } + TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command being run, NULL when called from #do_retry() + * @param is interpreter state. + */ +static void +withdraw_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct WithdrawState *ws = cls; + const struct TALER_ReservePrivateKeyP *rp; + const struct TALER_TESTING_Command *create_reserve; + + (void) cmd; + create_reserve = TALER_TESTING_interpreter_lookup_command + (is, ws->reserve_reference); + if (NULL == create_reserve) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (GNUNET_OK != + TALER_TESTING_get_trait_reserve_priv (create_reserve, + 0, + &rp)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_planchet_setup_random (&ws->ps); + ws->is = is; + + ws->pk = TALER_TESTING_find_pk + (TALER_EXCHANGE_get_keys (is->exchange), + &ws->amount); + if (NULL == ws->pk) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to determine denomination key at %s\n", + cmd->label); + GNUNET_assert (0); + } + + ws->wsh = TALER_EXCHANGE_reserve_withdraw (is->exchange, + ws->pk, + rp, + &ws->ps, + &reserve_withdraw_cb, + ws); + if (NULL == ws->wsh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } +} + + +/** + * Free the state of a "withdraw" CMD, and possibly cancel + * a pending operation thereof. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +withdraw_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct WithdrawState *ws = cls; + + if (NULL != ws->wsh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %s did not complete\n", + cmd->label); + TALER_EXCHANGE_reserve_withdraw_cancel (ws->wsh); + ws->wsh = NULL; + } + if (NULL != ws->retry_task) + { + GNUNET_SCHEDULER_cancel (ws->retry_task); + ws->retry_task = NULL; + } + if (NULL != ws->sig.rsa_signature) + { + GNUNET_CRYPTO_rsa_signature_free (ws->sig.rsa_signature); + ws->sig.rsa_signature = NULL; + } + GNUNET_free_non_null (ws->exchange_url); + GNUNET_free (ws); +} + + +/** + * Offer internal data to a "withdraw" CMD state to other + * commands. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param index index number of the object to offer. + * + * @return #GNUNET_OK on success + */ +static int +withdraw_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct WithdrawState *ws = cls; + const struct TALER_TESTING_Command *reserve_cmd; + const struct TALER_ReservePrivateKeyP *reserve_priv; + + /* We offer the reserve key where these coins were withdrawn + * from. */ + reserve_cmd = TALER_TESTING_interpreter_lookup_command + (ws->is, ws->reserve_reference); + + if (NULL == reserve_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ws->is); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_reserve_priv + (reserve_cmd, 0, &reserve_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ws->is); + return GNUNET_SYSERR; + } + + ws->exchange_url = GNUNET_strdup + (TALER_EXCHANGE_get_base_url (ws->is->exchange)); + + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_coin_priv (0 /* only one coin */, + &ws->ps.coin_priv), + TALER_TESTING_make_trait_blinding_key (0 /* only one coin */, + &ws->ps.blinding_key), + TALER_TESTING_make_trait_denom_pub (0 /* only one coin */, + ws->pk), + TALER_TESTING_make_trait_denom_sig (0 /* only one coin */, + &ws->sig), + TALER_TESTING_make_trait_reserve_priv (0, + reserve_priv), + TALER_TESTING_make_trait_amount_obj (0, + &ws->amount), + TALER_TESTING_make_trait_url (0, ws->exchange_url), + + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Create a withdraw command, letting the caller specify + * the desired amount as string. + * + * @param label command label. + * @param amount how much we withdraw. + * @param expected_response_code which HTTP response code + * we expect from the exchange. + * + * @return the withdraw command to be executed by the interpreter. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_withdraw_amount + (const char *label, + const char *reserve_reference, + const char *amount, + unsigned int expected_response_code) +{ + struct WithdrawState *ws; + + ws = GNUNET_new (struct WithdrawState); + ws->reserve_reference = reserve_reference; + + if (GNUNET_OK != + TALER_string_to_amount (amount, + &ws->amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + amount, + label); + GNUNET_assert (0); + } + + ws->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .cls = ws, + .label = label, + .run = &withdraw_run, + .cleanup = &withdraw_cleanup, + .traits = &withdraw_traits + }; + + return cmd; +} + + +/** + * Create withdraw command, letting the caller specify the + * amount by a denomination key. + * + * @param label command label. + * @param exchange connection handle to the exchange. + * @param reserve_reference reference to the reserve to withdraw + * from; will provide reserve priv to sign the request. + * @param dk denomination public key. + * @param expected_response_code expected HTTP response code. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_withdraw_denomination + (const char *label, + const char *reserve_reference, + const struct TALER_EXCHANGE_DenomPublicKey *dk, + unsigned int expected_response_code) +{ + struct WithdrawState *ws; + + if (NULL == dk) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Denomination key not specified at %s\n", + label); + GNUNET_assert (0); + } + ws = GNUNET_new (struct WithdrawState); + ws->reserve_reference = reserve_reference; + ws->pk = dk; + ws->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .cls = ws, + .label = label, + .run = &withdraw_run, + .cleanup = &withdraw_cleanup, + .traits = &withdraw_traits + }; + + return cmd; +} + + +/** + * Modify a withdraw command to enable retries when the + * reserve is not yet full or we get other transient + * errors from the exchange. + * + * @param cmd a withdraw command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_withdraw_with_retry (struct TALER_TESTING_Command cmd) +{ + struct WithdrawState *ws; + + GNUNET_assert (&withdraw_run == cmd.run); + ws = cmd.cls; + ws->do_retry = GNUNET_YES; + return cmd; +} + + +/* end of testing_api_cmd_withdraw.c */ diff --git a/src/lib/testing_api_helpers.c b/src/lib/testing_api_helpers.c new file mode 100644 index 000000000..e56a57b92 --- /dev/null +++ b/src/lib/testing_api_helpers.c @@ -0,0 +1,1050 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_helpers.c + * @brief helper functions + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * Remove files from previous runs + * + * @param config_name configuration filename. + */ +void +TALER_TESTING_cleanup_files (const char *config_name) +{ + if (GNUNET_OK != + GNUNET_CONFIGURATION_parse_and_run (config_name, + &TALER_TESTING_cleanup_files_cfg, + NULL)) + exit (77); +} + + +/** + * Remove files from previous runs + * + * @param cls NULL + * @param cfg configuration + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_cleanup_files_cfg (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *dir; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + "exchange", + "keydir", + &dir)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "keydir"); + return GNUNET_SYSERR; + } + if (GNUNET_YES == + GNUNET_DISK_directory_test (dir, + GNUNET_NO)) + GNUNET_break (GNUNET_OK == + GNUNET_DISK_directory_remove (dir)); + GNUNET_free (dir); + // TODO: auditor-specific clean-up here! + return GNUNET_OK; +} + + +/** + * Run `taler-exchange-keyup`. + * + * @param config_filename configuration file to use + * @param output_filename where to write the output for the auditor + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_run_keyup (const char *config_filename, + const char *output_filename) +{ + struct GNUNET_OS_Process *proc; + + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-keyup", + "taler-exchange-keyup", + "-c", config_filename, + "-o", output_filename, + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-exchange-keyup`," + " is your PATH correct?\n"); + return GNUNET_SYSERR; + } + GNUNET_OS_process_wait (proc); + GNUNET_OS_process_destroy (proc); + return GNUNET_OK; +} + + +/** + * Run `taler-auditor-sign`. + * + * @param config_filename configuration file to use + * @param exchange_master_pub master public key of the exchange + * @param auditor_base_url what is the base URL of the auditor + * @param signdata_in where is the information from taler-exchange-keyup + * @param signdata_out where to write the output for the exchange + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_run_auditor_sign (const char *config_filename, + const char *exchange_master_pub, + const char *auditor_base_url, + const char *signdata_in, + const char *signdata_out) +{ + struct GNUNET_OS_Process *proc; + + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-auditor-sign", + "taler-auditor-sign", + "-c", config_filename, + "-u", auditor_base_url, + "-m", exchange_master_pub, + "-r", signdata_in, + "-o", signdata_out, + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-auditor-sign`," + " is your PATH correct?\n"); + return GNUNET_SYSERR; + } + GNUNET_OS_process_wait (proc); + GNUNET_OS_process_destroy (proc); + return GNUNET_OK; +} + + +/** + * Run `taler-auditor-exchange`. + * + * @param config_filename configuration file to use + * @param exchange_master_pub master public key of the exchange + * @param exchange_base_url what is the base URL of the exchange + * @param do_remove #GNUNET_NO to add exchange, #GNUNET_YES to remove + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_run_auditor_exchange (const char *config_filename, + const char *exchange_master_pub, + const char *exchange_base_url, + int do_remove) +{ + struct GNUNET_OS_Process *proc; + + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-auditor-exchange", + "taler-auditor-exchange", + "-c", config_filename, + "-u", exchange_base_url, + "-m", exchange_master_pub, + (GNUNET_YES == do_remove) + ? "-r" + : NULL, + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-auditor-exchange`," + " is your PATH correct?\n"); + return GNUNET_SYSERR; + } + GNUNET_OS_process_wait (proc); + GNUNET_OS_process_destroy (proc); + return GNUNET_OK; +} + + +/** + * Run `taler-exchange-dbinit -r` (reset exchange database). + * + * @param config_filename configuration file to use + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_exchange_db_reset (const char *config_filename) +{ + struct GNUNET_OS_Process *proc; + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-dbinit", + "taler-exchange-dbinit", + "-c", config_filename, + "-r", + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-exchange-dbinit`," + " is your PATH correct?\n"); + return GNUNET_NO; + } + if (GNUNET_SYSERR == + GNUNET_OS_process_wait_status (proc, + &type, + &code)) + { + GNUNET_break (0); + GNUNET_OS_process_destroy (proc); + return GNUNET_SYSERR; + } + GNUNET_OS_process_destroy (proc); + if ( (type == GNUNET_OS_PROCESS_EXITED) && + (0 != code) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to setup (exchange) database\n"); + return GNUNET_NO; + } + if ( (type != GNUNET_OS_PROCESS_EXITED) || + (0 != code) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected error running" + " `taler-exchange-dbinit'!\n"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Run `taler-auditor-dbinit -r` (reset auditor database). + * + * @param config_filename configuration file to use + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_auditor_db_reset (const char *config_filename) +{ + struct GNUNET_OS_Process *proc; + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-auditor-dbinit", + "taler-auditor-dbinit", + "-c", config_filename, + "-r", + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-auditor-dbinit`," + " is your PATH correct?\n"); + return GNUNET_NO; + } + if (GNUNET_SYSERR == + GNUNET_OS_process_wait_status (proc, + &type, + &code)) + { + GNUNET_break (0); + GNUNET_OS_process_destroy (proc); + return GNUNET_SYSERR; + } + GNUNET_OS_process_destroy (proc); + if ( (type == GNUNET_OS_PROCESS_EXITED) && + (0 != code) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to setup (auditor) database\n"); + return GNUNET_NO; + } + if ( (type != GNUNET_OS_PROCESS_EXITED) || + (0 != code) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected error running" + " `taler-auditor-dbinit'!\n"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Type of closure for + * #sign_keys_for_exchange. + */ +struct SignInfo +{ + /** + * Set to the base URL of the exchange. To be free'd + * by the caller. + */ + char *exchange_base_url; + + /** + * Set to the auditor's base URL. To be free'd by the caller. + */ + char *auditor_base_url; + + /** + * Name of the configuration file to use. + */ + const char *config_filename; + + /** + * Must be set to input file with the data to be signed before + * calling #TALER_TESTING_sign_keys_for_exchange. + */ + const char *auditor_sign_input_filename; +}; + + +/** + * Sign the keys for an exchange given configuration @a cfg. + * The information to be signed must be in a file "auditor.in". + * + * @param cls[in,out] a `struct SignInfo` with + * further paramters + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +static int +sign_keys_for_exchange (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct SignInfo *si = cls; + char *test_home_dir; + char *signed_keys_out; + char *exchange_master_pub; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "exchange", + "BASE_URL", + &si->exchange_base_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + "exchange", + "BASE_URL"); + si->exchange_base_url = NULL; + return GNUNET_NO; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "auditor", + "BASE_URL", + &si->auditor_base_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + "auditor", + "BASE_URL"); + GNUNET_free (si->exchange_base_url); + si->exchange_base_url = NULL; + si->auditor_base_url = NULL; + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + "paths", + "TALER_TEST_HOME", + &test_home_dir)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "paths", + "TALER_TEST_HOME"); + GNUNET_free (si->exchange_base_url); + GNUNET_free (si->auditor_base_url); + si->exchange_base_url = NULL; + si->auditor_base_url = NULL; + return GNUNET_SYSERR; + } + + GNUNET_asprintf (&signed_keys_out, + "%s/.local/share/taler/auditors/auditor.out", + test_home_dir); + GNUNET_free (test_home_dir); + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "exchange", + "MASTER_PUBLIC_KEY", + &exchange_master_pub)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "MASTER_PUBLIC_KEY"); + GNUNET_free (si->exchange_base_url); + GNUNET_free (si->auditor_base_url); + si->exchange_base_url = NULL; + si->auditor_base_url = NULL; + GNUNET_free (signed_keys_out); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_TESTING_run_auditor_exchange (si->config_filename, + exchange_master_pub, + si->exchange_base_url, + GNUNET_NO)) + { + GNUNET_free (si->exchange_base_url); + GNUNET_free (si->auditor_base_url); + si->exchange_base_url = NULL; + si->auditor_base_url = NULL; + return GNUNET_NO; + } + + if (GNUNET_OK != + TALER_TESTING_run_auditor_sign (si->config_filename, + exchange_master_pub, + si->auditor_base_url, + si->auditor_sign_input_filename, + signed_keys_out)) + { + GNUNET_free (si->exchange_base_url); + GNUNET_free (si->auditor_base_url); + si->exchange_base_url = NULL; + si->auditor_base_url = NULL; + return GNUNET_NO; + } + GNUNET_free (signed_keys_out); + GNUNET_free (exchange_master_pub); + return GNUNET_OK; +} + + +/** + * Prepare launching an exchange. Checks that the configured + * port is available, runs taler-exchange-keyup, + * taler-auditor-sign and taler-exchange-dbinit. Does NOT + * launch the exchange process itself. + * + * @param config_filename configuration file to use + * @param auditor_base_url[out] will be set to the auditor base url, + * if the config has any; otherwise it will be set to + * NULL. + * @param exchange_base_url[out] will be set to the exchange base url, + * if the config has any; otherwise it will be set to + * NULL. + * @return #GNUNET_OK on success, #GNUNET_NO if test should be + * skipped, #GNUNET_SYSERR on test failure + */ +int +TALER_TESTING_prepare_exchange (const char *config_filename, + char **auditor_base_url, + char **exchange_base_url) +{ + struct SignInfo si = { + .config_filename = config_filename, + .exchange_base_url = NULL, + .auditor_base_url = NULL, + .auditor_sign_input_filename = "auditor.in" + }; + + if (GNUNET_OK != + TALER_TESTING_run_keyup (config_filename, + si.auditor_sign_input_filename)) + return GNUNET_NO; + if (GNUNET_OK != + TALER_TESTING_exchange_db_reset (config_filename)) + return GNUNET_NO; + if (GNUNET_OK != + TALER_TESTING_auditor_db_reset (config_filename)) + return GNUNET_NO; + if (GNUNET_OK != + GNUNET_CONFIGURATION_parse_and_run (config_filename, + &sign_keys_for_exchange, + &si)) + return GNUNET_NO; + *exchange_base_url = si.exchange_base_url; + *auditor_base_url = si.auditor_base_url; + return GNUNET_OK; +} + + +/** + * 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 + */ +const struct TALER_EXCHANGE_DenomPublicKey * +TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_Amount *amount) +{ + struct GNUNET_TIME_Absolute now; + struct TALER_EXCHANGE_DenomPublicKey *pk; + char *str; + + now = GNUNET_TIME_absolute_get (); + for (unsigned int i=0;i<keys->num_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 (unsigned int i=0;i<keys->num_denom_keys;i++) + { + pk = &keys->denom_keys[i]; + if ( (0 == TALER_amount_cmp (amount, + &pk->value)) && + ( (now.abs_value_us < pk->valid_from.abs_value_us) || + (now.abs_value_us > + pk->withdraw_valid_until.abs_value_us) ) ) + { + GNUNET_log + (GNUNET_ERROR_TYPE_WARNING, + "Have denomination key for `%s', but with wrong" + " expiration range %llu vs [%llu,%llu)\n", + str, + (unsigned long long) now.abs_value_us, + (unsigned long long) pk->valid_from.abs_value_us, + (unsigned long long) + pk->withdraw_valid_until.abs_value_us); + GNUNET_free (str); + return NULL; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No denomination key for amount %s found\n", + str); + GNUNET_free (str); + return NULL; +} + + +/** + * Wait for the exchange to have started. Waits for at + * most 10s, after that returns 77 to indicate an error. + * + * @param base_url what URL should we expect the exchange + * to be running at + * @return 0 on success + */ +int +TALER_TESTING_wait_exchange_ready (const char *base_url) +{ + char *wget_cmd; + unsigned int iter; + + GNUNET_asprintf (&wget_cmd, + "wget -q -t 1 -T 1 %skeys" + " -o /dev/null -O /dev/null", + base_url); // make sure ends with '/' + /* give child time to start and bind against the socket */ + fprintf (stderr, + "Waiting for `taler-exchange-httpd' to be ready\n"); + iter = 0; + do + { + if (10 == iter) + { + fprintf (stderr, + "Failed to launch `taler-exchange-httpd' (or `wget')\n"); + GNUNET_free (wget_cmd); + return 77; + } + fprintf (stderr, ".\n"); + sleep (1); + iter++; + } + while (0 != system (wget_cmd)); + GNUNET_free (wget_cmd); + return 0; +} + + +/** + * Wait for the auditor to have started. Waits for at + * most 10s, after that returns 77 to indicate an error. + * + * @param base_url what URL should we expect the auditor + * to be running at + * @return 0 on success + */ +int +TALER_TESTING_wait_auditor_ready (const char *base_url) +{ + char *wget_cmd; + unsigned int iter; + + GNUNET_asprintf (&wget_cmd, + "wget -q -t 1 -T 1 %sversion" + " -o /dev/null -O /dev/null", + base_url); // make sure ends with '/' + /* give child time to start and bind against the socket */ + fprintf (stderr, + "Waiting for `taler-auditor-httpd' to be ready\n"); + iter = 0; + do + { + if (10 == iter) + { + fprintf (stderr, + "Failed to launch `taler-auditor-httpd' (or `wget')\n"); + GNUNET_free (wget_cmd); + return 77; + } + fprintf (stderr, ".\n"); + sleep (1); + iter++; + } + while (0 != system (wget_cmd)); + GNUNET_free (wget_cmd); + return 0; +} + + +/** + * Initialize scheduler loop and curl context for the testcase + * including starting and stopping the exchange using the given + * configuration file. + * + * @param main_cb routine containing all the commands to run. + * @param main_cb_cls closure for @a main_cb, typically NULL. + * @param config_file configuration file for the test-suite. + * + * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise. + * non-#GNUNET_OK codes are #GNUNET_SYSERR most of the + * time. + */ +int +TALER_TESTING_setup_with_exchange (TALER_TESTING_Main main_cb, + void *main_cb_cls, + const char *config_filename) +{ + struct TALER_TESTING_SetupContext setup_ctx = { + .config_filename = config_filename, + .main_cb = main_cb, + .main_cb_cls = main_cb_cls + }; + int result; + + if (GNUNET_OK != + (result = GNUNET_CONFIGURATION_parse_and_run (config_filename, + &TALER_TESTING_setup_with_exchange_cfg, + &setup_ctx))) + return result; + return GNUNET_OK; +} + + +/** + * Initialize scheduler loop and curl context for the test case + * including starting and stopping the exchange using the given + * configuration file. + * + * @param cls must be a `struct TALER_TESTING_SetupContext *` + * @param cfg configuration to use. + * @return #GNUNET_OK if no errors occurred. + */ +int +TALER_TESTING_setup_with_exchange_cfg (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + const struct TALER_TESTING_SetupContext *setup_ctx = cls; + struct GNUNET_OS_Process *exchanged; + unsigned long long port; + char *serve; + char *base_url; + int result; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "exchange", + "SERVE", + &serve)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "SERVE"); + return GNUNET_NO; + } + + if (0 == strcmp ("tcp", serve)) + { + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "exchange", + "PORT", + &port)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "PORT"); + GNUNET_free (serve); + return GNUNET_NO; + } + + if (GNUNET_OK != + GNUNET_NETWORK_test_port_free (IPPROTO_TCP, + (uint16_t) port)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Required port %llu not available, skipping.\n", + port); + GNUNET_free (serve); + return GNUNET_NO; + } + } + GNUNET_free (serve); + exchanged = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-httpd", + "taler-exchange-httpd", + "-c", setup_ctx->config_filename, + "-i", + NULL); + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "exchange", + "BASE_URL", + &base_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "BASE_URL"); + return GNUNET_NO; + } + + if (0 != TALER_TESTING_wait_exchange_ready (base_url)) + { + GNUNET_free (base_url); + return 77; + } + GNUNET_free (base_url); + + /* NOTE: this call blocks. */ + result = TALER_TESTING_setup (setup_ctx->main_cb, + setup_ctx->main_cb_cls, + setup_ctx->config_filename, + exchanged, + GNUNET_YES); + GNUNET_break (0 == + GNUNET_OS_process_kill (exchanged, + SIGTERM)); + GNUNET_break (GNUNET_OK == + GNUNET_OS_process_wait (exchanged)); + GNUNET_OS_process_destroy (exchanged); + return result; +} + + +/** + * Initialize scheduler loop and curl context for the test case + * including starting and stopping the auditor and exchange using the + * given configuration file. + * + * @param cls must be a `struct TALER_TESTING_SetupContext *` + * @param cfg configuration to use. + * @return #GNUNET_OK if no errors occurred. + */ +int +TALER_TESTING_setup_with_auditor_and_exchange_cfg (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + const struct TALER_TESTING_SetupContext *setup_ctx = cls; + struct GNUNET_OS_Process *auditord; + unsigned long long port; + char *serve; + char *base_url; + int result; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "auditor", + "SERVE", + &serve)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "auditor", + "SERVE"); + return GNUNET_NO; + } + + if (0 == strcmp ("tcp", serve)) + { + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "auditor", + "PORT", + &port)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "auditor", + "PORT"); + GNUNET_free (serve); + return GNUNET_NO; + } + + if (GNUNET_OK != + GNUNET_NETWORK_test_port_free (IPPROTO_TCP, + (uint16_t) port)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Required port %llu not available, skipping.\n", + port); + GNUNET_free (serve); + return GNUNET_NO; + } + } + GNUNET_free (serve); + auditord = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-auditor-httpd", + "taler-auditor-httpd", + "-c", setup_ctx->config_filename, + NULL); + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "auditor", + "BASE_URL", + &base_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "auditor", + "BASE_URL"); + return GNUNET_NO; + } + + if (0 != TALER_TESTING_wait_auditor_ready (base_url)) + { + GNUNET_free (base_url); + GNUNET_break (0 == + GNUNET_OS_process_kill (auditord, + SIGTERM)); + GNUNET_break (GNUNET_OK == + GNUNET_OS_process_wait (auditord)); + GNUNET_OS_process_destroy (auditord); + return 77; + } + GNUNET_free (base_url); + + /* NOTE: this call blocks. */ + result = TALER_TESTING_setup_with_exchange_cfg ((void *) setup_ctx, + cfg); + GNUNET_break (0 == + GNUNET_OS_process_kill (auditord, + SIGTERM)); + GNUNET_break (GNUNET_OK == + GNUNET_OS_process_wait (auditord)); + GNUNET_OS_process_destroy (auditord); + return result; +} + + +/** + * Initialize scheduler loop and curl context for the test case + * including starting and stopping the auditor and exchange using the + * given configuration file. + * + * @param main_cb main method. + * @param main_cb_cls main method closure. + * @param config_filename configuration file name. Is is used + * by both this function and the exchange itself. In the + * first case it gives out the exchange port number and + * the exchange base URL so as to check whether the port + * is available and the exchange responds when requested + * at its base URL. + * @return #GNUNET_OK if no errors occurred. + */ +int +TALER_TESTING_setup_with_auditor_and_exchange (TALER_TESTING_Main main_cb, + void *main_cb_cls, + const char *config_file) +{ + struct TALER_TESTING_SetupContext setup_ctx = { + .config_filename = config_file, + .main_cb = main_cb, + .main_cb_cls = main_cb_cls + }; + + return GNUNET_CONFIGURATION_parse_and_run (config_file, + &TALER_TESTING_setup_with_auditor_and_exchange_cfg, + &setup_ctx); +} + + +/** + * Test port in URL string for availability. + */ +int +TALER_TESTING_url_port_free (const char *url) +{ + const char *port; + long pnum; + + port = strrchr (url, + (unsigned char) ':'); + if (NULL == port) + pnum = 80; + else + pnum = strtol (port + 1, NULL, 10); + if (GNUNET_OK != + GNUNET_NETWORK_test_port_free (IPPROTO_TCP, + pnum)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Port %u not available.\n", + (unsigned int) pnum); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Allocate and return a piece of wire-details. Combines + * the @a account_no and the @a bank_url to a + * @a payto://-URL and adds some salt to create the JSON. + * + * @param account_no account number + * @param bank_url the bank_url (FIXME/WARNING: shouldn't this be a _hostname_ ??) + * @return JSON describing the account, including the + * payto://-URL of the account, must be manually decref'd + */ +json_t * +TALER_TESTING_make_wire_details (unsigned long long account_no, + const char *bank_url) +{ + char *payto; + json_t *ret; + + GNUNET_asprintf (&payto, + "payto://x-taler-bank/%s/%llu", + bank_url, + account_no); + ret = json_pack ("{s:s, s:s}", + "url", payto, + "salt", "test-salt (must be constant for aggregation tests)"); + GNUNET_free (payto); + return ret; +} + + +/** + * Prepare launching a fakebank. Check that the configuration + * file has the right option, and that the port is available. + * If everything is OK, return the configured URL of the fakebank. + * + * @param config_filename configuration file to use + * @param config_section which account to use (must match x-taler-bank) + * @return NULL on error, fakebank URL otherwise + */ +char * +TALER_TESTING_prepare_fakebank (const char *config_filename, + const char *config_section) +{ + struct GNUNET_CONFIGURATION_Handle *cfg; + char *payto_url; + char *fakebank_url; + const char *start; + const char *end; + + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != GNUNET_CONFIGURATION_load (cfg, + config_filename)) + return NULL; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + config_section, + "URL", + &payto_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + config_section, + "URL"); + GNUNET_CONFIGURATION_destroy (cfg); + return NULL; + } + GNUNET_CONFIGURATION_destroy (cfg); + if (0 != strncasecmp (payto_url, + "payto://x-taler-bank/", + strlen ("payto://x-taler-bank/"))) + { + GNUNET_log_config_invalid + (GNUNET_ERROR_TYPE_WARNING, + config_section, + "URL", + "expected `x-taler-bank' payto://-URL"); + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_free (payto_url); + return NULL; + } + start = &payto_url [strlen ("payto://x-taler-bank/")]; + end = strchr (start, + (unsigned char) '/'); + if (NULL == end) + end = &start[strlen (start)]; + fakebank_url = GNUNET_strndup (start, + end - start); + GNUNET_free (payto_url); + if (GNUNET_OK != + TALER_TESTING_url_port_free (fakebank_url)) + { + GNUNET_free (fakebank_url); + return NULL; + } + return fakebank_url; +} + +/* end of testing_api_helpers.c */ diff --git a/src/lib/testing_api_loop.c b/src/lib/testing_api_loop.c new file mode 100644 index 000000000..3e7e36432 --- /dev/null +++ b/src/lib/testing_api_loop.c @@ -0,0 +1,825 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_loop.c + * @brief main interpreter loop for testcases + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" +#include "taler_fakebank_lib.h" + +/** + * Pipe used to communicate child death via signal. + * Must be global, as used in signal handler! + */ +static struct GNUNET_DISK_PipeHandle *sigpipe; + +/** + * Lookup command by label. + * + * @param is interpreter state to search + * @param label label to look for + * @return NULL if command was not found + */ +const struct TALER_TESTING_Command * +TALER_TESTING_interpreter_lookup_command + (struct TALER_TESTING_Interpreter *is, + const char *label) +{ + if (NULL == label) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Attempt to lookup command for empty label\n"); + return NULL; + } + /* Search backwards as we most likely reference recent commands */ + for (int i=is->ip; i >= 0; i--) + { + const struct TALER_TESTING_Command *cmd = &is->commands[i]; + + /* Give precedence to top-level commands. */ + if ( (NULL != cmd->label) && + (0 == strcmp (cmd->label, + label)) ) + return cmd; + + if (TALER_TESTING_cmd_is_batch (cmd)) + { +#define BATCH_INDEX 1 + struct TALER_TESTING_Command *batch; + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_cmd (cmd, + BATCH_INDEX, + &batch)); + for (unsigned int j=0; + NULL != (cmd = &batch[j])->label; + j++) + { + if ( (NULL != cmd->label) && + (0 == strcmp (cmd->label, + label)) ) + return cmd; + } + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Command not found: %s\n", + label); + return NULL; + +} + + +/** + * Obtain main execution context for the main loop. + */ +struct GNUNET_CURL_Context * +TALER_TESTING_interpreter_get_context + (struct TALER_TESTING_Interpreter *is) +{ + return is->ctx; +} + + +struct TALER_FAKEBANK_Handle * +TALER_TESTING_interpreter_get_fakebank + (struct TALER_TESTING_Interpreter *is) +{ + return is->fakebank; +} + + +/** + * Run tests starting the "fakebank" first. The "fakebank" + * is a C minimalist version of the human-oriented Python bank, + * which is also part of the Taler project. + * + * @param is pointer to the interpreter state + * @param commands the list of commands to execute + * @param bank_url the url the fakebank is supposed to run on + */ +void +TALER_TESTING_run_with_fakebank + (struct TALER_TESTING_Interpreter *is, + struct TALER_TESTING_Command *commands, + const char *bank_url) +{ + const char *port; + long pnum; + + port = strrchr (bank_url, + (unsigned char) ':'); + if (NULL == port) + pnum = 80; + else + pnum = strtol (port + 1, NULL, 10); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting Fakebank on port %u (%s)\n", + (unsigned int) pnum, + bank_url); + is->fakebank = TALER_FAKEBANK_start ((uint16_t) pnum); + if (NULL == is->fakebank) + { + GNUNET_break (0); + is->result = GNUNET_SYSERR; + return; + } + TALER_TESTING_run (is, + commands); +} + + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct InterpreterState` + */ +static void +interpreter_run (void *cls); + + +/** + * Current command is done, run the next one. + */ +void +TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *is) +{ + static unsigned long long ipc; + static struct GNUNET_TIME_Absolute last_report; + struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + + if (GNUNET_SYSERR == is->result) + return; /* ignore, we already failled! */ + if (TALER_TESTING_cmd_is_batch (cmd)) + { +#define CURRENT_BATCH_SUBCMD_INDEX 0 + struct TALER_TESTING_Command *sub_cmd; + + GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_cmd + (cmd, CURRENT_BATCH_SUBCMD_INDEX, &sub_cmd)); + + if (NULL == sub_cmd->label) + is->ip++; + } + else + is->ip++; + if (0 == (ipc % 1000)) + { + if (0 != ipc) + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Interpreter executed 1000 instructions in %s\n", + GNUNET_STRINGS_relative_time_to_string (GNUNET_TIME_absolute_get_duration (last_report), + GNUNET_YES)); + last_report = GNUNET_TIME_absolute_get (); + } + ipc++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Current command failed, clean up and fail the test case. + * + * @param is interpreter of the test + */ +void +TALER_TESTING_interpreter_fail + (struct TALER_TESTING_Interpreter *is) +{ + struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed at command `%s'\n", + cmd->label); + while (TALER_TESTING_cmd_is_batch (cmd)) + { + cmd = TALER_TESTING_cmd_batch_get_current (cmd); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Batch is at command `%s'\n", + cmd->label); + } + is->result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Create command array terminator. + * + * @return a end-command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_end (void) +{ + static struct TALER_TESTING_Command cmd; + cmd.label = NULL; + + return cmd; +} + + +/** + * Obtain current label. + */ +const char * +TALER_TESTING_interpreter_get_current_label + (struct TALER_TESTING_Interpreter *is) +{ + struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + + return cmd->label; +} + + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct TALER_TESTING_Interpreter` + */ +static void +interpreter_run (void *cls) +{ + struct TALER_TESTING_Interpreter *is = cls; + struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + + is->task = NULL; + + if (NULL == cmd->label) + { + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Running command END\n"); + is->result = GNUNET_OK; + GNUNET_SCHEDULER_shutdown (); + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Running command `%s'\n", + cmd->label); + + cmd->run (cmd->cls, + cmd, + is); +} + + +/** + * Function run when the test terminates (good or bad). + * Cleans up our state. + * + * @param cls the interpreter state. + */ +static void +do_shutdown (void *cls) +{ + struct TALER_TESTING_Interpreter *is = cls; + struct TALER_TESTING_Command *cmd; + const char *label; + + label = is->commands[is->ip].label; + if (NULL == label) + label = "END"; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Executing shutdown at `%s'\n", + label); + + for (unsigned int j=0;NULL != (cmd = &is->commands[j])->label;j++) + cmd->cleanup (cmd->cls, + cmd); + if (NULL != is->exchange) + { + TALER_EXCHANGE_disconnect (is->exchange); + is->exchange = NULL; + } + if (NULL != is->task) + { + GNUNET_SCHEDULER_cancel (is->task); + is->task = NULL; + } + if (NULL != is->ctx) + { + GNUNET_CURL_fini (is->ctx); + is->ctx = NULL; + } + if (NULL != is->rc) + { + GNUNET_CURL_gnunet_rc_destroy (is->rc); + is->rc = NULL; + } + if (NULL != is->timeout_task) + { + GNUNET_SCHEDULER_cancel (is->timeout_task); + is->timeout_task = NULL; + } + if (NULL != is->child_death_task) + { + GNUNET_SCHEDULER_cancel (is->child_death_task); + is->child_death_task = NULL; + } + if (NULL != is->fakebank) + { + TALER_FAKEBANK_stop (is->fakebank); + is->fakebank = NULL; + } + GNUNET_free_non_null (is->commands); +} + + +/** + * Function run when the test terminates (good or bad) with timeout. + * + * @param cls NULL + */ +static void +do_timeout (void *cls) +{ + struct TALER_TESTING_Interpreter *is = cls; + + is->timeout_task = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Terminating test due to timeout\n"); + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Task triggered whenever we receive a SIGCHLD (child + * process died). + * + * @param cls closure + */ +static void +maint_child_death (void *cls) +{ + struct TALER_TESTING_Interpreter *is = cls; + struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + const struct GNUNET_DISK_FileHandle *pr; + + struct GNUNET_OS_Process **processp; + char c[16]; + + if (TALER_TESTING_cmd_is_batch (cmd)) + { + struct TALER_TESTING_Command *batch_cmd; + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_get_trait_cmd + (cmd, 0, &batch_cmd)); /* bad? */ + cmd = batch_cmd; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got SIGCHLD for `%s'.\n", + cmd->label); + + is->child_death_task = NULL; + pr = GNUNET_DISK_pipe_handle (sigpipe, + GNUNET_DISK_PIPE_END_READ); + GNUNET_break (0 < + GNUNET_DISK_file_read (pr, + &c, + sizeof (c))); + if (GNUNET_OK != + TALER_TESTING_get_trait_process (cmd, + 0, + &processp)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got the dead child process handle" + ", waiting for termination ...\n"); + + GNUNET_OS_process_wait (*processp); + GNUNET_OS_process_destroy (*processp); + *processp = NULL; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "... definitively terminated\n"); + + if (GNUNET_OK == is->reload_keys) + { + if (NULL == is->exchanged) + { + GNUNET_break (0); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Triggering key state reload at exchange\n"); + GNUNET_break (0 == GNUNET_OS_process_kill + (is->exchanged, SIGUSR1)); + sleep (5); /* make sure signal was received and processed */ + } + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Dead child, go on with next command.\n"); + TALER_TESTING_interpreter_next (is); +} + + +/** + * Wait until we receive SIGCHLD signal. + * Then obtain the process trait of the current + * command, wait on the the zombie and continue + * with the next command. + */ +void +TALER_TESTING_wait_for_sigchld + (struct TALER_TESTING_Interpreter *is) +{ + const struct GNUNET_DISK_FileHandle *pr; + + GNUNET_assert (NULL == is->child_death_task); + pr = GNUNET_DISK_pipe_handle (sigpipe, + GNUNET_DISK_PIPE_END_READ); + is->child_death_task + = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + pr, + &maint_child_death, + is); +} + + +/** + * Run the testsuite. Note, CMDs are copied into + * the interpreter state because they are _usually_ + * defined into the "run" method that returns after + * having scheduled the test interpreter. + * + * @param is the interpreter state + * @param commands the list of command to execute + * @param timeout how long to wait + */ +void +TALER_TESTING_run2 (struct TALER_TESTING_Interpreter *is, + struct TALER_TESTING_Command *commands, + struct GNUNET_TIME_Relative timeout) +{ + unsigned int i; + + if (NULL != is->timeout_task) + { + GNUNET_SCHEDULER_cancel (is->timeout_task); + is->timeout_task = NULL; + } + /* get the number of commands */ + for (i=0;NULL != commands[i].label;i++) ; + is->commands = GNUNET_new_array (i + 1, + struct TALER_TESTING_Command); + memcpy (is->commands, + commands, + sizeof (struct TALER_TESTING_Command) * i); + is->timeout_task = GNUNET_SCHEDULER_add_delayed + (timeout, + &do_timeout, + is); + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, is); + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, is); +} + + +/** + * Run the testsuite. Note, CMDs are copied into + * the interpreter state because they are _usually_ + * defined into the "run" method that returns after + * having scheduled the test interpreter. + * + * @param is the interpreter state + * @param commands the list of command to execute + */ +void +TALER_TESTING_run (struct TALER_TESTING_Interpreter *is, + struct TALER_TESTING_Command *commands) +{ + TALER_TESTING_run2 (is, + commands, + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, + 5)); +} + + +/** + * Information used by the wrapper around the main + * "run" method. + */ +struct MainContext +{ + /** + * Main "run" method. + */ + TALER_TESTING_Main main_cb; + + /** + * Closure for @e main_cb. + */ + void *main_cb_cls; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Configuration filename. The wrapper uses it to fetch + * the exchange port number; We could have passed the port + * number here, but having the config filename seems more + * generic. + */ + const char *config_filename; + + /** + * URL of the exchange. + */ + char *exchange_url; + +}; + + +/** + * Signal handler called for SIGCHLD. Triggers the + * respective handler by writing to the trigger pipe. + */ +static void +sighandler_child_death () +{ + static char c; + int old_errno = errno; /* back-up errno */ + + GNUNET_break (1 == GNUNET_DISK_file_write + (GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_WRITE), + &c, sizeof (c))); + errno = old_errno; /* restore errno */ +} + + +/** + * Called once a connection to the exchange has been + * established. + * + * @param cls closure, typically, the "run" method containing + * all the commands to be run, and a closure for it. + * @param keys the exchange's keys. + * @param compat protocol compatibility information. + */ +static void +cert_cb (void *cls, + const struct TALER_EXCHANGE_Keys *keys, + enum TALER_EXCHANGE_VersionCompatibility compat) +{ + struct MainContext *main_ctx = cls; + struct TALER_TESTING_Interpreter *is = main_ctx->is; + + if (NULL == keys) + { + if (GNUNET_NO == is->working) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Got NULL response for /keys during startup, retrying!\n"); + TALER_EXCHANGE_disconnect (is->exchange); + GNUNET_assert (NULL != + (is->exchange = TALER_EXCHANGE_connect (is->ctx, + main_ctx->exchange_url, + &cert_cb, + main_ctx, + TALER_EXCHANGE_OPTION_END))); + return; + } + else + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Got NULL response for /keys during execution!\n"); + + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got %d DK from /keys\n", + keys->num_denom_keys); + } + is->key_generation++; + is->keys = keys; + + /* /keys has been called for some reason and + * the interpreter is already running. */ + if (GNUNET_YES == is->working) + return; + + is->working = GNUNET_YES; + + /* Very first start of tests, call "run()" */ + if (1 == is->key_generation) + { + main_ctx->main_cb (main_ctx->main_cb_cls, + is); + return; + } + + /* Tests already started, just trigger the + * next command. */ + GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Initialize scheduler loop and curl context for the testcase, + * and responsible to run the "run" method. + * + * @param cls closure, typically the "run" method, the + * interpreter state and a closure for "run". + */ +static void +main_wrapper_exchange_agnostic (void *cls) +{ + struct MainContext *main_ctx = cls; + + main_ctx->main_cb (main_ctx->main_cb_cls, + main_ctx->is); +} + + +/** + * Function run when the test is aborted before we launch the actual + * interpreter. Cleans up our state. + * + * @param cls the main context + */ +static void +do_abort (void *cls) +{ + struct MainContext *main_ctx = cls; + struct TALER_TESTING_Interpreter *is = main_ctx->is; + + is->timeout_task = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Executing abort prior to interpreter launch\n"); + if (NULL != is->exchange) + { + TALER_EXCHANGE_disconnect (is->exchange); + is->exchange = NULL; + } +} + + +/** + * Initialize scheduler loop and curl context for the testcase, + * and responsible to run the "run" method. + * + * @param cls a `struct MainContext *` + * @param cfg configuration to use + */ +static int +main_exchange_connect_with_cfg (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct MainContext *main_ctx = cls; + struct TALER_TESTING_Interpreter *is = main_ctx->is; + char *exchange_url; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "exchange", + "BASE_URL", + &exchange_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "BASE_URL"); + return GNUNET_SYSERR; + } + main_ctx->exchange_url = exchange_url; + is->cfg = cfg; + is->timeout_task = GNUNET_SCHEDULER_add_shutdown (&do_abort, + main_ctx); + GNUNET_break (NULL != + (is->exchange = TALER_EXCHANGE_connect (is->ctx, + exchange_url, + &cert_cb, + main_ctx, + TALER_EXCHANGE_OPTION_END))); + is->cfg = NULL; + return GNUNET_OK; +} + + +/** + * Initialize scheduler loop and curl context for the testcase, + * and responsible to run the "run" method. + * + * @param cls a `struct MainContext *` + */ +static void +main_wrapper_exchange_connect (void *cls) +{ + struct MainContext *main_ctx = cls; + + GNUNET_break (GNUNET_OK == + GNUNET_CONFIGURATION_parse_and_run (main_ctx->config_filename, + &main_exchange_connect_with_cfg, + main_ctx)); +} + + +/** + * Install signal handlers plus schedules the main wrapper + * around the "run" method. + * + * @param main_cb the "run" method which contains all the + * commands. + * @param main_cb_cls a closure for "run", typically NULL. + * @param config_filename configuration filename. + * @param exchanged exchange process handle: will be put in the + * state as some commands - e.g. revoke - need to send + * signal to it, for example to let it know to reload the + * key state.. if NULL, the interpreter will run without + * trying to connect to the exchange first. + * @param exchange_connect #GNUNET_YES if the test should connect + * to the exchange, #GNUNET_NO otherwise + * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise. + * non-GNUNET_OK codes are #GNUNET_SYSERR most of the + * times. + */ +int +TALER_TESTING_setup (TALER_TESTING_Main main_cb, + void *main_cb_cls, + const char *config_filename, + struct GNUNET_OS_Process *exchanged, + int exchange_connect) +{ + struct TALER_TESTING_Interpreter is; + struct MainContext main_ctx = { + .main_cb = main_cb, + .main_cb_cls = main_cb_cls, + /* needed to init the curl ctx */ + .is = &is, + /* needed to read values like exchange port + * number to construct the exchange url.*/ + .config_filename = config_filename + }; + struct GNUNET_SIGNAL_Context *shc_chld; + + memset (&is, + 0, + sizeof (is)); + is.exchanged = exchanged; + sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, + GNUNET_NO, GNUNET_NO); + GNUNET_assert (NULL != sigpipe); + shc_chld = GNUNET_SIGNAL_handler_install + (GNUNET_SIGCHLD, + &sighandler_child_death); + is.ctx = GNUNET_CURL_init + (&GNUNET_CURL_gnunet_scheduler_reschedule, + &is.rc); + GNUNET_assert (NULL != is.ctx); + is.rc = GNUNET_CURL_gnunet_rc_create (is.ctx); + + /* Blocking */ + + if (GNUNET_YES == exchange_connect) + GNUNET_SCHEDULER_run (&main_wrapper_exchange_connect, + &main_ctx); + else + GNUNET_SCHEDULER_run (&main_wrapper_exchange_agnostic, + &main_ctx); + if (NULL != is.final_cleanup_cb) + is.final_cleanup_cb (is.final_cleanup_cb_cls); + GNUNET_free_non_null (main_ctx.exchange_url); + GNUNET_SIGNAL_handler_uninstall (shc_chld); + GNUNET_DISK_pipe_close (sigpipe); + sigpipe = NULL; + return is.result; +} + +/* end of testing_api_loop.c */ diff --git a/src/lib/testing_api_trait_amount.c b/src/lib/testing_api_trait_amount.c new file mode 100644 index 000000000..8f2b79d45 --- /dev/null +++ b/src/lib/testing_api_trait_amount.c @@ -0,0 +1,82 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_trait_amount.c + * @brief offer amounts as traits. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_AMOUNT "amount" + +/** + * Obtain an amount from a @a cmd. + * + * @param cmd command to extract the amount from. + * @param index which amount to pick if @a cmd has multiple + * on offer + * @param amount[out] set to the amount. + * + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_get_trait_amount_obj + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct TALER_Amount **amount) +{ + return cmd->traits (cmd->cls, + (const void **) amount, + TALER_TESTING_TRAIT_AMOUNT, + index); +} + + +/** + * Offer amount. + * + * @param index which amount to offer, in case there are + * multiple available. + * @param amount the amount to offer. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_amount_obj + (unsigned int index, + const struct TALER_Amount *amount) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_AMOUNT, + .ptr = (const void *) amount + }; + + return ret; +} + + +/* end of testing_api_trait_amount.c */ diff --git a/src/lib/testing_api_trait_blinding_key.c b/src/lib/testing_api_trait_blinding_key.c new file mode 100644 index 000000000..6e52d1148 --- /dev/null +++ b/src/lib/testing_api_trait_blinding_key.c @@ -0,0 +1,80 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/testing_api_trait_blinding_key.c + * @brief offer blinding keys as traits. + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_BLINDING_KEY "blinding-key" + + +/** + * Obtain a blinding key from a @a cmd. + * + * @param cmd command to extract trait from + * @param index which coin to pick if @a cmd has multiple on offer. + * @param blinding_key[out] set to the offered blinding key. + * + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_blinding_key + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct TALER_DenominationBlindingKeyP **blinding_key) +{ + return cmd->traits (cmd->cls, + (const void **) blinding_key, + TALER_TESTING_TRAIT_BLINDING_KEY, + index); +} + + + +/** + * Offer blinding key. + * + * @param index index number to associate to the offered key. + * @param blinding_key blinding key to offer. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_blinding_key + (unsigned int index, + const struct TALER_DenominationBlindingKeyP *blinding_key) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_BLINDING_KEY, + .ptr = (const void *) blinding_key + }; + + return ret; +} + +/* end of testing_api_trait_blinding_key.c */ diff --git a/src/lib/testing_api_trait_cmd.c b/src/lib/testing_api_trait_cmd.c new file mode 100644 index 000000000..61e976568 --- /dev/null +++ b/src/lib/testing_api_trait_cmd.c @@ -0,0 +1,82 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_trait_cmd.c + * @brief offers CMDs as traits. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_CMD "cmd" + +/** + * Obtain a command from @a cmd. + * + * @param cmd command to extract the command from. + * @param index always zero. Commands offering this + * kind of traits do not need this index. For + * example, a "batch" CMD returns always the + * CMD currently being executed. + * @param cmd_[out] where to write the wire details. + * + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_cmd + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + struct TALER_TESTING_Command **_cmd) +{ + return cmd->traits (cmd->cls, + (const void **) _cmd, + TALER_TESTING_TRAIT_CMD, + index); +} + + +/** + * Offer a command in a trait. + * + * @param index always zero. Commands offering this + * kind of traits do not need this index. For + * example, a "meta" CMD returns always the + * CMD currently being executed. + * @param cmd wire details to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_cmd + (unsigned int index, + const struct TALER_TESTING_Command *cmd) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_CMD, + .ptr = (const struct TALER_TESTING_Command *) cmd + }; + return ret; +} + +/* end of testing_api_trait_cmd.c */ diff --git a/src/lib/testing_api_trait_coin_priv.c b/src/lib/testing_api_trait_coin_priv.c new file mode 100644 index 000000000..b06e4a207 --- /dev/null +++ b/src/lib/testing_api_trait_coin_priv.c @@ -0,0 +1,78 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_trait_coin_priv.c + * @brief coin priv traits. + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_COIN_PRIVATE_KEY "coin-private-key" + + +/** + * Obtain a coin private key from a @a cmd. + * + * @param cmd command to extract trait from. + * @param index index of the coin priv to obtain. + * @param coin_priv[out] set to the private key of the coin. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_coin_priv + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct TALER_CoinSpendPrivateKeyP **coin_priv) +{ + return cmd->traits (cmd->cls, + (const void **) coin_priv, + TALER_TESTING_TRAIT_COIN_PRIVATE_KEY, + index); +} + + +/** + * Offer coin private key. + * + * @param index index number to associate with offered coin priv. + * @param coin_priv coin private key to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_coin_priv + (unsigned int index, + const struct TALER_CoinSpendPrivateKeyP *coin_priv) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_COIN_PRIVATE_KEY, + .ptr = (const void *) coin_priv + }; + + return ret; +} + +/* end of testing_api_trait_coin_priv.c */ diff --git a/src/lib/testing_api_trait_denom_pub.c b/src/lib/testing_api_trait_denom_pub.c new file mode 100644 index 000000000..448a262c8 --- /dev/null +++ b/src/lib/testing_api_trait_denom_pub.c @@ -0,0 +1,79 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/testing_api_trait_denom_pub.c + * @brief denom pub traits. + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_DENOM_PUB "denomination-public-key" + + +/** + * Obtain a denomination public key from a @a cmd. + * + * @param cmd command to extract trait from + * @param index index number of the denom to obtain. + * @param denom_pub[out] set to the offered denom pub. + * + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_denom_pub + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct TALER_EXCHANGE_DenomPublicKey **denom_pub) +{ + return cmd->traits (cmd->cls, + (const void **) denom_pub, + TALER_TESTING_TRAIT_DENOM_PUB, + index); +} + + +/** + * Make a trait for a denomination public key. + * + * @param index index number to associate to the offered denom pub. + * @param denom_pub denom pub to offer with this trait. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_denom_pub + (unsigned int index, + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_DENOM_PUB, + .ptr = (const void *) denom_pub + }; + + return ret; +} + +/* end of testing_api_trait_denom_pub.c */ diff --git a/src/lib/testing_api_trait_denom_sig.c b/src/lib/testing_api_trait_denom_sig.c new file mode 100644 index 000000000..03062c835 --- /dev/null +++ b/src/lib/testing_api_trait_denom_sig.c @@ -0,0 +1,80 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_trait_denom_sig.c + * @brief offer denomination signatures as traits + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_DENOM_SIG "denomination-signature" + + +/** + * Obtain a denomination signature from a @a cmd. + * + * @param cmd command to extract the denom sig from. + * @param index index number associated with the denom sig. + * @param denom_sig[out] set to the offered signature. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_denom_sig + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct TALER_DenominationSignature **denom_sig) +{ + return cmd->traits (cmd->cls, + (const void **) denom_sig, + TALER_TESTING_TRAIT_DENOM_SIG, + index); +} + + +/** + * Offer denom sig. + * + * @param index index number to associate to the signature on + * offer. + * @param denom_sig the denom sig on offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_denom_sig + (unsigned int index, + const struct TALER_DenominationSignature *denom_sig) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_DENOM_SIG, + .ptr = (const void *) denom_sig + }; + + return ret; +} + + +/* end of testing_api_trait_denom_sig.c */ diff --git a/src/lib/testing_api_trait_exchange_pub.c b/src/lib/testing_api_trait_exchange_pub.c new file mode 100644 index 000000000..e8cdfbb35 --- /dev/null +++ b/src/lib/testing_api_trait_exchange_pub.c @@ -0,0 +1,77 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/testing_api_trait_exchange_pub.c + * @brief exchange pub traits. + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_EXCHANGE_PUB "exchange-public-key" + + +/** + * Obtain a exchange public key from a @a cmd. + * + * @param cmd command to extract trait from + * @param index index number of the exchange to obtain. + * @param exchange_pub[out] set to the offered exchange pub. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_exchange_pub + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct TALER_ExchangePublicKeyP **exchange_pub) +{ + return cmd->traits (cmd->cls, + (const void **) exchange_pub, + TALER_TESTING_TRAIT_EXCHANGE_PUB, + index); +} + + +/** + * Make a trait for a exchange public key. + * + * @param index index number to associate to the offered exchange pub. + * @param exchange_pub exchange pub to offer with this trait. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_exchange_pub + (unsigned int index, + const struct TALER_ExchangePublicKeyP *exchange_pub) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_EXCHANGE_PUB, + .ptr = (const void *) exchange_pub + }; + + return ret; +} + +/* end of testing_api_trait_exchange_pub.c */ diff --git a/src/lib/testing_api_trait_exchange_sig.c b/src/lib/testing_api_trait_exchange_sig.c new file mode 100644 index 000000000..97ce2d969 --- /dev/null +++ b/src/lib/testing_api_trait_exchange_sig.c @@ -0,0 +1,77 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/testing_api_trait_exchange_sig.c + * @brief exchange pub traits. + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_EXCHANGE_SIG "exchange-online-signature" + + +/** + * Obtain a exchange signature (online sig) from a @a cmd. + * + * @param cmd command to extract trait from + * @param index index number of the exchange to obtain. + * @param exchange_sig[out] set to the offered exchange signature. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_exchange_sig + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct TALER_ExchangeSignatureP **exchange_sig) +{ + return cmd->traits (cmd->cls, + (const void **) exchange_sig, + TALER_TESTING_TRAIT_EXCHANGE_SIG, + index); +} + + +/** + * Make a trait for a exchange signature. + * + * @param index index number to associate to the offered exchange pub. + * @param exchange_sig exchange signature to offer with this trait. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_exchange_sig + (unsigned int index, + const struct TALER_ExchangeSignatureP *exchange_sig) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_EXCHANGE_SIG, + .ptr = (const void *) exchange_sig + }; + + return ret; +} + +/* end of testing_api_trait_exchange_sig.c */ diff --git a/src/lib/testing_api_trait_fresh_coin.c b/src/lib/testing_api_trait_fresh_coin.c new file mode 100644 index 000000000..1f6cdac2c --- /dev/null +++ b/src/lib/testing_api_trait_fresh_coin.c @@ -0,0 +1,79 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_fresh_coin.c + * @brief traits to offer fresh conins (after "melt" operations) + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_FRESH_COINS "fresh-coins" + +/** + * Get a array of fresh coins. + * + * @param cmd command to extract the fresh coin from. + * @param index which array to pick if @a cmd has multiple + * on offer. + * @param fresh_coins[out] will point to the offered array. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_fresh_coins + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct FreshCoin **fresh_coins) +{ + return cmd->traits (cmd->cls, + (const void **) fresh_coins, + TALER_TESTING_TRAIT_FRESH_COINS, + index); +} + + +/** + * Offer a _array_ of fresh coins. + * + * @param index which array of fresh coins to offer, + * if there are multiple on offer. Tipically passed as + * zero. + * @param fresh_coins the array of fresh coins to offer + * + * @return the trait, + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_fresh_coins + (unsigned int index, + struct FreshCoin *fresh_coins) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_FRESH_COINS, + .ptr = (const void *) fresh_coins + }; + return ret; +} + +/* end of testing_api_trait_fresh_coin.c */ diff --git a/src/lib/testing_api_trait_json.c b/src/lib/testing_api_trait_json.c new file mode 100644 index 000000000..5f6647fbb --- /dev/null +++ b/src/lib/testing_api_trait_json.c @@ -0,0 +1,122 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_trait_json.c + * @brief offers JSON traits. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_WIRE_DETAILS "wire-details" +#define TALER_TESTING_TRAIT_EXCHANGE_KEYS "exchange-keys" + +/** + * Obtain serialized exchange keys from @a cmd. + * + * @param cmd command to extract the keys from. + * @param index index number associate with the keys on offer. + * @param keys[out] where to write the serialized keys. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_exchange_keys + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const json_t **keys) +{ + return cmd->traits (cmd->cls, + (const void **) keys, + TALER_TESTING_TRAIT_EXCHANGE_KEYS, + index); +} + + +/** + * Offer serialized keys in a trait. + * + * @param index index number associate with the serial keys + * on offer. + * @param keys serialized keys to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_exchange_keys + (unsigned int index, + const json_t *keys) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_EXCHANGE_KEYS, + .ptr = (const json_t *) keys + }; + return ret; +} + +/** + * Obtain wire details from @a cmd. + * + * @param cmd command to extract the wire details from. + * @param index index number associate with the wire details + * on offer; usually zero, as one command sticks to + * one bank account. + * @param wire_details[out] where to write the wire details. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_wire_details + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const json_t **wire_details) +{ + return cmd->traits (cmd->cls, + (const void **) wire_details, + TALER_TESTING_TRAIT_WIRE_DETAILS, + index); +} + + +/** + * Offer wire details in a trait. + * + * @param index index number associate with the wire details + * on offer; usually zero, as one command sticks to + * one bank account. + * @param wire_details wire details to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_wire_details + (unsigned int index, + const json_t *wire_details) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_WIRE_DETAILS, + .ptr = (const json_t *) wire_details + }; + return ret; +} + +/* end of testing_api_trait_json.c */ diff --git a/src/lib/testing_api_trait_key_peer.c b/src/lib/testing_api_trait_key_peer.c new file mode 100644 index 000000000..d10623d28 --- /dev/null +++ b/src/lib/testing_api_trait_key_peer.c @@ -0,0 +1,127 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_trait_key_peer.c + * @brief traits to offer peer's (private) keys + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_KEY_PEER "key-peer" +#define TALER_TESTING_TRAIT_KEY_PEER_PUB "key-peer-pub" + +/** + * Obtain a private key from a "peer". Used e.g. to obtain + * a merchant's priv to sign a /track request. + * + * @param cmd command that is offering the key. + * @param index (tipically zero) which key to return if there + * are multiple on offer. + * @param priv[out] set to the key coming from @a cmd. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_peer_key + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct GNUNET_CRYPTO_EddsaPrivateKey **priv) +{ + return cmd->traits (cmd->cls, + (const void **) priv, + TALER_TESTING_TRAIT_KEY_PEER, + index); +} + +/** + * Offer private key, typically done when CMD_1 needs it to + * sign a request. + * + * @param index (tipically zero) which key to return if there are + * multiple on offer. + * @param priv which object should be offered. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_peer_key + (unsigned int index, + const struct GNUNET_CRYPTO_EddsaPrivateKey *priv) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_KEY_PEER, + .ptr = (const void *) priv + }; + return ret; +} + + +/** + * Obtain a public key from a "peer". Used e.g. to obtain + * a merchant's public key to use backend's API. + * + * @param cmd command offering the key. + * @param index (tipically zero) which key to return if there + * are multiple on offer. + * @param pub[out] set to the key coming from @a cmd. + * + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_peer_key_pub + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct GNUNET_CRYPTO_EddsaPublicKey **pub) +{ + return cmd->traits (cmd->cls, + (const void **) pub, + TALER_TESTING_TRAIT_KEY_PEER_PUB, + index); +} + +/** + * Offer public key. + * + * @param index (tipically zero) which key to return if there + * are multiple on offer. NOTE: if one key is offered, it + * is mandatory to set this as zero. + * @param pub which object should be returned. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_peer_key_pub + (unsigned int index, + struct GNUNET_CRYPTO_EddsaPublicKey *pub) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_KEY_PEER_PUB, + .ptr = (const void *) pub + }; + return ret; +} + + +/* end of testing_api_trait_key_peer.c */ diff --git a/src/lib/testing_api_trait_number.c b/src/lib/testing_api_trait_number.c new file mode 100644 index 000000000..ee2e76dbb --- /dev/null +++ b/src/lib/testing_api_trait_number.c @@ -0,0 +1,116 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_trait_number.c + * @brief traits to offer numbers + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_UINT "uint" +#define TALER_TESTING_TRAIT_UINT64 "uint-64" + +/** + * Obtain a number from @a cmd. + * + * @param cmd command to extract the number from. + * @param index the number's index number. + * @param n[out] set to the number coming from @a cmd. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_uint + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const unsigned int **n) +{ + return cmd->traits (cmd->cls, + (const void **) n, + TALER_TESTING_TRAIT_UINT, + index); +} + + +/** + * Offer a number. + * + * @param index the number's index number. + * @param n the number to offer. + * @return #GNUNET_OK on success. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_uint + (unsigned int index, + const unsigned int *n) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_UINT, + .ptr = (const void *) n + }; + return ret; +} + +/** + * Obtain a "number" value from @a cmd, 64-bit version. + * + * @param cmd command to extract the number from. + * @param index the number's index number. + * @param n[out] set to the number coming from @a cmd. + * + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_uint64 + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const uint64_t **n) +{ + return cmd->traits (cmd->cls, + (const void **) n, + TALER_TESTING_TRAIT_UINT64, + index); +} + +/** + * Offer number trait, 64-bit version. + * + * @param index the number's index number. + * @param n number to offer. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_uint64 + (unsigned int index, + const uint64_t *n) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_UINT64, + .ptr = (const void *) n + }; + return ret; +} + +/* end of testing_api_trait_number.c */ diff --git a/src/lib/testing_api_trait_process.c b/src/lib/testing_api_trait_process.c new file mode 100644 index 000000000..449b6536f --- /dev/null +++ b/src/lib/testing_api_trait_process.c @@ -0,0 +1,83 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty + of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_trait_process.c + * @brief trait offering process handles. + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_PROCESS "process" + + +/** + * Obtain location where a command stores a pointer to a process. + * + * @param cmd command to extract trait from. + * @param index which process to pick if @a cmd + * has multiple on offer. + * @param coin_priv[out] set to the address of the pointer to the + * process. + * + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_process + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + struct GNUNET_OS_Process ***processp) +{ + return cmd->traits (cmd->cls, + (const void **) processp, + TALER_TESTING_TRAIT_PROCESS, + index); +} + + +/** + * Offer location where a command stores a pointer to a process. + * + * @param index offered location index number, in case there are + * multiple on offer. + * @param processp process location to offer. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_process + (unsigned int index, + struct GNUNET_OS_Process **processp) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_PROCESS, + .ptr = (const void *) processp + }; + + return ret; +} + +/* end of testing_api_trait_process.c */ diff --git a/src/lib/testing_api_trait_reserve_priv.c b/src/lib/testing_api_trait_reserve_priv.c new file mode 100644 index 000000000..4a619d07f --- /dev/null +++ b/src/lib/testing_api_trait_reserve_priv.c @@ -0,0 +1,76 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/testing_api_trait_reserve_priv.c + * @brief implements reserve private key trait + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_RESERVE_PRIVATE_KEY \ + "reserve-private-key" + +/** + * Obtain a reserve private key from a @a cmd. + * + * @param cmd command to extract the reserve priv from. + * @param index reserve priv's index number. + * @param reserve_priv[out] set to the reserve priv. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_reserve_priv + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct TALER_ReservePrivateKeyP **reserve_priv) +{ + return cmd->traits (cmd->cls, + (const void **) reserve_priv, + TALER_TESTING_TRAIT_RESERVE_PRIVATE_KEY, + index); +} + + +/** + * Offer a reserve private key. + * + * @param index reserve priv's index number. + * @param reserve_priv reserve private key to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_reserve_priv + (unsigned int index, + const struct TALER_ReservePrivateKeyP *reserve_priv) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_RESERVE_PRIVATE_KEY, + .ptr = (const void *) reserve_priv + }; + return ret; +} + +/* end of testing_api_trait_reserve_priv.c */ diff --git a/src/lib/testing_api_trait_string.c b/src/lib/testing_api_trait_string.c new file mode 100644 index 000000000..8b8f511a7 --- /dev/null +++ b/src/lib/testing_api_trait_string.c @@ -0,0 +1,305 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_trait_string.c + * @brief offers strings traits. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_CONTRACT_TERMS "contract-terms" +#define TALER_TESTING_TRAIT_TRANSFER_SUBJECT "transfer-subject" +#define TALER_TESTING_TRAIT_AMOUNT "amount" +#define TALER_TESTING_TRAIT_URL "url" +#define TALER_TESTING_TRAIT_ORDER_ID "order-id" +#define TALER_TESTING_TRAIT_REJECTED "rejected" + +/** + * Obtain contract terms from @a cmd. + * + * @param cmd command to extract the contract terms from. + * @param index contract terms index number. + * @param contract_terms[out] where to write the contract + * terms. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_contract_terms + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const json_t **contract_terms) +{ + return cmd->traits (cmd->cls, + (const void **) contract_terms, + TALER_TESTING_TRAIT_CONTRACT_TERMS, + index); +} + +/** + * Offer contract terms. + * + * @param index contract terms index number. + * @param contract_terms contract terms to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_contract_terms + (unsigned int index, + const json_t *contract_terms) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_CONTRACT_TERMS, + .ptr = (const void *) contract_terms + }; + return ret; +} + + +/** + * Obtain a transfer subject from @a cmd. + * + * @param cmd command to extract the subject from. + * @param index index number associated with the transfer + * subject to offer. + * @param transfer_subject[out] where to write the offered + * transfer subject. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_transfer_subject + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const char **transfer_subject) +{ + return cmd->traits (cmd->cls, + (const void **) transfer_subject, + TALER_TESTING_TRAIT_TRANSFER_SUBJECT, + index); +} + +/** + * Offer transfer subject. + * + * @param index index number associated with the transfer + * subject being offered. + * @param transfer_subject transfer subject to offer. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_transfer_subject + (unsigned int index, + const char *transfer_subject) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_TRANSFER_SUBJECT, + .ptr = (const void *) transfer_subject + }; + return ret; +} + + +/** + * Obtain an amount from @a cmd. + * + * @param cmd command to extract the amount from. + * @param index which amount is to be picked, in case + * multiple are offered. + * @param amount[out] where to write the wire details. + * + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_amount + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const char **amount) +{ + return cmd->traits (cmd->cls, + (const void **) amount, + TALER_TESTING_TRAIT_AMOUNT, + index); +} + +/** + * Offer amount in a trait. + * + * @param index which amount is to be offered, + * in case multiple are offered. + * @param amount the amount to offer. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_amount + (unsigned int index, + const char *amount) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_AMOUNT, + .ptr = (const void *) amount + }; + return ret; +} + + +/** + * Obtain a url from @a cmd. + * + * @param cmd command to extract the url from. + * @param index which url is to be picked, in case + * multiple are offered. + * @param url[out] where to write the url. + * + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_url + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const char **url) +{ + return cmd->traits (cmd->cls, + (const void **) url, + TALER_TESTING_TRAIT_URL, + index); +} + +/** + * Offer url in a trait. + * + * @param index which url is to be picked, + * in case multiple are offered. + * @param url the url to offer. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_url + (unsigned int index, + const char *url) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_URL, + .ptr = (const void *) url + }; + return ret; +} + + +/** + * Obtain a order id from @a cmd. + * + * @param cmd command to extract the order id from. + * @param index which order id is to be picked, in case + * multiple are offered. + * @param order_id[out] where to write the order id. + * + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_order_id + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const char **order_id) +{ + return cmd->traits (cmd->cls, + (const void **) order_id, + TALER_TESTING_TRAIT_ORDER_ID, + index); +} + +/** + * Offer order id in a trait. + * + * @param index which order id is to be offered, + * in case multiple are offered. + * @param order_id the order id to offer. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_order_id + (unsigned int index, + const char *order_id) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_ORDER_ID, + .ptr = (const void *) order_id + }; + return ret; +} + +/** + * Obtain the reference to a "reject" CMD. Usually offered + * by _rejected_ bank transfers. + * + * @param cmd command to extract the reference from. + * @param index which reference is to be picked, in case + * multiple are offered. + * @param rejected_reference[out] where to write the reference. + * + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_rejected + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const char **rejected_reference) +{ + return cmd->traits (cmd->cls, + (const void **) rejected_reference, + TALER_TESTING_TRAIT_REJECTED, + index); +} + +/** + * Offer a "reject" CMD reference. + * + * @param index which reference is to be offered, + * in case multiple are offered. + * @param rejected_reference the reference to offer. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_rejected + (unsigned int index, + const char *rejected) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_REJECTED, + .ptr = (const void *) rejected + }; + return ret; +} + +/* end of testing_api_trait_string.c */ diff --git a/src/lib/testing_api_trait_wtid.c b/src/lib/testing_api_trait_wtid.c new file mode 100644 index 000000000..672329713 --- /dev/null +++ b/src/lib/testing_api_trait_wtid.c @@ -0,0 +1,76 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/testing_api_trait_number.c + * @brief traits to offer numbers + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_WTID "wtid" + +/** + * Obtain a WTID value from @a cmd. + * + * @param cmd command to extract trait from + * @param index which WTID to pick if @a cmd has multiple on + * offer + * @param wtid[out] set to the wanted WTID. + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_get_trait_wtid + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + const struct TALER_WireTransferIdentifierRawP **wtid) +{ + return cmd->traits (cmd->cls, + (const void **) wtid, + TALER_TESTING_TRAIT_WTID, + index); +} + + +/** + * Offer a WTID. + * + * @param index associate the object with this index + * @param wtid which object should be returned + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_wtid + (unsigned int index, + const struct TALER_WireTransferIdentifierRawP *wtid) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_WTID, + .ptr = (const void *) wtid + }; + return ret; +} + +/* end of testing_api_trait_number.c */ diff --git a/src/lib/testing_api_traits.c b/src/lib/testing_api_traits.c new file mode 100644 index 000000000..aa6c79c83 --- /dev/null +++ b/src/lib/testing_api_traits.c @@ -0,0 +1,80 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/testing_api_traits.c + * @brief loop for trait resolution + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * End a trait array. Usually, commands offer several traits, + * and put them in arrays. + */ +struct TALER_TESTING_Trait +TALER_TESTING_trait_end () +{ + struct TALER_TESTING_Trait end = { + .index = 0, + .trait_name = NULL, + .ptr = NULL + }; + + return end; +} + +/** + * Pick the chosen trait from the traits array. + * + * @param traits the traits array. + * @param ret where to store the result. + * @param trait type of the trait to extract. + * @param index index number of the object to extract. + * @return #GNUNET_OK if no error occurred, #GNUNET_SYSERR otherwise. + */ +int +TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits, + const void **ret, + const char *trait, + unsigned int index) +{ + for (unsigned int i=0; NULL != traits[i].trait_name; i++) + { + if ( (0 == strcmp (trait, traits[i].trait_name)) && + (index == traits[i].index) ) + { + *ret = (void *) traits[i].ptr; + return GNUNET_OK; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Trait %s/%u not found.\n", + trait, index); + + return GNUNET_SYSERR; +} + +/* end of testing_api_traits.c */ diff --git a/src/lib/testing_auditor_api_cmd_deposit_confirmation.c b/src/lib/testing_auditor_api_cmd_deposit_confirmation.c new file mode 100644 index 000000000..ad02a6ef5 --- /dev/null +++ b/src/lib/testing_auditor_api_cmd_deposit_confirmation.c @@ -0,0 +1,411 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file auditor-lib/testing_auditor_api_cmd_deposit_confirmation.c + * @brief command for testing /deposit_confirmation. + * @author Christian Grothoff + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_auditor_service.h" +#include "taler_testing_lib.h" +#include "taler_signatures.h" +#include "backoff.h" + + +/** + * State for a "deposit confirmation" CMD. + */ +struct DepositConfirmationState +{ + + /** + * Reference to any command that is able to provide a deposit. + */ + const char *deposit_reference; + + /** + * What is the deposited amount without the fee (i.e. the + * amount we expect in the deposit confirmation)? + */ + const char *amount_without_fee; + + /** + * Which coin of the @e deposit_reference should we confirm. + */ + unsigned int coin_index; + + /** + * DepositConfirmation handle while operation is running. + */ + struct TALER_AUDITOR_DepositConfirmationHandle *dc; + + /** + * Auditor connection. + */ + struct TALER_AUDITOR_Handle *auditor; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Should we retry on (transient) failures? + */ + int do_retry; + +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +deposit_confirmation_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #deposit_confirmation_run. + * + * @param cls a `struct DepositConfirmationState` + */ +static void +do_retry (void *cls) +{ + struct DepositConfirmationState *dcs = cls; + + dcs->retry_task = NULL; + deposit_confirmation_run (dcs, + NULL, + dcs->is); +} + + +/** + * Callback to analyze the /deposit-confirmation response, just used + * to check if the response code is acceptable. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param obj raw response from the auditor. + */ +static void +deposit_confirmation_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const json_t *obj) +{ + struct DepositConfirmationState *dcs = cls; + + dcs->dc = NULL; + if (dcs->expected_response_code != http_status) + { + if (GNUNET_YES == dcs->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying deposit confirmation failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + dcs->backoff = GNUNET_TIME_UNIT_ZERO; + else + dcs->backoff = EXCHANGE_LIB_BACKOFF (dcs->backoff); + dcs->retry_task = GNUNET_SCHEDULER_add_delayed (dcs->backoff, + &do_retry, + dcs); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s in %s:%u\n", + http_status, + dcs->is->commands[dcs->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (obj, stderr, 0); + TALER_TESTING_interpreter_fail (dcs->is); + return; + } + TALER_TESTING_interpreter_next (dcs->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +deposit_confirmation_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct DepositConfirmationState *dcs = cls; + const struct TALER_TESTING_Command *deposit_cmd; + struct GNUNET_HashCode h_wire; + struct GNUNET_HashCode h_contract_terms; + struct GNUNET_TIME_Absolute timestamp; + struct GNUNET_TIME_Absolute refund_deadline; + struct TALER_Amount amount_without_fee; + struct TALER_CoinSpendPublicKeyP coin_pub; + const struct GNUNET_CRYPTO_EddsaPrivateKey *merchant_priv; + struct TALER_MerchantPublicKeyP merchant_pub; + const struct TALER_ExchangePublicKeyP *exchange_pub; + const struct TALER_ExchangeSignatureP *exchange_sig; + const json_t *wire_details; + const json_t *contract_terms; + const struct TALER_CoinSpendPrivateKeyP *coin_priv; + const struct TALER_EXCHANGE_Keys *keys; + const struct TALER_EXCHANGE_SigningPublicKey *spk; + + dcs->is = is; + GNUNET_assert (NULL != dcs->deposit_reference); + deposit_cmd + = TALER_TESTING_interpreter_lookup_command (is, + dcs->deposit_reference); + if (NULL == deposit_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_exchange_pub (deposit_cmd, + dcs->coin_index, + &exchange_pub)); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_exchange_sig (deposit_cmd, + dcs->coin_index, + &exchange_sig)); + keys = TALER_EXCHANGE_get_keys (dcs->is->exchange); + GNUNET_assert (NULL != keys); + spk = TALER_EXCHANGE_get_exchange_signing_key_info (keys, + exchange_pub); + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_contract_terms (deposit_cmd, + dcs->coin_index, + &contract_terms)); + /* Very unlikely to fail */ + GNUNET_assert (NULL != contract_terms); + TALER_JSON_hash (contract_terms, + &h_contract_terms); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_wire_details (deposit_cmd, + dcs->coin_index, + &wire_details)); + TALER_JSON_hash (wire_details, + &h_wire); + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_coin_priv (deposit_cmd, + dcs->coin_index, + &coin_priv)); + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_peer_key (deposit_cmd, + dcs->coin_index, + &merchant_priv)); + GNUNET_CRYPTO_eddsa_key_get_public (merchant_priv, + &merchant_pub.eddsa_pub); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (dcs->amount_without_fee, + &amount_without_fee)); + + dcs->dc = TALER_AUDITOR_deposit_confirmation + (dcs->auditor, + &h_wire, + &h_contract_terms, + timestamp, + refund_deadline, + &amount_without_fee, + &coin_pub, + &merchant_pub, + exchange_pub, + exchange_sig, + &keys->master_pub, + spk->valid_from, + spk->valid_until, + spk->valid_legal, + &spk->master_sig, + &deposit_confirmation_cb, + dcs); + + if (NULL == dcs->dc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + return; +} + + +/** + * Free the state of a "deposit_confirmation" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, a `struct DepositConfirmationState` + * @param cmd the command which is being cleaned up. + */ +static void +deposit_confirmation_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct DepositConfirmationState *dcs = cls; + + if (NULL != dcs->dc) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + dcs->is->ip, + cmd->label); + TALER_AUDITOR_deposit_confirmation_cancel (dcs->dc); + dcs->dc = NULL; + } + if (NULL != dcs->retry_task) + { + GNUNET_SCHEDULER_cancel (dcs->retry_task); + dcs->retry_task = NULL; + } + GNUNET_free (dcs); +} + + +/** + * Offer internal data to other commands. + * + * @param cls closure. + * @param ret[out] set to the wanted data. + * @param trait name of the trait. + * @param index index number of the traits to be returned. + * + * @return #GNUNET_OK on success + */ +static int +deposit_confirmation_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + /* Must define this function because some callbacks + * look for certain traits on _all_ the commands. */ + return GNUNET_SYSERR; +} + + +/** + * Create a "deposit-confirmation" command. + * + * @param label command label. + * @param auditor auditor connection. + * @param deposit_reference reference to any operation that can + * provide a coin. + * @param coin_index if @a deposit_reference offers an array of + * coins, this parameter selects which one in that array. + * This value is currently ignored, as only one-coin + * deposits are implemented. + * @param amount_without_fee deposited amount without the fee + * @param expected_response_code expected HTTP response code. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_deposit_confirmation + (const char *label, + struct TALER_AUDITOR_Handle *auditor, + const char *deposit_reference, + unsigned int coin_index, + const char *amount_without_fee, + unsigned int expected_response_code) +{ + struct TALER_TESTING_Command cmd = {0}; /* need explicit zeroing..*/ + struct DepositConfirmationState *dcs; + + dcs = GNUNET_new (struct DepositConfirmationState); + dcs->auditor = auditor; + dcs->deposit_reference = deposit_reference; + dcs->coin_index = coin_index; + dcs->amount_without_fee = amount_without_fee; + dcs->expected_response_code = expected_response_code; + + cmd.cls = dcs; + cmd.label = label; + cmd.run = &deposit_confirmation_run; + cmd.cleanup = &deposit_confirmation_cleanup; + cmd.traits = &deposit_confirmation_traits; + + return cmd; +} + + +/** + * Modify a deposit confirmation command to enable retries when we get + * transient errors from the auditor. + * + * @param cmd a deposit confirmation command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_deposit_confirmation_with_retry (struct TALER_TESTING_Command cmd) +{ + struct DepositConfirmationState *dcs; + + GNUNET_assert (&deposit_confirmation_run == cmd.run); + dcs = cmd.cls; + dcs->do_retry = GNUNET_YES; + return cmd; +} + + +/* end of testing_auditor_api_cmd_deposit_confirmation.c */ diff --git a/src/lib/testing_auditor_api_cmd_exchanges.c b/src/lib/testing_auditor_api_cmd_exchanges.c new file mode 100644 index 000000000..ed4ca5e0e --- /dev/null +++ b/src/lib/testing_auditor_api_cmd_exchanges.c @@ -0,0 +1,296 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file auditor-lib/testing_auditor_api_cmd_exchanges.c + * @brief command for testing /exchanges. + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_auditor_service.h" +#include "taler_testing_lib.h" +#include "taler_signatures.h" +#include "backoff.h" + + +/** + * State for a "deposit confirmation" CMD. + */ +struct ExchangesState +{ + + /** + * Exchanges handle while operation is running. + */ + struct TALER_AUDITOR_ListExchangesHandle *leh; + + /** + * Auditor connection. + */ + struct TALER_AUDITOR_Handle *auditor; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Should we retry on (transient) failures? + */ + int do_retry; + +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +exchanges_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #exchanges_run. + * + * @param cls a `struct ExchangesState` + */ +static void +do_retry (void *cls) +{ + struct ExchangesState *es = cls; + + es->retry_task = NULL; + exchanges_run (es, + NULL, + es->is); +} + + +/** + * Callback to analyze the /exchanges response. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param obj raw response from the auditor. + */ +static void +exchanges_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + unsigned int num_exchanges, + const struct TALER_AUDITOR_ExchangeInfo *ei, + const json_t *raw_response) +{ + struct ExchangesState *es = cls; + + es->leh = NULL; + if (es->expected_response_code != http_status) + { + if (GNUNET_YES == es->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying list exchanges failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + es->backoff = GNUNET_TIME_UNIT_ZERO; + else + es->backoff = EXCHANGE_LIB_BACKOFF (es->backoff); + es->retry_task = GNUNET_SCHEDULER_add_delayed (es->backoff, + &do_retry, + es); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s in %s:%u\n", + http_status, + es->is->commands[es->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (raw_response, stderr, 0); + TALER_TESTING_interpreter_fail (es->is); + return; + } + TALER_TESTING_interpreter_next (es->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +exchanges_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct ExchangesState *es = cls; + + es->is = is; + es->leh = TALER_AUDITOR_list_exchanges + (es->auditor, + &exchanges_cb, + es); + + if (NULL == es->leh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + return; +} + + +/** + * Free the state of a "exchanges" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, a `struct ExchangesState` + * @param cmd the command which is being cleaned up. + */ +static void +exchanges_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct ExchangesState *es = cls; + + if (NULL != es->leh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + es->is->ip, + cmd->label); + TALER_AUDITOR_list_exchanges_cancel (es->leh); + es->leh = NULL; + } + if (NULL != es->retry_task) + { + GNUNET_SCHEDULER_cancel (es->retry_task); + es->retry_task = NULL; + } + GNUNET_free (es); +} + + +/** + * Offer internal data to other commands. + * + * @param cls closure. + * @param ret[out] set to the wanted data. + * @param trait name of the trait. + * @param index index number of the traits to be returned. + * + * @return #GNUNET_OK on success + */ +static int +exchanges_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + /* Must define this function because some callbacks + * look for certain traits on _all_ the commands. */ + return GNUNET_SYSERR; +} + + +/** + * Create a "list exchanges" command. + * + * @param label command label. + * @param auditor auditor connection. + * @param expected_response_code expected HTTP response code. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exchanges + (const char *label, + struct TALER_AUDITOR_Handle *auditor, + unsigned int expected_response_code) +{ + struct TALER_TESTING_Command cmd = {0}; /* need explicit zeroing..*/ + struct ExchangesState *es; + + es = GNUNET_new (struct ExchangesState); + es->auditor = auditor; + es->expected_response_code = expected_response_code; + + cmd.cls = es; + cmd.label = label; + cmd.run = &exchanges_run; + cmd.cleanup = &exchanges_cleanup; + cmd.traits = &exchanges_traits; + + return cmd; +} + + +/** + * Modify an exchanges command to enable retries when we get + * transient errors from the auditor. + * + * @param cmd a deposit confirmation command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exchanges_with_retry (struct TALER_TESTING_Command cmd) +{ + struct ExchangesState *es; + + GNUNET_assert (&exchanges_run == cmd.run); + es = cmd.cls; + es->do_retry = GNUNET_YES; + return cmd; +} + + +/* end of testing_auditor_api_cmd_exchanges.c */ diff --git a/src/lib/testing_auditor_api_cmd_exec_auditor.c b/src/lib/testing_auditor_api_cmd_exec_auditor.c new file mode 100644 index 000000000..afe9d38fc --- /dev/null +++ b/src/lib/testing_auditor_api_cmd_exec_auditor.c @@ -0,0 +1,162 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file auditor-lib/testing_auditor_api_cmd_exec_auditor.c + * @brief run the taler-auditor command + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "auditor_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "auditor" CMD. + */ +struct AuditorState +{ + + /** + * Process for the "auditor" command. + */ + struct GNUNET_OS_Process *auditor_proc; + + /** + * Configuration file used by the command. + */ + const char *config_filename; +}; + + +/** + * Run the command; calls the `taler-auditor' program. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +auditor_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct AuditorState *ks = cls; + + ks->auditor_proc + = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-auditor", + "taler-auditor", + "-c", ks->config_filename, + NULL); + if (NULL == ks->auditor_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "auditor" CMD, and possibly kills its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +auditor_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct AuditorState *ks = cls; + + if (NULL != ks->auditor_proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (ks->auditor_proc, + SIGKILL)); + GNUNET_OS_process_wait (ks->auditor_proc); + GNUNET_OS_process_destroy (ks->auditor_proc); + ks->auditor_proc = NULL; + } + GNUNET_free (ks); +} + + +/** + * Offer "auditor" CMD internal data to other commands. + * + * @param cls closure. + * @param ret[out] result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +auditor_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct AuditorState *ks = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_process (0, &ks->auditor_proc), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Make the "exec-auditor" CMD. + * + * @param label command label. + * @param config_filename configuration filename. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_auditor (const char *label, + const char *config_filename) +{ + struct TALER_TESTING_Command cmd; + struct AuditorState *ks; + + ks = GNUNET_new (struct AuditorState); + ks->config_filename = config_filename; + cmd.cls = ks; + cmd.label = label; + cmd.run = &auditor_run; + cmd.cleanup = &auditor_cleanup; + cmd.traits = &auditor_traits; + return cmd; +} + +/* end of testing_auditor_api_cmd_exec_auditor.c */ diff --git a/src/lib/testing_auditor_api_cmd_exec_auditor_dbinit.c b/src/lib/testing_auditor_api_cmd_exec_auditor_dbinit.c new file mode 100644 index 000000000..09771660c --- /dev/null +++ b/src/lib/testing_auditor_api_cmd_exec_auditor_dbinit.c @@ -0,0 +1,163 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file auditor-lib/testing_auditor_api_cmd_exec_auditor_dbinit.c + * @brief run the taler-auditor-dbinit "-r" command + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "auditor_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "auditor-dbinit" CMD. + */ +struct AuditorDbinitState +{ + + /** + * Process for the "auditor-dbinit" command. + */ + struct GNUNET_OS_Process *auditor_dbinit_proc; + + /** + * Configuration file used by the command. + */ + const char *config_filename; +}; + + +/** + * Run the command; calls the `taler-auditor-dbinit' program. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +auditor_dbinit_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct AuditorDbinitState *ks = cls; + + ks->auditor_dbinit_proc + = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-auditor-dbinit", + "taler-auditor-dbinit", + "-c", ks->config_filename, + "-r", + NULL); + if (NULL == ks->auditor_dbinit_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "auditor-dbinit" CMD, and possibly kills its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +auditor_dbinit_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct AuditorDbinitState *ks = cls; + + if (NULL != ks->auditor_dbinit_proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (ks->auditor_dbinit_proc, + SIGKILL)); + GNUNET_OS_process_wait (ks->auditor_dbinit_proc); + GNUNET_OS_process_destroy (ks->auditor_dbinit_proc); + ks->auditor_dbinit_proc = NULL; + } + GNUNET_free (ks); +} + + +/** + * Offer "auditor-dbinit" CMD internal data to other commands. + * + * @param cls closure. + * @param ret[out] result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +auditor_dbinit_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct AuditorDbinitState *ks = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_process (0, &ks->auditor_dbinit_proc), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Make the "exec-auditor-dbinit" CMD. + * + * @param label command label. + * @param config_filename configuration filename. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_auditor_dbinit (const char *label, + const char *config_filename) +{ + struct TALER_TESTING_Command cmd; + struct AuditorDbinitState *ks; + + ks = GNUNET_new (struct AuditorDbinitState); + ks->config_filename = config_filename; + cmd.cls = ks; + cmd.label = label; + cmd.run = &auditor_dbinit_run; + cmd.cleanup = &auditor_dbinit_cleanup; + cmd.traits = &auditor_dbinit_traits; + return cmd; +} + +/* end of testing_auditor_api_cmd_exec_auditor_dbinit.c */ diff --git a/src/lib/testing_auditor_api_cmd_exec_wire_auditor.c b/src/lib/testing_auditor_api_cmd_exec_wire_auditor.c new file mode 100644 index 000000000..8371f9690 --- /dev/null +++ b/src/lib/testing_auditor_api_cmd_exec_wire_auditor.c @@ -0,0 +1,162 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file auditor-lib/testing_auditor_api_cmd_exec_wire-auditor.c + * @brief run the taler-wire-auditor command + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "auditor_api_handle.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "wire-auditor" CMD. + */ +struct WireAuditorState +{ + + /** + * Process for the "wire-auditor" command. + */ + struct GNUNET_OS_Process *wire_auditor_proc; + + /** + * Configuration file used by the command. + */ + const char *config_filename; +}; + + +/** + * Run the command; calls the `taler-wire-auditor' program. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +wire_auditor_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct WireAuditorState *ks = cls; + + ks->wire_auditor_proc + = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-wire-auditor", + "taler-wire-auditor", + "-c", ks->config_filename, + NULL); + if (NULL == ks->wire_auditor_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "wire-auditor" CMD, and possibly kills its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +wire_auditor_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct WireAuditorState *ks = cls; + + if (NULL != ks->wire_auditor_proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (ks->wire_auditor_proc, + SIGKILL)); + GNUNET_OS_process_wait (ks->wire_auditor_proc); + GNUNET_OS_process_destroy (ks->wire_auditor_proc); + ks->wire_auditor_proc = NULL; + } + GNUNET_free (ks); +} + + +/** + * Offer "wire-auditor" CMD internal data to other commands. + * + * @param cls closure. + * @param ret[out] result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +wire_auditor_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct WireAuditorState *ks = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_process (0, &ks->wire_auditor_proc), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Make the "exec wire-auditor" CMD. + * + * @param label command label. + * @param config_filename configuration filename. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_wire_auditor (const char *label, + const char *config_filename) +{ + struct TALER_TESTING_Command cmd; + struct WireAuditorState *ks; + + ks = GNUNET_new (struct WireAuditorState); + ks->config_filename = config_filename; + cmd.cls = ks; + cmd.label = label; + cmd.run = &wire_auditor_run; + cmd.cleanup = &wire_auditor_cleanup; + cmd.traits = &wire_auditor_traits; + return cmd; +} + +/* end of testing_auditor_api_cmd_exec_wire_auditor.c */ diff --git a/src/lib/testing_auditor_api_helpers.c b/src/lib/testing_auditor_api_helpers.c new file mode 100644 index 000000000..7e7103150 --- /dev/null +++ b/src/lib/testing_auditor_api_helpers.c @@ -0,0 +1,226 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file auditor-lib/testing_auditor_api_helpers.c + * @brief helper functions + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_testing_lib.h" +#include "taler_testing_auditor_lib.h" +#include "taler_auditor_service.h" + + +/** + * Closure for #cleanup_auditor. + */ +struct CleanupContext +{ + /** + * Where we find the state to clean up. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Next cleanup routine to call, NULL for none. + */ + GNUNET_SCHEDULER_TaskCallback fcb; + + /** + * Closure for @e fcb + */ + void *fcb_cls; +}; + + +/** + * Function to clean up the auditor connection. + * + * @param cls a `struct CleanupContext` + */ +static void +cleanup_auditor (void *cls) +{ + struct CleanupContext *cc = cls; + struct TALER_TESTING_Interpreter *is = cc->is; + + TALER_AUDITOR_disconnect (is->auditor); + is->auditor = NULL; + if (NULL != cc->fcb) + cc->fcb (cc->fcb_cls); + GNUNET_free (cc); +} + + + +/** + * Function called with information about the auditor. + * + * @param cls closure + * @param vi basic information about the auditor + * @param compat protocol compatibility information + */ +static void +auditor_version_cb (void *cls, + const struct TALER_AUDITOR_VersionInformation *vi, + enum TALER_AUDITOR_VersionCompatibility compat) +{ + struct TALER_TESTING_Interpreter *is = cls; + + /* TODO: check vi/compat? */ + is->auditor_working = GNUNET_YES; +} + + + +/** + * Closure for #auditor_main_wrapper() + */ +struct MainWrapperContext +{ + /** + * Main function to launch. + */ + TALER_TESTING_Main main_cb; + + /** + * Closure for @e main_cb. + */ + void *main_cb_cls; + + /** + * Configuration we use. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Name of the configuration file. + */ + const char *config_filename; + +}; + + +/** + * Setup the @a is 'auditor' member before running the main test loop. + * + * @param cls must be a `struct MainWrapperContext *` + * @param is[in,out] interpreter state to setup + */ +static void +auditor_main_wrapper (void *cls, + struct TALER_TESTING_Interpreter *is) +{ + struct MainWrapperContext *mwc = cls; + struct CleanupContext *cc; + char *auditor_base_url; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (mwc->cfg, + "auditor", + "BASE_URL", + &auditor_base_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "auditor", + "BASE_URL"); + return; + } + is->auditor = TALER_AUDITOR_connect (is->ctx, + auditor_base_url, + &auditor_version_cb, + is); + GNUNET_free (auditor_base_url); + if (NULL == is->auditor) + { + GNUNET_break (0); + return; + } + cc = GNUNET_new (struct CleanupContext); + cc->is = is; + cc->fcb = is->final_cleanup_cb; + cc->fcb_cls = is->final_cleanup_cb; + is->final_cleanup_cb = &cleanup_auditor; + is->final_cleanup_cb_cls = cc; + mwc->main_cb (mwc->main_cb_cls, + is); +} + + +/** + * Install signal handlers plus schedules the main wrapper + * around the "run" method. + * + * @param cls our `struct MainWrapperContext` + * @param cfg configuration we use + * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise. + * non-GNUNET_OK codes are #GNUNET_SYSERR most of the + * times. + */ +static int +setup_with_cfg (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct MainWrapperContext *mwc = cls; + struct TALER_TESTING_SetupContext setup_ctx = { + .config_filename = mwc->config_filename, + .main_cb = &auditor_main_wrapper, + .main_cb_cls = mwc + }; + + mwc->cfg = cfg; + return TALER_TESTING_setup_with_auditor_and_exchange_cfg (&setup_ctx, + cfg); +} + + +/** + * Install signal handlers plus schedules the main wrapper + * around the "run" method. + * + * @param main_cb the "run" method which contains all the + * commands. + * @param main_cb_cls a closure for "run", typically NULL. + * @param config_filename configuration filename. + * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise. + * non-GNUNET_OK codes are #GNUNET_SYSERR most of the + * times. + */ +int +TALER_TESTING_AUDITOR_setup (TALER_TESTING_Main main_cb, + void *main_cb_cls, + const char *config_filename) +{ + struct MainWrapperContext mwc = { + .main_cb = main_cb, + .main_cb_cls = main_cb_cls, + .config_filename = config_filename + }; + + return GNUNET_CONFIGURATION_parse_and_run (config_filename, + &setup_with_cfg, + &mwc); +} + + + +/* end of testing_auditor_api_helpers.c */ |