diff options
author | Marcello Stanisci <stanisci.m@gmail.com> | 2018-01-23 10:28:24 +0100 |
---|---|---|
committer | Marcello Stanisci <stanisci.m@gmail.com> | 2018-02-12 16:12:07 +0100 |
commit | fe6960cce854cd4c665a27c4368e4397c8e7bcfb (patch) | |
tree | 162a95572079b3e7a014cb845ee028329a06f5f8 /src/exchange-lib | |
parent | b198bb3867b6a15c65a8860af12d7a634de906a0 (diff) | |
download | exchange-fe6960cce854cd4c665a27c4368e4397c8e7bcfb.tar.xz |
Implement new traits-based tests.
Diffstat (limited to 'src/exchange-lib')
34 files changed, 6558 insertions, 338 deletions
diff --git a/src/exchange-lib/Makefile.am b/src/exchange-lib/Makefile.am index 64a5f9649..cf80a1d42 100644 --- a/src/exchange-lib/Makefile.am +++ b/src/exchange-lib/Makefile.am @@ -34,14 +34,26 @@ libtalerexchange_la_LIBADD = \ -ljansson \ $(XLIB) - libtalertesting_la_LDFLAGS = \ -version-info 0:0:0 \ -no-undefined libtalertesting_la_SOURCES = \ + 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_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_helpers.c \ testing_api_loop.c \ testing_api_traits.c \ @@ -50,7 +62,12 @@ libtalertesting_la_SOURCES = \ testing_api_trait_denom_pub.c \ testing_api_trait_denom_sig.c \ testing_api_trait_process.c \ - testing_api_trait_reserve_priv.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 libtalertesting_la_LIBADD = \ $(top_builddir)/src/json/libtalerjson.la \ @@ -71,13 +88,17 @@ endif endif check_PROGRAMS = \ + test_exchange_api_keys_cherry_picking_new \ + test_exchange_api_new \ test_exchange_api \ - test_exchange_api_keys_cherry_picking \ - test_exchange_api_new + test_exchange_api_keys_cherry_picking AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; +# FIXME: uncomment those. TESTS = \ + test_exchange_api_keys_cherry_picking_new \ + test_exchange_api_new \ test_exchange_api \ test_exchange_api_keys_cherry_picking @@ -108,6 +129,19 @@ test_exchange_api_new_LDADD = \ -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_exchange_api_keys_cherry_picking_SOURCES = \ test_exchange_api_keys_cherry_picking.c test_exchange_api_keys_cherry_picking_LDADD = \ diff --git a/src/exchange-lib/test_exchange_api_keys_cherry_picking.conf b/src/exchange-lib/test_exchange_api_keys_cherry_picking.conf index 2b94dba65..b2d2fbc5f 100644 --- a/src/exchange-lib/test_exchange_api_keys_cherry_picking.conf +++ b/src/exchange-lib/test_exchange_api_keys_cherry_picking.conf @@ -1,7 +1,7 @@ # This file is in the public domain. # [PATHS] -# Persistant data storage for the testcase +# Persistent data storage for the testcase TALER_TEST_HOME = test_exchange_api_home/ [taler] diff --git a/src/exchange-lib/test_exchange_api_keys_cherry_picking_new.c b/src/exchange-lib/test_exchange_api_keys_cherry_picking_new.c new file mode 100644 index 000000000..c32d6424f --- /dev/null +++ b/src/exchange-lib/test_exchange_api_keys_cherry_picking_new.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" + + +/** + * 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[] = { + + /* Send signal to the exchange to see if it reacts */ + TALER_TESTING_cmd_signal ("signal-reaction-1", + is->exchanged, + SIGUSR1), + + TALER_TESTING_cmd_check_keys ("check-keys-1", + 1, 4, + is->exchange), + + TALER_TESTING_cmd_exec_keyup ("keyup-2", /* 1st keyup happens at start-up */ + CONFIG_FILE_EXTENDED), + + TALER_TESTING_cmd_exec_auditor_sign ("sign-keys-1", + CONFIG_FILE), + + TALER_TESTING_cmd_signal ("trigger-keys-reload-1", + is->exchanged, + SIGUSR1), + + TALER_TESTING_cmd_check_keys ("check-keys-2", + 2, 8, + is->exchange), + + + /** + * End the suite. Fixme: better to have a label for this + * too, as it shows "(null)" in logs. + */ + 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)) + { + 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/exchange-lib/test_exchange_api_new.c b/src/exchange-lib/test_exchange_api_new.c index f9d60249d..580c4e972 100644 --- a/src/exchange-lib/test_exchange_api_new.c +++ b/src/exchange-lib/test_exchange_api_new.c @@ -2,23 +2,29 @@ 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 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. + 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/> + 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" @@ -37,10 +43,17 @@ #define CONFIG_FILE "test_exchange_api.conf" /** - * URL of the fakebank. Obtained from CONFIG_FILE's "exchange-wire-test:BANK_URL" option. + * URL of the fakebank. Obtained from CONFIG_FILE's + * "exchange-wire-test:BANK_URI" option. */ static char *fakebank_url; +/** + * FIXME: what about putting exchange_url also global + * here? Right now, the exchange port is being "bounced" + * between functions before exchange_url is constructed + * and TALER_EXCHANGE_connect() is called with that. + */ /** * Account number of the exchange at the bank. @@ -72,6 +85,15 @@ static char *fakebank_url; 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. * @@ -83,9 +105,22 @@ static char *fakebank_url; fakebank_url, USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO, \ USER_LOGIN_NAME, USER_LOGIN_PASS) +/** + * 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. + * Main function that will tell the interpreter what commands to + * run. * * @param cls closure */ @@ -94,9 +129,609 @@ run (void *cls, struct TALER_TESTING_Interpreter *is) { struct TALER_TESTING_Command commands[] = { + + /****** Start of "wire" testing ******/ + + /** + * Move money to the exchange's bank account. + */ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1", "EUR:5.01"), - CMD_EXEC_WIREWATCH ("exec-wirewatch-1"), + + /** + * Make a reserve exist, according to the previous + * transfer. + */ + CMD_EXEC_WIREWATCH ("wirewatch-1"), + + /** + * Check if 'test' wire method is offered by the exchange. + */ + TALER_TESTING_cmd_wire ("wire-test-1", + is->exchange, + "test", + NULL, + MHD_HTTP_OK), + + /** + * Check if 'sepa' wire method is offered by the exchange. + */ + TALER_TESTING_cmd_wire ("wire-sepa-1", + is->exchange, + "sepa", + NULL, + MHD_HTTP_OK), + + /****** End of "wire" testing ******/ + + /****** Start of withdraw and spend testing ******/ + + /** + * Withdraw EUR:5. + */ + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", + is->exchange, + "create-reserve-1", + "EUR:5", + MHD_HTTP_OK), + + /** + * Check the reserve is depleted. + */ + TALER_TESTING_cmd_status ("status-1", + is->exchange, + "create-reserve-1", + "EUR:0", + MHD_HTTP_OK), + /** + * Spend the coin. + */ + TALER_TESTING_cmd_deposit + ("deposit-simple", is->exchange, "withdraw-coin-1", 0, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\",\"account_number\":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", + is->exchange, + "create-reserve-1", + "EUR:5", + MHD_HTTP_FORBIDDEN), + + /** + * Try to double spend using different wire details. + */ + TALER_TESTING_cmd_deposit + ("deposit-double-1", is->exchange, "withdraw-coin-1", 0, + TALER_TESTING_make_wire_details + ("{\"type\":\"test\",\"account_number\":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", is->exchange, "withdraw-coin-1", 0, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\", \"account_number\":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", is->exchange, "withdraw-coin-1", 0, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\", \"account_number\":43}", + fakebank_url), + "{\"items\":[{\"name\":\"ice cream\",\"value\":2}]}", + GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_FORBIDDEN), + + /****** End of withdraw and spend testing ******/ + + /****** Start of refresh testing ******/ + + /** + * 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", + is->exchange, + "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", is->exchange, + "refresh-withdraw-coin-1", 0, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\",\"account_number\":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-1", is->exchange, "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", is->exchange, + "refresh-melt-1", MHD_HTTP_OK), + + /** + * Do it again to check idempotency + */ + TALER_TESTING_cmd_refresh_reveal + ("refresh-reveal-1-idempotency", + is->exchange, "refresh-melt-1", MHD_HTTP_OK), + + /** + * Test that /refresh/link works + */ + TALER_TESTING_cmd_refresh_link + ("refresh-link-1", is->exchange, + "refresh-reveal-1", MHD_HTTP_OK), + + /** + * Try to spend a refreshed EUR:1 coin + */ + TALER_TESTING_cmd_deposit + ("refresh-deposit-refreshed-1a", is->exchange, + "refresh-reveal-1-idempotency", 0, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\",\"account_number\":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", is->exchange, + "refresh-reveal-1", 4, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\",\"account_number\":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", is->exchange, "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...) */ + + /****** End of refresh testing ******/ + + /* **** Test tracking API ***** */ + + /** + * Try resolving a deposit's WTID, as we never triggered + * execution of transactions, the answer should be that + * the exchange knows about the deposit, but has no WTID yet. + */ + TALER_TESTING_cmd_track_transaction + ("deposit-wtid-found", is->exchange, + "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", is->exchange, + "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", is->exchange, + 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", "https://exchange.com/", + "EUR:4.98", 2, 42), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-99c1", "https://exchange.com/", + "EUR:0.98", 2, 42), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-99c2", "https://exchange.com/", + "EUR:0.98", 2, 42), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-99c", "https://exchange.com/", + "EUR:0.08", 2, 43), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-aai-1", "https://exchange.com/", + "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", "https://exchange.com/", + "EUR:5.01", 42, 2), + + TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"), + + TALER_TESTING_cmd_track_transaction + ("deposit-wtid-ok", is->exchange, + "deposit-simple", 0, MHD_HTTP_OK, "check_bank_transfer-499c"), + + TALER_TESTING_cmd_track_transfer + ("wire-deposit-success-bank", is->exchange, + "check_bank_transfer-99c1", 0, MHD_HTTP_OK, "EUR:0.98", + "EUR:0.01"), + + TALER_TESTING_cmd_track_transfer + ("wire-deposits-success-wtid", is->exchange, + "deposit-wtid-ok", 0, MHD_HTTP_OK, "EUR:4.98", + "EUR:0.01"), + + /* **** End of test tracking API ***** */ + + /* **** test /refund API ***** */ + + /** + * 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", + is->exchange, + "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", is->exchange, "withdraw-coin-r1", 0, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\", \"account_number\":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", "https://exchange.com/", + "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"), + + /** + * 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", is->exchange, "withdraw-coin-r1", 0, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\", \"account_number\":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", "https://exchange.com/", + "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", + is->exchange, + "create-reserve-rb", + "EUR:5", + MHD_HTTP_OK), + + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-aai-3b", "https://exchange.com/", + "EUR:5.01", 42, 2), + + + TALER_TESTING_cmd_deposit + ("deposit-refund-1b", is->exchange, "withdraw-coin-rb", 0, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\", \"account_number\":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"), + + /* ************** End of refund API testing ************* */ + + /* ************** Test /payback API ************* */ + + /** + * 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", + is->exchange, + "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"), + + /* Check the money is back with the reserve */ + TALER_TESTING_cmd_status ("payback-reserve-status-1", + is->exchange, + "payback-create-reserve-1", + "EUR:5.0", + MHD_HTTP_OK), + + /** + * 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", + is->exchange, + "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", + is->exchange, + "payback-create-reserve-2", + "EUR:1", + MHD_HTTP_OK), + + TALER_TESTING_cmd_deposit + ("payback-deposit-partial", is->exchange, + "payback-withdraw-coin-2a", 0, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\",\"account_number\":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", is->exchange, + "payback-withdraw-coin-2b", 0, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\",\"account_number\":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", is->exchange, + "payback-withdraw-coin-2a", 0, + TALER_TESTING_make_wire_details + ("{ \"type\":\"test\",\"account_number\":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", + is->exchange, + "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", "https://exchange.com/", + "EUR:5.01", 42, 2), + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-pr2", "https://exchange.com/", + "EUR:2.02", 42, 2), + TALER_TESTING_cmd_check_bank_transfer + ("check_bank_transfer-pr3", "https://exchange.com/", + "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"), + + /* ************** End of /payback API ************* */ + + /** + * End the suite. Fixme: better to have a label for this + * too, as it shows a "(null)" token on logs. + */ TALER_TESTING_cmd_end () }; @@ -105,7 +740,6 @@ run (void *cls, fakebank_url); } - int main (int argc, char * const *argv) @@ -116,9 +750,15 @@ main (int argc, GNUNET_log_setup ("test-exchange-api-new", "INFO", NULL); - if (NULL == (fakebank_url = TALER_TESTING_prepare_fakebank (CONFIG_FILE))) + if (NULL == (fakebank_url + /* Check fakebank port is available and config cares + * about bank url. */ + = TALER_TESTING_prepare_fakebank (CONFIG_FILE))) 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)) { case GNUNET_SYSERR: @@ -128,10 +768,15 @@ main (int argc, 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; diff --git a/src/exchange-lib/testing_api_cmd_bank_check.c b/src/exchange-lib/testing_api_cmd_bank_check.c new file mode 100644 index 000000000..ab5522441 --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_bank_check.c @@ -0,0 +1,268 @@ +/* + 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" + +struct BankCheckState +{ + + /** + * Exchange base URL (Fixme: why?) + */ + const char *exchange_base_url; + + /** + * Expected transferred amount. + */ + const char *amount; + + /** + * Expected account number that gave money + */ + unsigned int debit_account; + + /** + * Expected account number that received money + */ + unsigned int credit_account; + + /** + * Wire transfer subject (set by fakebank-lib). + */ + char *subject; + + /** + * Binary form of the transfer subject. Some commands expect + * it - via appropriate traits - to be in binary form. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; +}; + +/** + * Run the command. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command to execute, a /wire one. + * @param is the interpreter state. + */ +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; + + + 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 (GNUNET_OK != + TALER_FAKEBANK_check (is->fakebank, + &amount, + bcs->debit_account, + bcs->credit_account, + bcs->exchange_base_url, + &bcs->subject)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_interpreter_next (is); +} + +/** + * Cleanup the state. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command which is being cleaned up. + */ +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); +} + +/** + * Extract information from a command that is useful for other + * commands. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param selector more detailed information about which object + * to return in case there were multiple generated + * by the command + * @return #GNUNET_OK on success + */ +static int +check_bank_transfer_traits (void *cls, + void **ret, + const char *trait, + unsigned int index) +{ + + + struct BankCheckState *bcs = cls; + + GNUNET_assert (GNUNET_OK == + GNUNET_STRINGS_string_to_data + (bcs->subject, + strlen (bcs->subject), + &bcs->wtid, + sizeof (struct TALER_WireTransferIdentifierRawP))); + + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_transfer_subject (0, bcs->subject), + TALER_TESTING_make_trait_wtid (0, &bcs->wtid), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + + +/** + * Command to check whether a particular wire transfer has been + * made or not. + * + * @param label the command label + * @param exchange_base_url base url of the exchange (Fixme: why?) + * @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, + unsigned int debit_account, + unsigned int credit_account) +{ + struct BankCheckState *bcs; + struct TALER_TESTING_Command cmd; + + 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; + + cmd.label = label; + cmd.cls = bcs; + cmd.run = &check_bank_transfer_run; + cmd.cleanup = &check_bank_transfer_cleanup; + // traits? + cmd.traits = &check_bank_transfer_traits; + + return cmd; +} + +/** + * Cleanup the state. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command which is being cleaned up. + */ +void +check_bank_empty_cleanup + (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + return; +} + +/** + * Run the command. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command to execute, a /wire one. + * @param is the interpreter state. + */ +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); +} + +/** + * Check bank's balance is zero. + * + * @param credit_account the account that received money + * + * @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; + + return cmd; +} diff --git a/src/exchange-lib/testing_api_cmd_check_keys.c b/src/exchange-lib/testing_api_cmd_check_keys.c new file mode 100644 index 000000000..8f77a83b4 --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_check_keys.c @@ -0,0 +1,153 @@ +/* + 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" + +struct CheckKeysState +{ + /** + * FIXME + */ + unsigned int generation; + + /** + * FIXME + */ + unsigned int num_denom_keys; + + /** + * Connection to the exchange. + */ + struct TALER_EXCHANGE_Handle *exchange; + +}; + +/** + * Run the command. + * + * @param cls closure, typically a #struct SignalState. + * @param cmd the command to execute, a /wire one. + * @param is the interpreter state. + */ +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) + { + /* Go back to waiting for /keys signal! */ + is->working = GNUNET_NO; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Triggering /keys dl, cmd `%s'\n", + cmd->label); + GNUNET_break (0 == TALER_EXCHANGE_check_keys_current + (cks->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); + fprintf (stderr, "Got %u keys in step %s\n", + is->keys->num_denom_keys, cmd->label); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_interpreter_next (is); +} + + +/** + * Cleanup the state. + * + * @param cls closure, typically a #struct SignalState. + * @param cmd the command which is being cleaned up. + */ +void +check_keys_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct CheckKeysState *cks = cls; + + GNUNET_free (cks); +} + +/** + * Make a "check keys" command. + * + * @param label command label + * @param generation FIXME + * @param num_denom_keys FIXME + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_check_keys + (const char *label, + unsigned int generation, + unsigned int num_denom_keys, + struct TALER_EXCHANGE_Handle *exchange) +{ + + struct CheckKeysState *cks; + struct TALER_TESTING_Command cmd; + + cks = GNUNET_new (struct CheckKeysState); + + cks->generation = generation; + cks->num_denom_keys = num_denom_keys; + cks->exchange = exchange; + + cmd.cls = cks; + cmd.label = label; + cmd.run = &check_keys_run; + cmd.cleanup = &check_keys_cleanup; + + return cmd; + + +} diff --git a/src/exchange-lib/testing_api_cmd_deposit.c b/src/exchange-lib/testing_api_cmd_deposit.c new file mode 100644 index 000000000..f79515959 --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_deposit.c @@ -0,0 +1,448 @@ +/* + 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" + +struct DepositState +{ + + /** + * Amount to deposit. + */ + const char *amount; + + /** + * Reference to any command that is able to provide a coin. + */ + const char *coin_reference; + + /** + * If this @e coin_ref refers to an operation that generated + * an array of coins, this value determines which coin to pick. + */ + unsigned int coin_index; + + /** + * JSON string describing the merchant's "wire details". + */ + char *wire_details; + + /** + * JSON string describing what a proposal is about. + */ + const char *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. + */ + struct TALER_MerchantPrivateKeyP merchant_priv; + + /** + * Deposit handle while operation is running. + */ + struct TALER_EXCHANGE_DepositHandle *dh; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Exchange connection. + */ + struct TALER_EXCHANGE_Handle *exchange; +}; + +/** + * Function called with the result of a /deposit operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for + * successful deposit; 0 if the exchange's reply is bogus + * (fails to follow the protocol) + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param exchange_pub public key the exchange used for signing + * @param obj the received JSON reply, should be kept as proof + * (and, in case of errors, be forwarded to the customer) + */ +static void +deposit_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const json_t *obj) +{ + struct DepositState *ds = cls; + + ds->dh = NULL; + if (ds->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + ds->is->commands[ds->is->ip].label); + json_dumpf (obj, stderr, 0); + TALER_TESTING_interpreter_fail (ds->is); + return; + } + TALER_TESTING_interpreter_next (ds->is); +} + +/** + * Run the command. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command to execute, a /wire one. + * @param i the interpreter state. + */ +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; + struct TALER_CoinSpendPrivateKeyP *coin_priv; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_EXCHANGE_DenomPublicKey *denom_pub; + 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; + json_t *contract_terms; + json_t *wire; + 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; + } + contract_terms = json_loads (ds->contract_terms, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == contract_terms) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse proposal data `%s' at %u/%s\n", + ds->contract_terms, is->ip, this_cmd->label); + TALER_TESTING_interpreter_fail (is); + return; + } + GNUNET_assert (GNUNET_OK == + TALER_JSON_hash (contract_terms, + &h_contract_terms)); + json_decref (contract_terms); + + wire = json_loads (ds->wire_details, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == wire) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse wire details `%s' at %u/%s\n", + ds->wire_details, + is->ip, + this_cmd->label); + TALER_TESTING_interpreter_fail (is); + return; + } + + 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_hash + (wire, &dr.h_wire)); + dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); + dr.refund_deadline = GNUNET_TIME_absolute_hton + (refund_deadline); + TALER_amount_hton (&dr.amount_with_fee, &amount); + TALER_amount_hton + (&dr.deposit_fee, &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 + (ds->exchange, + &amount, + wire_deadline, + wire, + &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); + json_decref (wire); + TALER_TESTING_interpreter_fail (is); + return; + } + json_decref (wire); + return; +} + +/** + * Cleanup the state. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command which is being cleaned up. + */ +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; + } + + GNUNET_free (ds->wire_details); + GNUNET_free (ds); +} + +/** + * Extract information from a command that is useful for other + * commands. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param selector more detailed information about which object + * to return in case there were multiple generated + * by the command + * @return #GNUNET_OK on success + */ +static int +deposit_traits (void *cls, + 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. */ + 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[] = { + 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_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + +/** + * Create a deposit command. + * + * @param label command label + * @param exchange exchange connection + * @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 bank details of the merchant performing the + * deposit + * @param contract_terms contract terms to be signed over by the + * coin + * @param refund_deadline refund deadline, zero means 'no refunds' + * @param amount how much is going to be deposited + * @param expected_response_code which HTTP status code we expect + * in the response + * + * @return the deposit command to be run by the interpreter + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_deposit + (const char *label, + struct TALER_EXCHANGE_Handle *exchange, + const char *coin_reference, + unsigned int coin_index, + char *wire_details, + const char *contract_terms, + struct GNUNET_TIME_Relative refund_deadline, + const char *amount, + unsigned int expected_response_code) +{ + struct TALER_TESTING_Command cmd; + struct DepositState *ds; + + ds = GNUNET_new (struct DepositState); + ds->exchange = exchange; + ds->coin_reference = coin_reference; + ds->coin_index = coin_index; + ds->wire_details = wire_details; + ds->contract_terms = contract_terms; + ds->refund_deadline = refund_deadline; + ds->amount = amount; + ds->expected_response_code = expected_response_code; + + cmd.cls = ds; + cmd.label = label; + cmd.run = &deposit_run; + cmd.cleanup = &deposit_cleanup; + cmd.traits = &deposit_traits; + + return cmd; +} diff --git a/src/exchange-lib/testing_api_cmd_exec_aggregator.c b/src/exchange-lib/testing_api_cmd_exec_aggregator.c new file mode 100644 index 000000000..8d2131b11 --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_exec_aggregator.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 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" + + +struct AggregatorState +{ + + /** + * Process for the aggregator. + */ + struct GNUNET_OS_Process *aggregator_proc; + + /** + * Which configuration file should we pass to the process? + */ + const char *config_filename; + +}; + + +/** + * Runs the command. Note that upon return, the interpreter + * will not automatically run the next command, as the command + * may continue asynchronously in other scheduler tasks. Thus, + * the command must ensure to eventually call + * #TALER_TESTING_interpreter_next() or + * #TALER_TESTING_interpreter_fail(). + * + * @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); +} + + +/** + * Clean up after the command. Run during forced termination + * (CTRL-C) or test failure or test success. + * + * @param cls closure + */ +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); +} + + +/** + * Extract information from a command that is useful for other + * commands. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param selector more detailed information about which object + * to return in case there were multiple generated + * by the command + * @return #GNUNET_OK on success + */ +static int +aggregator_traits (void *cls, + 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); +} + + +/** + * Execute taler-exchange-wirewatch process. + * + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_aggregator (const char *label, + const char *config_filename) +{ + struct TALER_TESTING_Command cmd; + struct AggregatorState *as; + + as = GNUNET_new (struct AggregatorState); + as->config_filename = config_filename; + cmd.cls = as; + cmd.label = label; + cmd.run = &aggregator_run; + cmd.cleanup = &aggregator_cleanup; + cmd.traits = &aggregator_traits; + return cmd; +} + +/* end of testing_api_cmd_exec_aggregator.c */ diff --git a/src/exchange-lib/testing_api_cmd_exec_auditor-sign.c b/src/exchange-lib/testing_api_cmd_exec_auditor-sign.c new file mode 100644 index 000000000..bae0c076a --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_exec_auditor-sign.c @@ -0,0 +1,221 @@ +/* + 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" + + +struct AuditorSignState +{ + + /** + * Process for the "auditor sign" command. + */ + struct GNUNET_OS_Process *auditor_sign_proc; + + /** + * Which configuration file should we pass to the process? + */ + const char *config_filename; + +}; + + +/** + * Runs the command. Note that upon return, the interpreter + * will not automatically run the next command, as the command + * may continue asynchronously in other scheduler tasks. Thus, + * the command must ensure to eventually call + * #TALER_TESTING_interpreter_next() or + * #TALER_TESTING_interpreter_fail(). + * + * @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 *signed_keys_out; + char *exchange_master_pub; + + 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_string (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; + } + + GNUNET_asprintf (&signed_keys_out, + "%s/.local/share/taler/auditors/auditor.out", + 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", signed_keys_out, + NULL); + + if (NULL == ass->auditor_sign_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Clean up after the command. Run during forced termination + * (CTRL-C) or test failure or test success. + * + * @param cls closure + */ +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 (ass); +} + + +/** + * Extract information from a command that is useful for other + * commands. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param selector more detailed information about which object + * to return in case there were multiple generated + * by the command + * @return #GNUNET_OK on success + */ +static int +auditor_sign_traits (void *cls, + 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); +} + + +/** + * Execute taler-auditor-sign process. + * + * @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 TALER_TESTING_Command cmd; + struct AuditorSignState *ass; + + ass = GNUNET_new (struct AuditorSignState); + ass->config_filename = config_filename; + cmd.cls = ass; + cmd.label = label; + cmd.run = &auditor_sign_run; + cmd.cleanup = &auditor_sign_cleanup; + cmd.traits = &auditor_sign_traits; + return cmd; +} + +/* end of testing_api_cmd_exec_auditor-sign.c */ diff --git a/src/exchange-lib/testing_api_cmd_exec_keyup.c b/src/exchange-lib/testing_api_cmd_exec_keyup.c new file mode 100644 index 000000000..9398dd4fe --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_exec_keyup.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_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" + + +struct KeyupState +{ + + /** + * Process for the "keyup" command. + */ + struct GNUNET_OS_Process *keyup_proc; + + /** + * Which configuration file should we pass to the process? + */ + const char *config_filename; + +}; + + +/** + * Runs the command. Note that upon return, the interpreter + * will not automatically run the next command, as the command + * may continue asynchronously in other scheduler tasks. Thus, + * the command must ensure to eventually call + * #TALER_TESTING_interpreter_next() or + * #TALER_TESTING_interpreter_fail(). + * + * @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); +} + + +/** + * Clean up after the command. Run during forced termination + * (CTRL-C) or test failure or test success. + * + * @param cls closure + */ +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); +} + + +/** + * Extract information from a command that is useful for other + * commands. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param selector more detailed information about which object + * to return in case there were multiple generated + * by the command + * @return #GNUNET_OK on success + */ +static int +keyup_traits (void *cls, + 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); +} + + +/** + * Execute taler-exchange-keyup process. + * + * @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 TALER_TESTING_Command cmd; + struct KeyupState *ks; + + ks = GNUNET_new (struct KeyupState); + ks->config_filename = config_filename; + cmd.cls = ks; + cmd.label = label; + cmd.run = &keyup_run; + cmd.cleanup = &keyup_cleanup; + cmd.traits = &keyup_traits; + return cmd; +} + +/* end of testing_api_cmd_exec_keyup.c */ diff --git a/src/exchange-lib/testing_api_cmd_exec_wirewatch.c b/src/exchange-lib/testing_api_cmd_exec_wirewatch.c index 7b95afbc4..107ac55a7 100644 --- a/src/exchange-lib/testing_api_cmd_exec_wirewatch.c +++ b/src/exchange-lib/testing_api_cmd_exec_wirewatch.c @@ -122,11 +122,11 @@ static int wirewatch_traits (void *cls, void **ret, const char *trait, - const char *selector) + unsigned int index) { struct WirewatchState *ws = cls; struct TALER_TESTING_Trait traits[] = { - TALER_TESTING_make_trait_process (NULL, + TALER_TESTING_make_trait_process (0, &ws->wirewatch_proc), TALER_TESTING_trait_end () }; @@ -134,7 +134,7 @@ wirewatch_traits (void *cls, return TALER_TESTING_get_trait (traits, ret, trait, - selector); + index); } diff --git a/src/exchange-lib/testing_api_cmd_fakebank_transfer.c b/src/exchange-lib/testing_api_cmd_fakebank_transfer.c index 6b1a99dc3..dfbaaddea 100644 --- a/src/exchange-lib/testing_api_cmd_fakebank_transfer.c +++ b/src/exchange-lib/testing_api_cmd_fakebank_transfer.c @@ -2,18 +2,21 @@ 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 + 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 @@ -104,14 +107,17 @@ struct FakebankTransferState /** - * Function called upon completion of our /admin/add/incoming request. + * Function called upon completion of our /admin/add/incoming + * request. * * @param cls closure with the interpreter state - * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request - * 0 if the exchange's reply is bogus (fails to follow the protocol) + * @param 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) + * @param full_response full response from the exchange (for + * logging, in case of errors) */ static void add_incoming_cb (void *cls, @@ -167,8 +173,8 @@ fakebank_transfer_run (void *cls, const struct TALER_TESTING_Command *ref; struct TALER_ReservePrivateKeyP *reserve_priv; - ref = TALER_TESTING_interpreter_lookup_command (is, - fts->reserve_reference); + ref = TALER_TESTING_interpreter_lookup_command + (is, fts->reserve_reference); if (NULL == ref) { GNUNET_break (0); @@ -177,7 +183,7 @@ fakebank_transfer_run (void *cls, } if (GNUNET_OK != TALER_TESTING_get_trait_reserve_priv (ref, - NULL, + 0, &reserve_priv)) { GNUNET_break (0); @@ -193,27 +199,27 @@ fakebank_transfer_run (void *cls, 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)); + 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, - "https://exchange.com/", /* exchange URL: FIXME */ - subject, - &fts->amount, - fts->debit_account_no, - fts->credit_account_no, - &add_incoming_cb, - fts); + fts->aih = TALER_BANK_admin_add_incoming + (TALER_TESTING_interpreter_get_context (is), + fts->bank_url, + &auth, + "https://exchange.com/", /* exchange URL: FIXME */ + subject, + &fts->amount, + fts->debit_account_no, + fts->credit_account_no, + &add_incoming_cb, + fts); GNUNET_free (subject); if (NULL == fts->aih) { @@ -263,11 +269,11 @@ static int fakebank_transfer_traits (void *cls, void **ret, const char *trait, - const char *selector) + unsigned int index) { struct FakebankTransferState *fts = cls; struct TALER_TESTING_Trait traits[] = { - TALER_TESTING_make_trait_reserve_priv (NULL, + TALER_TESTING_make_trait_reserve_priv (0, &fts->reserve_priv), TALER_TESTING_trait_end () }; @@ -275,12 +281,13 @@ fakebank_transfer_traits (void *cls, if (NULL != fts->subject) { GNUNET_break (0); - return GNUNET_SYSERR; /* we do NOT create a reserve private key */ + /* we do NOT create a reserve private key */ + return GNUNET_SYSERR; } return TALER_TESTING_get_trait (traits, ret, trait, - selector); + index); } @@ -330,14 +337,15 @@ TALER_TESTING_cmd_fakebank_transfer (const char *label, * */ 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) +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) { struct TALER_TESTING_Command cmd; struct FakebankTransferState *fts; @@ -373,14 +381,15 @@ TALER_TESTING_cmd_fakebank_transfer_with_subject (const char *label, * */ 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) +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) { struct TALER_TESTING_Command cmd; struct FakebankTransferState *fts; diff --git a/src/exchange-lib/testing_api_cmd_payback.c b/src/exchange-lib/testing_api_cmd_payback.c new file mode 100644 index 000000000..6c791e561 --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_payback.c @@ -0,0 +1,491 @@ +/* + 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" + +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 filename. + */ + const char *config_filename; + + /** + * Encoding of the denomination (to revoke) public key hash. + */ + char *dhks; + +}; + +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; + + /** + * Connection to the exchange. + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * Handle to the ongoing operation. + */ + struct TALER_EXCHANGE_PaybackHandle *ph; + +}; + +/** + * Check the result of the payback request. + * + * @param cls closure + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for + * successful status request; 0 if the exchange's reply is + * bogus (fails to follow the protocol) + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param amount amount the exchange will wire back for this coin + * @param timestamp what time did the exchange receive the + * /payback request + * @param reserve_pub public key of the reserve receiving the + * payback + * @param full_response full response from the exchange (for + * logging, in case of errors) + */ +static void +payback_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Absolute timestamp, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *full_response) +{ + + struct 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; + 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\n", + http_status, + cmd->label); + 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, typically a #struct WireState. + * @param cmd the command to execute, a /wire one. + * @param is the interpreter state. + */ +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; + struct TALER_CoinSpendPrivateKeyP *coin_priv; + struct TALER_DenominationBlindingKeyP *blinding_key; + struct TALER_EXCHANGE_DenomPublicKey *denom_pub; + struct TALER_DenominationSignature *coin_sig; + struct TALER_PlanchetSecretsP planchet; + + ps->is = is; + ps->exchange = is->exchange; + 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 (ps->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. + */ +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 (rs->dhks); + GNUNET_free (rs); +} + +/** + * Cleanup the state. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command which is being cleaned up. + */ +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); +} + +/** + * Extract information from a command that is useful for other + * commands. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param selector more detailed information about which object + * to return in case there were multiple generated + * by the command + * @return #GNUNET_OK on success + */ +static int +revoke_traits (void *cls, + 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 command. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command to execute, a /wire one. + * @param is the interpreter state. + */ +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; + 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 reserve private key + * @param amount denomination to pay back. + * + * @return a /revoke 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; + struct TALER_TESTING_Command cmd; + + ps = GNUNET_new (struct PaybackState); + ps->expected_response_code = expected_response_code; + ps->coin_reference = coin_reference; + ps->amount = amount; + + cmd.cls = ps; + cmd.label = label; + cmd.run = &payback_run; + cmd.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 any command which offers + * a coin trait + * @param config_filename configuration file name. + * + * @return a /revoke 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; + struct TALER_TESTING_Command cmd; + + rs = GNUNET_new (struct RevokeState); + rs->expected_response_code = expected_response_code; + rs->coin_reference = coin_reference; + rs->config_filename = config_filename; + + cmd.cls = rs; + cmd.label = label; + cmd.run = &revoke_run; + cmd.cleanup = &revoke_cleanup; + cmd.traits = &revoke_traits; + + return cmd; +} diff --git a/src/exchange-lib/testing_api_cmd_refresh.c b/src/exchange-lib/testing_api_cmd_refresh.c new file mode 100644 index 000000000..3e58552ba --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_refresh.c @@ -0,0 +1,993 @@ +/* + 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" + +/** + * Structure specifying details about a coin to be melted. + * Used in a NULL-terminated array as part of command + * specification. + */ +struct MeltDetails +{ + + /** + * Amount to melt (including fee). + */ + const char *amount; + + /** + * Reference to reserve_withdraw operations for coin to + * be used for the /refresh/melt operation. + */ + const char *coin_reference; +}; + + +/** + * State for a "refresh melt" operation. + */ +struct RefreshMeltState +{ + /** + * Fixme: figure out this data purpose. + */ + const char *amount; + + /** + * Information about coins to be melted. + */ + struct MeltDetails melted_coin; + + /** + * Data used in the refresh operation. + */ + char *refresh_data; + + /** + * Number of bytes in @e refresh_data. + */ + size_t refresh_data_length; + + /** + * Reference to a previous melt command. + */ + const char *melt_reference; + + /** + * Melt handle while operation is running. + */ + struct TALER_EXCHANGE_RefreshMeltHandle *rmh; + + /** + * Connection to the exchange. + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Array of the public keys corresponding to + * the @e fresh_amounts, set by the interpreter. + */ + struct TALER_EXCHANGE_DenomPublicKey *fresh_pks; + + /** + * Set by the interpreter (upon completion) to the + * noreveal index selected by the exchange. + */ + uint16_t noreveal_index; +}; + +/** + * State for a "refresh reveal" operation. + */ +struct RefreshRevealState +{ + /** + * Link to a "refresh melt" command. + */ + const char *melt_reference; + + /** + * Reveal handle while operation is running. + */ + struct TALER_EXCHANGE_RefreshRevealHandle *rrh; + + /** + * Number of fresh coins withdrawn, set by the interpreter. + * Length of the @e fresh_coins array. + */ + unsigned int num_fresh_coins; + + /** + * Information about coins withdrawn, set by the interpreter. + */ + struct FreshCoin *fresh_coins; + + /** + * Connection to the exchange. + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; +}; + +/** + * State for a "refresh link" operation. + */ +struct RefreshLinkState +{ + /** + * Link to a "refresh reveal" command. + */ + const char *reveal_reference; + + /** + * Link handle while operation is running. + */ + struct TALER_EXCHANGE_RefreshLinkHandle *rlh; + + /** + * Connection to the exchange. + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; +}; + + +/** + * Function called with the result of the /refresh/reveal operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for + * successful status request. 0 if the exchange's reply is + * bogus (fails to follow the protocol) + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param num_coins number of fresh coins created, length of the + * @a sigs and @a coin_privs arrays, 0 if the operation + * failed + * @param coin_privs array of @a num_coins private keys for the + * coins that were created, NULL on error + * @param sigs array of signature over @a num_coins coins, NULL on + * error + * @param full_response full response from the exchange (for + * logging, in case of errors) + */ +static void +reveal_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + unsigned int num_coins, + const struct TALER_CoinSpendPrivateKeyP *coin_privs, + const struct TALER_DenominationSignature *sigs, + const json_t *full_response) +{ + + struct RefreshRevealState *rrs = cls; + const struct TALER_TESTING_Command *melt_cmd; + + rrs->rrh = NULL; + if (rrs->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + rrs->is->commands[rrs->is->ip].label); + 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); + + struct TALER_EXCHANGE_DenomPublicKey *fresh_pks; + unsigned int i; + 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 (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, a /wire one. + * @param is the interpreter state. + */ +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 + (rrs->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; + } +} + + +/** + * Cleanup the state. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +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; + } + + { /* Not sure why block-ing this */ + unsigned int j; + + for (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; +} + + +/** + * Function called with the result of a /refresh/link operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for + * successful status request 0 if the exchange's reply is + * bogus (fails to follow the protocol) + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param num_coins number of fresh coins created, length of the + * @a sigs and @a coin_privs arrays, 0 if the operation + * failed + * @param coin_privs array of @a num_coins private keys for the + * coins that were created, NULL on error + * @param sigs array of signature over @a num_coins coins, NULL on + * error + * @param pubs array of public keys for the @a sigs, NULL on error + * @param full_response full response from the exchange (for + * logging, in case of errors) + */ +static void +link_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + unsigned int num_coins, + const struct TALER_CoinSpendPrivateKeyP *coin_privs, + const struct TALER_DenominationSignature *sigs, + const struct TALER_DenominationPublicKey *pubs, + const json_t *full_response) +{ + + struct 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; + unsigned int *num_fresh_coins; + + rls->rlh = NULL; + if (rls->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + link_cmd->label); + 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\n", + num_coins, *num_fresh_coins); + 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. */ + 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[i].coin_priv, + sizeof (struct TALER_CoinSpendPrivateKeyP))) && + (0 == GNUNET_CRYPTO_rsa_signature_cmp + (fc[i].sig.rsa_signature, + sigs[i].rsa_signature)) && + (0 == GNUNET_CRYPTO_rsa_public_key_cmp + (fc[i].pk->key.rsa_public_key, + pubs[i].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, a /wire one. + * @param i the interpreter state. + */ +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; + } + } + + 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 + (rls->exchange, coin_priv, &link_cb, rls); + + if (NULL == rls->rlh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } +} + +/** + * Cleanup the state. + * + * @param cls closure + * @param cmd the command which is being cleaned up. + */ +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; + } +} + + +/** + * Function called with the result of the /refresh/melt operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, never #MHD_HTTP_OK (200) + * as for successful intermediate response this callback is + * skipped. 0 if the exchange's reply is bogus (fails to + * follow the protocol) + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param noreveal_index choice by the exchange in the + * cut-and-choose protocol, UINT16_MAX on error + * @param exchange_pub public key the exchange used for signing + * @param full_response full response from the exchange (for + * logging, in case of errors) + */ +static void +melt_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + 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) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + rms->is->commands[rms->is->ip].label); + json_dumpf (full_response, stderr, 0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + rms->noreveal_index = noreveal_index; + TALER_TESTING_interpreter_next (rms->is); +} + +/** + * Run the command. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command to execute, a /wire one. + * @param i the interpreter state. + */ +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; + const char *melt_fresh_amounts[] = { + /* with 0.01 withdraw fees (except for 1ct coins), + this totals up to exactly EUR:3.97, and with + the 0.03 refresh fee, to EUR:4.0 */ + "EUR:1", "EUR:1", "EUR:1", "EUR:0.1", "EUR:0.1", "EUR:0.1", + "EUR:0.1", "EUR:0.1", "EUR:0.1", "EUR:0.1", "EUR:0.1", + "EUR:0.01", "EUR:0.01", "EUR:0.01", "EUR:0.01", "EUR:0.01", + "EUR:0.01", 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); + { + struct TALER_CoinSpendPrivateKeyP *melt_priv; + struct TALER_Amount melt_amount; + struct TALER_Amount fresh_amount; + struct TALER_DenominationSignature *melt_sig; + struct TALER_EXCHANGE_DenomPublicKey *melt_denom_pub; + unsigned int i; + + 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 (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 (rms->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 + (rms->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; + } + } +} + +/** + * Cleanup the state. + * + * @param cls closure, typically a #struct RefreshMeltState. + * @param cmd the command which is being cleaned up. + */ +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; + } + 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; +} + +/** + * Extract information from a command that is useful for other + * commands. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param selector more detailed information about which object + * to return in case there were multiple generated + * by the command + * @return #GNUNET_OK on success + */ +static int +refresh_melt_traits (void *cls, + 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 exchange connection to the exchange + * @param amount Fixme + * @param coin_reference reference to a command that will provide + * a coin to refresh + * @param expected_response_code expected HTTP code + */ + +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_melt + (const char *label, + struct TALER_EXCHANGE_Handle *exchange, + const char *amount, + const char *coin_reference, + unsigned int expected_response_code) +{ + struct RefreshMeltState *rms; + struct MeltDetails md; + struct TALER_TESTING_Command cmd; + + md.coin_reference = coin_reference; + md.amount = amount; + + rms = GNUNET_new (struct RefreshMeltState); + rms->amount = amount; + rms->melted_coin = md; + rms->expected_response_code = expected_response_code; + rms->exchange = exchange; + + cmd.label = label; + cmd.cls = rms; + cmd.run = &refresh_melt_run; + cmd.cleanup = &refresh_melt_cleanup; + cmd.traits = &refresh_melt_traits; + + return cmd; +} + +/** + * Extract information from a command that is useful for other + * commands. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param selector more detailed information about which object + * to return in case there were multiple generated + * by the command + * @return #GNUNET_OK on success + */ +static int +refresh_reveal_traits (void *cls, + 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]; + unsigned int i; + + /* Making coin privs traits */ + for (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 (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 (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 "refresh reveal" command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_reveal + (const char *label, + struct TALER_EXCHANGE_Handle *exchange, + const char *melt_reference, + unsigned int expected_response_code) +{ + struct RefreshRevealState *rrs; + struct TALER_TESTING_Command cmd; + + rrs = GNUNET_new (struct RefreshRevealState); + rrs->melt_reference = melt_reference; + rrs->exchange = exchange; + rrs->expected_response_code = expected_response_code; + + cmd.cls = rrs; + cmd.label = label; + cmd.run = &refresh_reveal_run; + cmd.cleanup = &refresh_reveal_cleanup; + cmd.traits = &refresh_reveal_traits; + + return cmd; +} + + +/** + * Create a "refresh link" 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 "refresh link" command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_link + (const char *label, + struct TALER_EXCHANGE_Handle *exchange, + const char *reveal_reference, + unsigned int expected_response_code) +{ + struct RefreshLinkState *rrs; + struct TALER_TESTING_Command cmd; + + rrs = GNUNET_new (struct RefreshLinkState); + rrs->reveal_reference = reveal_reference; + rrs->exchange = exchange; + rrs->expected_response_code = expected_response_code; + + cmd.cls = rrs; + cmd.label = label; + cmd.run = &refresh_link_run; + cmd.cleanup = &refresh_link_cleanup; + + return cmd; +} diff --git a/src/exchange-lib/testing_api_cmd_refund.c b/src/exchange-lib/testing_api_cmd_refund.c new file mode 100644 index 000000000..a092ee2d2 --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_refund.c @@ -0,0 +1,295 @@ +/* + 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" + +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. Left un-initialized in the + * old test-suite. What's the best way to init it? + */ + 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. + * + * @param cls closure + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for + * successful deposit; 0 if the exchange's reply is bogus + * (fails to follow the protocol). + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param exchange_pub public key the exchange used for signing @a + * obj + * @param obj the received JSON reply, should be kept as proof + * (and, in particular, be forwarded to the customer) + */ +static void +refund_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const json_t *obj) +{ + + struct 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\n", + http_status, + refund_cmd->label); + json_dumpf (obj, stderr, 0); + TALER_TESTING_interpreter_fail (rs->is); + return; + } + + switch (http_status) + { + case MHD_HTTP_OK: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Good /refund status code\n"); + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unmanaged HTTP status code: %u, command: %s\n", + http_status, refund_cmd->label); + } + + TALER_TESTING_interpreter_next (rs->is); +} + +/** + * Run the command. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command to execute, a /wire one. + * @param i the interpreter state. + */ +void +refund_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct RefundState *rs = cls; + struct TALER_CoinSpendPrivateKeyP *coin_priv; + struct TALER_CoinSpendPublicKeyP coin; + const char *contract_terms; + struct GNUNET_HashCode h_contract_terms; + json_t *j_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; + } + + j_contract_terms = json_loads + (contract_terms, JSON_REJECT_DUPLICATES, NULL); + + /* Very unlikely to fail */ + GNUNET_assert (NULL != j_contract_terms); + GNUNET_assert (GNUNET_OK == TALER_JSON_hash + (j_contract_terms, &h_contract_terms)); + + json_decref (j_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); +} + + +/** + * Cleanup the state. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command which is being cleaned up. + */ +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 test 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. + */ +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; + struct TALER_TESTING_Command cmd; + + 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; + + cmd.cls = rs; + cmd.label = label; + cmd.run = &refund_run; + cmd.cleanup = &refund_cleanup; + + return cmd; +} diff --git a/src/exchange-lib/testing_api_cmd_signal.c b/src/exchange-lib/testing_api_cmd_signal.c new file mode 100644 index 000000000..5b0fa1b9d --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_signal.c @@ -0,0 +1,112 @@ +/* + 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" + +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, typically a #struct SignalState. + * @param cmd the command to execute, a /wire one. + * @param is the interpreter state. + */ +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. + * + * @param cls closure, typically a #struct SignalState. + * @param cmd the command which is being cleaned up. + */ +void +signal_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct SignalState *ss = cls; + + GNUNET_free (ss); +} + +/** + * Send a signal to a process. + * + * @param process handle to the process + * @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; + struct TALER_TESTING_Command cmd; + + ss = GNUNET_new (struct SignalState); + + ss->process = process; + ss->signal = signal; + + cmd.cls = ss; + cmd.label = label; + cmd.run = &signal_run; + cmd.cleanup = &signal_cleanup; + + return cmd; +} diff --git a/src/exchange-lib/testing_api_cmd_status.c b/src/exchange-lib/testing_api_cmd_status.c new file mode 100644 index 000000000..c948277b5 --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_status.c @@ -0,0 +1,240 @@ +/* + 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" + +struct StatusState +{ + /** + * Label to the command which created the reserve to check, + * needed to resort the reserve key. + */ + const char *reserve_reference; + + /** + * Handle to a /reserve/status operation. + */ + struct TALER_EXCHANGE_ReserveStatusHandle *rsh; + + /** + * Expected reserve balance. + */ + const char *expected_balance; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Handle to the exchange. + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; +}; + +/** + * Check exchange returned expected values. + * + * @param cls closure + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for + * successful status request 0 if the exchange's reply is + * bogus (fails to follow the protocol) + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param[in] json original response in JSON format (useful only + * for diagnostics) + * @param balance current balance in the reserve, NULL on error + * @param history_length number of entries in the transaction + * history, 0 on error + * @param history detailed transaction history, NULL on error + */ +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\n", + http_status); + 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\n"); + 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). + */ + + TALER_TESTING_interpreter_next (ss->is); +} + +/** + * Run the command. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command to execute, a /wire one. + * @param i the interpreter state. + */ +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; + 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; + } + + if (GNUNET_OK != + TALER_TESTING_get_trait_reserve_priv (create_reserve, + 0, + &reserve_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &reserve_pub.eddsa_pub); + ss->rsh + = TALER_EXCHANGE_reserve_status (ss->exchange, + &reserve_pub, + &reserve_status_cb, + ss); +} + +/** + * Cleanup the state. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command which is being cleaned up. + */ +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 exchange the exchange to connect to. + * @param reserve_reference reference to the reserve to check. + * @param expected_balance balance expected to be at the referenced reserve. + * @param expected_response_code expected HTTP response code. + * + * @return the command to be executed by the interpreter. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_status (const char *label, + struct TALER_EXCHANGE_Handle *exchange, + const char *reserve_reference, + const char *expected_balance, + unsigned int expected_response_code) +{ + struct TALER_TESTING_Command cmd; + struct StatusState *ss; + + ss = GNUNET_new (struct StatusState); + ss->exchange = exchange; + ss->reserve_reference = reserve_reference; + ss->expected_balance = expected_balance; + ss->expected_response_code = expected_response_code; + + cmd.cls = ss; + cmd.label = label; + cmd.run = &status_run; + cmd.cleanup = &status_cleanup; + + return cmd; +} diff --git a/src/exchange-lib/testing_api_cmd_track.c b/src/exchange-lib/testing_api_cmd_track.c new file mode 100644 index 000000000..40894872b --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_track.c @@ -0,0 +1,819 @@ +/* + 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" + +struct TrackTransactionState +{ + + /** + * Which #OC_CHECK_BANK_TRANSFER wtid should this match? NULL + * for none. + */ + const char *bank_transfer_reference; + + /** + * Wire transfer identifier, set if #MHD_HTTP_OK was the + * response code. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Reference to any operation that can provide a transaction. + * Tipically a /deposit operation. + */ + const char *transaction_reference; + + /** + * Index of the coin involved in the transaction. + */ + unsigned int coin_index; + + /** + * Handle to the deposit wtid request. + */ + struct TALER_EXCHANGE_TrackTransactionHandle *tth; + + /** + * Handle to the exchange. + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; +}; + +struct TrackTransferState +{ + + /** + * Expected amount for this WTID. + */ + 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. + */ + const char *wtid_reference; + + /** + * Reference to any operation that can provide wire details. + */ + const char *wire_details_reference; + + /** + * Reference to any operation that can provide an amount. + */ + const char *total_amount_reference; + + /** + * Index to the WTID to pick. + */ + unsigned int index; + + /** + * Handle to the deposit wtid request. + */ + struct TALER_EXCHANGE_TrackTransferHandle *tth; + + /** + * Handle to the exchange. + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; +}; + + +/** + * Function called with detailed wire transfer data. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on exchange + * protocol violation + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param exchange_pub public key the exchange used for signing + * @param json original json reply (may include signatures, those + * have then been validated already) + * @param wtid wire transfer identifier used by the exchange, NULL + * if exchange did not yet execute the transaction + * @param execution_time actual or planned execution time for the + * wire transfer + * @param coin_contribution contribution to the @a total_amount of + * the deposited coin (may be NULL) + * @param total_amount total amount of the wire transfer, or NULL + * if the exchange could not provide any @a wtid (set only + * if @a http_status is #MHD_HTTP_OK) + */ +static void +deposit_wtid_cb + (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const json_t *json, + const struct TALER_WireTransferIdentifierRawP *wtid, + struct GNUNET_TIME_Absolute execution_time, + const struct TALER_Amount *coin_contribution) +{ + struct 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\n", + http_status, + cmd->label); + 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; + + 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; + } + + 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; + } + + 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, typically a #struct WireState. + * @param cmd the command to execute, a /track/transaction one. + * @param is the interpreter state. + */ +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; + struct TALER_CoinSpendPrivateKeyP *coin_priv; + struct TALER_CoinSpendPublicKeyP coin_pub; + const char *wire_details; + const char *contract_terms; + json_t *j_wire_details; + json_t *j_contract_terms; + 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; + } + + /* Parse them.. */ + j_wire_details = json_loads + (wire_details, JSON_REJECT_DUPLICATES, NULL); + j_contract_terms = json_loads + (contract_terms, JSON_REJECT_DUPLICATES, NULL); + + if ((NULL == j_wire_details) || (NULL == j_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_hash (j_wire_details, + &h_wire_details)) && + (GNUNET_OK == TALER_JSON_hash (j_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 + (tts->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. + * + * @param cls closure, typically a #struct WireState. + * @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); +} + + +/** + * Extract information from a command that is useful for other + * commands. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param selector more detailed information about which object + * to return in case there were multiple generated + * by the command + * @return #GNUNET_OK on success + */ +static int +track_transaction_traits (void *cls, + 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 exchange the exchange to connect to. + * @param transaction_reference reference to a deposit operation. + * @param coin_index index of the coin involved in the transaction + * @param expected_response_code expected HTTP response code. + * @param bank_transfer_reference which #OC_CHECK_BANK_TRANSFER + * wtid should this match? NULL + * for none + * + * @return the command to be executed by the interpreter. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_track_transaction + (const char *label, + struct TALER_EXCHANGE_Handle *exchange, + const char *transaction_reference, + unsigned int coin_index, + unsigned int expected_response_code, + const char *bank_transfer_reference) +{ + struct TALER_TESTING_Command cmd; + struct TrackTransactionState *tts; + + tts = GNUNET_new (struct TrackTransactionState); + tts->exchange = exchange; + tts->transaction_reference = transaction_reference; + tts->expected_response_code = expected_response_code; + tts->bank_transfer_reference = bank_transfer_reference; + tts->coin_index = coin_index; + + cmd.cls = tts; + cmd.label = label; + cmd.run = &track_transaction_run; + cmd.cleanup = &track_transaction_cleanup; + cmd.traits = &track_transaction_traits; + + return cmd; + +} + +/** + * Cleanup the state. + * + * @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); + +} + +/** + * Function called with detailed wire transfer data, including all + * of the coin transactions that were combined into the wire + * transfer. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on exchange + * protocol violation + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param exchange_pub public key the exchange used for signing + * @param json original json reply (may include signatures, those + * have then been validated already) + * @param h_wire hash of the wire transfer address the transfer + * went to, or NULL on error + * @param execution_time time when the exchange claims to have + * performed the wire transfer + * @param total_amount total amount of the wire transfer, or NULL + * if the exchange could not provide any @a wtid (set only + * if @a http_status is #MHD_HTTP_OK) + * @param wire_fee wire fee that was charged by the exchange + * @param details_length length of the @a details array + * @param details array with details about the combined + * transactions + */ +static void +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]; + + const struct TALER_TESTING_Command *wtid_cmd; + 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\n", + http_status, + cmd->label); + json_dumpf (json, stderr, 0); + TALER_TESTING_interpreter_fail (is); + return; + } + + switch (http_status) + { + + 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"); + + 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; + } + + wtid_cmd = TALER_TESTING_interpreter_lookup_command + (is, tts->wtid_reference); + + if (NULL == wtid_cmd) + { + GNUNET_break (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 char *wire_details; + json_t *j_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; + } + + j_wire_details = json_loads + (wire_details, JSON_REJECT_DUPLICATES, NULL); + + GNUNET_assert (NULL != j_wire_details); + + GNUNET_assert (GNUNET_OK == TALER_JSON_hash + (j_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 to execute, a /track/transfer one. + * @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; + 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; + } + } + tts->tth = TALER_EXCHANGE_track_transfer (tts->exchange, + wtid_ptr, + &track_transfer_cb, + tts); + + GNUNET_assert (NULL != tts->tth); +} + +/** + * Make a /track/transfer command, expecting the transfer + * not being done (yet). + * + * @param label the command label + * @param exchange connection to the exchange + * @param wtid_reference reference to any command which can provide + * a wtid + * @param index in case there are multiple wtid offered, this + * parameter selects a particular one + * @param expected_response_code expected HTTP response code + * + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_track_transfer_empty + (const char *label, + struct TALER_EXCHANGE_Handle *exchange, + const char *wtid_reference, + unsigned int index, + unsigned int expected_response_code) +{ + struct TrackTransferState *tts; + struct TALER_TESTING_Command cmd; + + tts = GNUNET_new (struct TrackTransferState); + + tts->wtid_reference = wtid_reference; + tts->index = index; + tts->expected_response_code = expected_response_code; + tts->exchange = exchange; + + cmd.cls = tts; + cmd.label = label; + cmd.run = &track_transfer_run; + cmd.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 exchange connection to the exchange + * @param wtid_reference reference to any command which can provide + * a wtid + * @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, + struct TALER_EXCHANGE_Handle *exchange, + 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; + struct TALER_TESTING_Command cmd; + + tts = GNUNET_new (struct TrackTransferState); + + tts->wtid_reference = wtid_reference; + tts->index = index; + tts->expected_response_code = expected_response_code; + tts->exchange = exchange; + tts->expected_total_amount = expected_total_amount; + tts->expected_wire_fee = expected_wire_fee; + + cmd.cls = tts; + cmd.label = label; + cmd.run = &track_transfer_run; + cmd.cleanup = &track_transfer_cleanup; + + return cmd; +} diff --git a/src/exchange-lib/testing_api_cmd_wire.c b/src/exchange-lib/testing_api_cmd_wire.c new file mode 100644 index 000000000..f65daec00 --- /dev/null +++ b/src/exchange-lib/testing_api_cmd_wire.c @@ -0,0 +1,267 @@ +/* + 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_testing_lib.h" + +struct WireState +{ + + /** + * Handle to the /wire operation. + */ + struct TALER_EXCHANGE_WireHandle *wh; + + /** + * Which wire-method we expect are 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; + + /** + * Connection to the exchange. + */ + struct TALER_EXCHANGE_Handle *exchange; +}; + + +/** + * Check all the expected values have been returned by /wire. + * + * @param cls closure + * @param wire_method name of the wire method (i.e. "sepa") + * @param fees fee structure for this method + */ +static void +check_method_and_fee_cb + (void *cls, + const char *wire_method, + const struct TALER_EXCHANGE_WireAggregateFees *fees); + +/** + * Callbacks called with the result(s) of a wire format inquiry + * request to the exchange. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) + * for successful request; 0 if the exchange's + * reply is bogus (fails to follow the protocol) + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param obj the received JSON reply, if successful this should + * be the wire format details as provided by /wire. + */ +static void +wire_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const json_t *obj) +{ + struct WireState *ws = cls; + struct TALER_TESTING_Command *cmd + = &ws->is->commands[ws->is->ip]; + + /** + * The handle has been free'd by GNUnet curl-lib. FIXME: + * shouldn't GNUnet nullify it once it frees it? + */ + ws->wh = NULL; + if (ws->expected_response_code != http_status) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ws->is); + return; + } + + if (GNUNET_OK != TALER_EXCHANGE_wire_get_fees + (&TALER_EXCHANGE_get_keys (ws->exchange)->master_pub, + obj, + // will check synchronously. + &check_method_and_fee_cb, + ws)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire fee extraction in command %s failed\n", + cmd->label); + json_dumpf (obj, stderr, 0); + TALER_TESTING_interpreter_fail (ws->is); + return; + } + + if (ws->method_found != GNUNET_OK) + { + 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); +} + +/** + * Check all the expected values have been returned by /wire. + * + * @param cls closure + * @param wire_method name of the wire method (i.e. "sepa") + * @param fees fee structure for this method + */ +static void +check_method_and_fee_cb + (void *cls, + const char *wire_method, + const struct TALER_EXCHANGE_WireAggregateFees *fees) +{ + struct WireState *ws = cls; + struct TALER_TESTING_Command *cmd + = &ws->is->commands[ws->is->ip]; // ugly? + struct TALER_Amount expected_fee; + + if (0 == strcmp (ws->expected_method, wire_method)) + ws->method_found = GNUNET_OK; + + if ( ws->expected_fee && (ws->method_found == GNUNET_OK) ) + { + GNUNET_assert (GNUNET_OK == TALER_string_to_amount + (ws->expected_fee, + &expected_fee)); + while (NULL != fees) + { + if (0 != TALER_amount_cmp (&fees->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); + return; + } + fees = fees->next; + } + } +} + +/** + * Run the command. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command to execute, a /wire one. + * @param i the interpreter state. + */ +void +wire_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *i) +{ + struct WireState *ws = cls; + ws->is = i; + ws->wh = TALER_EXCHANGE_wire (ws->exchange, + &wire_cb, + ws); +} + + +/** + * Cleanup the state. + * + * @param cls closure, typically a #struct WireState. + * @param cmd the command which is being cleaned up. + */ +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 to be executed by the interpreter. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_wire (const char *label, + struct TALER_EXCHANGE_Handle *exchange, + const char *expected_method, + const char *expected_fee, + unsigned int expected_response_code) +{ + struct TALER_TESTING_Command cmd; + struct WireState *ws; + + ws = GNUNET_new (struct WireState); + ws->exchange = exchange; + ws->expected_method = expected_method; + ws->expected_fee = expected_fee; + ws->expected_response_code = expected_response_code; + + cmd.cls = ws; + cmd.label = label; + cmd.run = &wire_run; + cmd.cleanup = &wire_cleanup; + + return cmd; +} diff --git a/src/exchange-lib/testing_api_cmd_withdraw.c b/src/exchange-lib/testing_api_cmd_withdraw.c index 642722b45..eb3bc8a6b 100644 --- a/src/exchange-lib/testing_api_cmd_withdraw.c +++ b/src/exchange-lib/testing_api_cmd_withdraw.c @@ -2,16 +2,18 @@ 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 + 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/> */ /** @@ -85,14 +87,17 @@ struct WithdrawState /** - * Function called upon completion of our /reserve/withdraw request. + * Function called upon completion of our /reserve/withdraw + * request. * * @param cls closure with the withdraw 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 http_status HTTP response code, #MHD_HTTP_OK (200) for + * successful status request; 0 if the exchange's reply is + * bogus (fails to follow the protocol) * @param ec taler-specific error code, #TALER_EC_NONE on success * @param sig signature over the coin, NULL on error - * @param full_response full response from the exchange (for logging, in case of errors) + * @param full_response full response from the exchange (for + * logging, in case of errors) */ static void reserve_withdraw_cb (void *cls, @@ -164,9 +169,8 @@ withdraw_run (void *cls, struct TALER_ReservePrivateKeyP *rp; const struct TALER_TESTING_Command *create_reserve; - create_reserve - = TALER_TESTING_interpreter_lookup_command (is, - ws->reserve_reference); + create_reserve = TALER_TESTING_interpreter_lookup_command + (is, ws->reserve_reference); if (NULL == create_reserve) { GNUNET_break (0); @@ -175,7 +179,7 @@ withdraw_run (void *cls, } if (GNUNET_OK != TALER_TESTING_get_trait_reserve_priv (create_reserve, - NULL, + 0, &rp)) { GNUNET_break (0); @@ -245,38 +249,72 @@ static int withdraw_traits (void *cls, void **ret, const char *trait, - const char *selector) + unsigned int index) { struct WithdrawState *ws = cls; + const struct TALER_TESTING_Command *reserve_cmd; + 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; + } + struct TALER_TESTING_Trait traits[] = { - TALER_TESTING_make_trait_coin_priv (NULL /* only one coin */, + TALER_TESTING_make_trait_coin_priv (0 /* only one coin */, &ws->ps.coin_priv), - TALER_TESTING_make_trait_blinding_key (NULL /* only one coin */, + TALER_TESTING_make_trait_blinding_key (0 /* only one coin */, &ws->ps.blinding_key), - TALER_TESTING_make_trait_denom_pub (NULL /* only one coin */, + TALER_TESTING_make_trait_denom_pub (0 /* only one coin */, ws->pk), - TALER_TESTING_make_trait_denom_sig (NULL /* only one coin */, + TALER_TESTING_make_trait_denom_sig (0 /* only one coin */, &ws->sig), + TALER_TESTING_make_trait_reserve_priv (0, + reserve_priv), TALER_TESTING_trait_end () }; return TALER_TESTING_get_trait (traits, ret, trait, - selector); + index); } /** - * Create withdraw command. + * Create a withdraw command. * + * @param label command label, used by other commands to + * reference this. + * @param exchange handle to the exchange. + * @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, - struct TALER_EXCHANGE_Handle *exchange, - const char *reserve_reference, - const char *amount, - unsigned int expected_response_code) +TALER_TESTING_cmd_withdraw_amount + (const char *label, + struct TALER_EXCHANGE_Handle *exchange, + const char *reserve_reference, + const char *amount, + unsigned int expected_response_code) { struct TALER_TESTING_Command cmd; struct WithdrawState *ws; @@ -294,8 +332,9 @@ TALER_TESTING_cmd_withdraw_amount (const char *label, label); GNUNET_assert (0); } - ws->pk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (exchange), - &ws->amount); + ws->pk = TALER_TESTING_find_pk + (TALER_EXCHANGE_get_keys (exchange), + &ws->amount); if (NULL == ws->pk) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -319,11 +358,12 @@ TALER_TESTING_cmd_withdraw_amount (const char *label, * */ struct TALER_TESTING_Command -TALER_TESTING_cmd_withdraw_denomination (const char *label, - struct TALER_EXCHANGE_Handle *exchange, - const char *reserve_reference, - const struct TALER_EXCHANGE_DenomPublicKey *dk, - unsigned int expected_response_code) +TALER_TESTING_cmd_withdraw_denomination + (const char *label, + struct TALER_EXCHANGE_Handle *exchange, + const char *reserve_reference, + const struct TALER_EXCHANGE_DenomPublicKey *dk, + unsigned int expected_response_code) { struct TALER_TESTING_Command cmd; struct WithdrawState *ws; @@ -349,7 +389,4 @@ TALER_TESTING_cmd_withdraw_denomination (const char *label, } - - - /* end of testing_api_cmd_withdraw.c */ diff --git a/src/exchange-lib/testing_api_helpers.c b/src/exchange-lib/testing_api_helpers.c index 1721b3e96..a6e421e16 100644 --- a/src/exchange-lib/testing_api_helpers.c +++ b/src/exchange-lib/testing_api_helpers.c @@ -1,4 +1,3 @@ - /* This file is part of TALER Copyright (C) 2018 Taler Systems SA @@ -31,6 +30,8 @@ /** * Remove files from previous runs + * + * @param config_name configuration filename. */ void TALER_TESTING_cleanup_files (const char *config_name) @@ -65,7 +66,7 @@ TALER_TESTING_cleanup_files (const char *config_name) /** * Prepare launching an exchange. Checks that the configured * port is available, runs taler-exchange-keyup, - * taler-auditor-sign and taler-exchange-dbinit. Does not + * taler-auditor-sign and taler-exchange-dbinit. Does NOT * launch the exchange process itself. * * @param config_filename configuration file to use @@ -79,42 +80,16 @@ TALER_TESTING_prepare_exchange (const char *config_filename) enum GNUNET_OS_ProcessStatusType type; unsigned long code; struct GNUNET_CONFIGURATION_Handle *cfg; - unsigned long long port; - - cfg = GNUNET_CONFIGURATION_create (); - if (GNUNET_OK != - GNUNET_CONFIGURATION_load (cfg, - config_filename)) - return GNUNET_NO; - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (cfg, - "exchange", - "PORT", - &port)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "PORT"); - GNUNET_CONFIGURATION_destroy (cfg); - return GNUNET_NO; - } - GNUNET_CONFIGURATION_destroy (cfg); - if (GNUNET_OK != - GNUNET_NETWORK_test_port_free (IPPROTO_TCP, - (uint16_t) port)) - { - fprintf (stderr, - "Required port %llu not available, skipping.\n", - port); - return GNUNET_NO; - } + char *test_home_dir; + char *signed_keys_out; + char *exchange_master_pub; proc = GNUNET_OS_start_process (GNUNET_NO, GNUNET_OS_INHERIT_STD_ALL, NULL, NULL, NULL, "taler-exchange-keyup", "taler-exchange-keyup", - "-c", "test_exchange_api.conf", + "-c", config_filename, "-o", "auditor.in", NULL); if (NULL == proc) @@ -126,21 +101,58 @@ TALER_TESTING_prepare_exchange (const char *config_filename) GNUNET_OS_process_wait (proc); GNUNET_OS_process_destroy (proc); + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != GNUNET_CONFIGURATION_load + (cfg, config_filename)) + return GNUNET_SYSERR; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "paths", + "TALER_TEST_HOME", + &test_home_dir)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "paths", + "TALER_TEST_HOME"); + GNUNET_CONFIGURATION_destroy (cfg); + return GNUNET_SYSERR; + } + + GNUNET_asprintf (&signed_keys_out, + "%s/.local/share/taler/auditors/auditor.out", + 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); + return GNUNET_SYSERR; + } + + GNUNET_CONFIGURATION_destroy (cfg); + proc = GNUNET_OS_start_process (GNUNET_NO, GNUNET_OS_INHERIT_STD_ALL, NULL, NULL, NULL, "taler-auditor-sign", "taler-auditor-sign", - "-c", "test_exchange_api.conf", + "-c", config_filename, "-u", "http://auditor/", - "-m", "98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG", + "-m", exchange_master_pub, "-r", "auditor.in", - "-o", "test_exchange_api_home/.local/share/taler/auditors/auditor.out", + "-o", signed_keys_out, NULL); if (NULL == proc) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to run `taler-exchange-keyup`, is your PATH correct?\n"); + "Failed to run `taler-auditor-sign`, is your PATH correct?\n"); return GNUNET_NO; } GNUNET_OS_process_wait (proc); @@ -151,7 +163,7 @@ TALER_TESTING_prepare_exchange (const char *config_filename) NULL, NULL, NULL, "taler-exchange-dbinit", "taler-exchange-dbinit", - "-c", "test_exchange_api.conf", + "-c", config_filename, "-r", NULL); if (NULL == proc) @@ -245,22 +257,60 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, * 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 FIXME: depends on what TALER_TESTING_setup returns. */ int TALER_TESTING_setup_with_exchange (TALER_TESTING_Main main_cb, void *main_cb_cls, - const char *config_file) + const char *config_filename) { int result; unsigned int iter; struct GNUNET_OS_Process *exchanged; + struct GNUNET_CONFIGURATION_Handle *cfg; + unsigned long long port; + + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_load (cfg, + config_filename)) + return GNUNET_NO; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "exchange", + "PORT", + &port)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "PORT"); + GNUNET_CONFIGURATION_destroy (cfg); + return GNUNET_NO; + } + + GNUNET_CONFIGURATION_destroy (cfg); + if (GNUNET_OK != + GNUNET_NETWORK_test_port_free (IPPROTO_TCP, + (uint16_t) port)) + { + fprintf (stderr, + "Required port %llu not available, skipping.\n", + port); + return GNUNET_NO; + } + exchanged = GNUNET_OS_start_process (GNUNET_NO, GNUNET_OS_INHERIT_STD_ALL, NULL, NULL, NULL, "taler-exchange-httpd", "taler-exchange-httpd", - "-c", config_file, + "-c", config_filename, "-i", NULL); /* give child time to start and bind against the socket */ @@ -287,7 +337,9 @@ TALER_TESTING_setup_with_exchange (TALER_TESTING_Main main_cb, fprintf (stderr, "\n"); result = TALER_TESTING_setup (main_cb, - main_cb_cls); + main_cb_cls, + config_filename, + exchanged); GNUNET_break (0 == GNUNET_OS_process_kill (exchanged, SIGTERM)); @@ -325,10 +377,32 @@ TALER_TESTING_url_port_free (const char *url) return GNUNET_OK; } +/** + * Allocate and return a piece of wire-details. Mostly, it adds + * the bank_url to the JSON. + * + * @param template the wire-details template. + * @param bank_url the bank_url + * + * @return the filled out and stringified wire-details. To + * be manually free'd. + */ +char * +TALER_TESTING_make_wire_details (const char *template, + const char *bank_url) +{ + json_t *jtemplate; + + GNUNET_assert (NULL != (jtemplate = json_loads + (template, JSON_REJECT_DUPLICATES, NULL))); + GNUNET_assert (0 == json_object_set + (jtemplate, "bank_url", json_string (bank_url))); + return json_dumps (jtemplate, JSON_COMPACT); +} /** * Prepare launching a fakebank. Check that the configuration - * file has the right option, and that the port is avaiable. + * 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 diff --git a/src/exchange-lib/testing_api_loop.c b/src/exchange-lib/testing_api_loop.c index 8c2364984..f22c99d86 100644 --- a/src/exchange-lib/testing_api_loop.c +++ b/src/exchange-lib/testing_api_loop.c @@ -2,18 +2,21 @@ 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 + 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 @@ -28,73 +31,12 @@ #include "taler_testing_lib.h" #include "taler_fakebank_lib.h" - -/** - * Global state of the interpreter, used by a command - * to access information about other commands. - */ -struct TALER_TESTING_Interpreter -{ - - /** - * Commands the interpreter will run. - */ - struct TALER_TESTING_Command *commands; - - /** - * Interpreter task (if one is scheduled). - */ - struct GNUNET_SCHEDULER_Task *task; - - /** - * ID of task called whenever we get a SIGCHILD. - * Used for #TALER_TESTING_wait_for_sigchld(). - */ - struct GNUNET_SCHEDULER_Task *child_death_task; - - /** - * Main execution context for the main loop. - */ - struct GNUNET_CURL_Context *ctx; - - /** - * Context for running the CURL event loop. - */ - struct GNUNET_CURL_RescheduleContext *rc; - - /** - * Handle to our fakebank, if #TALER_TESTING_run_with_fakebank() was used. - * Otherwise NULL. - */ - struct TALER_FAKEBANK_Handle *fakebank; - - /** - * Task run on timeout. - */ - struct GNUNET_SCHEDULER_Task *timeout_task; - - /** - * Instruction pointer. Tells #interpreter_run() which - * instruction to run next. - */ - unsigned int ip; - - /** - * Result of the testcases, #GNUNET_OK on success - */ - int result; - -}; - - /** * 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. * @@ -103,8 +45,9 @@ static struct GNUNET_DISK_PipeHandle *sigpipe; * @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) +TALER_TESTING_interpreter_lookup_command + (struct TALER_TESTING_Interpreter *is, + const char *label) { const struct TALER_TESTING_Command *cmd; @@ -131,23 +74,35 @@ TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is, * Obtain main execution context for the main loop. */ struct GNUNET_CURL_Context * -TALER_TESTING_interpreter_get_context (struct TALER_TESTING_Interpreter *is) +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) +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) +TALER_TESTING_run_with_fakebank + (struct TALER_TESTING_Interpreter *is, + struct TALER_TESTING_Command *commands, + const char *bank_url) { const char *port; long pnum; @@ -159,7 +114,7 @@ TALER_TESTING_run_with_fakebank (struct TALER_TESTING_Interpreter *is, else pnum = strtol (port + 1, NULL, 10); GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Staring Fakebank on port %u (%s)\n", + "Starting Fakebank on port %u (%s)\n", (unsigned int) pnum, bank_url); is->fakebank = TALER_FAKEBANK_start ((uint16_t) pnum); @@ -192,8 +147,7 @@ TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *i) if (GNUNET_SYSERR == i->result) return; /* ignore, we already failed! */ i->ip++; - i->task = GNUNET_SCHEDULER_add_now (&interpreter_run, - i); + i->task = GNUNET_SCHEDULER_add_now (&interpreter_run, i); } @@ -201,21 +155,25 @@ TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *i) * Current command failed, clean up and fail the test case. */ void -TALER_TESTING_interpreter_fail (struct TALER_TESTING_Interpreter *i) +TALER_TESTING_interpreter_fail + (struct TALER_TESTING_Interpreter *is) { - i->result = GNUNET_SYSERR; + // FIXME: disconnect from the exchange. + is->result = GNUNET_SYSERR; + // this cleans up too. 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; - return cmd; } @@ -224,7 +182,8 @@ TALER_TESTING_cmd_end (void) * Obtain current label. */ const char * -TALER_TESTING_interpreter_get_current_label (struct TALER_TESTING_Interpreter *is) +TALER_TESTING_interpreter_get_current_label + (struct TALER_TESTING_Interpreter *is) { struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; @@ -278,6 +237,10 @@ do_shutdown (void *cls) 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); + } if (NULL != is->task) { GNUNET_SCHEDULER_cancel (is->task); @@ -351,7 +314,7 @@ maint_child_death (void *cls) sizeof (c))); if (GNUNET_OK != TALER_TESTING_get_trait_process (cmd, - NULL, + 0, &processp)) { GNUNET_break (0); @@ -361,6 +324,14 @@ maint_child_death (void *cls) GNUNET_OS_process_wait (*processp); GNUNET_OS_process_destroy (*processp); *processp = NULL; + + if (GNUNET_OK == is->reload_keys) + { + GNUNET_break (0 == GNUNET_OS_process_kill + (is->exchanged, SIGUSR1)); + sleep (5); /* make sure signal was received and processed */ + } + TALER_TESTING_interpreter_next (is); } @@ -372,7 +343,8 @@ maint_child_death (void *cls) * with the next command. */ void -TALER_TESTING_wait_for_sigchld (struct TALER_TESTING_Interpreter *is) +TALER_TESTING_wait_for_sigchld + (struct TALER_TESTING_Interpreter *is) { const struct GNUNET_DISK_FileHandle *pr; @@ -384,42 +356,66 @@ TALER_TESTING_wait_for_sigchld (struct TALER_TESTING_Interpreter *is) pr, &maint_child_death, is); - } +/** + * Run the testsuite. FIXME: explain why the commands are + * copied into the state. + * + * @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) { unsigned int i; - + /* 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 (GNUNET_TIME_relative_multiply - (GNUNET_TIME_UNIT_SECONDS, 300), - &do_timeout, - is); - GNUNET_SCHEDULER_add_shutdown (&do_shutdown, - is); - is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, - is); + is->timeout_task = GNUNET_SCHEDULER_add_delayed + (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 300), + &do_timeout, is); + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, is); + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, is); } +/** + * Information used by the wrapper around the main + * "run" method. + */ struct MainContext { + /** + * Main "run" method. + */ TALER_TESTING_Main main_cb; + /** + * Closure for "run". + */ 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; + }; @@ -433,58 +429,170 @@ 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))); + 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. + */ +void +cert_cb (void *cls, + const struct TALER_EXCHANGE_Keys *keys, + enum TALER_EXCHANGE_VersionCompatibility compat) +{ + struct MainContext *main_ctx = cls; + + if (NULL == keys) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Got NULL response for /keys\n"); + + } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got %d DK from /keys\n", + keys->num_denom_keys); + + main_ctx->is->key_generation++; + main_ctx->is->keys = keys; + + /* /keys has been called for some reason and + * the interpreter is already running. */ + if (GNUNET_YES == main_ctx->is->working) + return; + + main_ctx->is->working = GNUNET_YES; + + /* Very first start of tests, call "run()" */ + if (1 == main_ctx->is->key_generation) + { + main_ctx->main_cb (main_ctx->main_cb_cls, + main_ctx->is); + return; + } + + /* Tests already started, just trigger the + * next command. */ + GNUNET_SCHEDULER_add_now (&interpreter_run, + main_ctx->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 (void *cls) { struct MainContext *main_ctx = cls; struct TALER_TESTING_Interpreter *is = main_ctx->is; + struct GNUNET_CONFIGURATION_Handle *cfg; + char *exchange_url; + long long unsigned int exchange_port; - is->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, - &is->rc); + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != GNUNET_CONFIGURATION_load + (cfg, main_ctx->config_filename)) + return; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "exchange", + "PORT", + &exchange_port)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "PORT"); + GNUNET_CONFIGURATION_destroy (cfg); + return; + } + GNUNET_asprintf (&exchange_url, + "http://localhost:%llu/", + exchange_port); + + 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); - main_ctx->main_cb (main_ctx->main_cb_cls, - main_ctx->is); + + GNUNET_assert ( NULL != + (is->exchange = TALER_EXCHANGE_connect (is->ctx, + exchange_url, + cert_cb, + main_ctx)) ); + GNUNET_free (exchange_url); + GNUNET_CONFIGURATION_destroy (cfg); + } /** - * Initialize scheduler loop and curl context for the testcase. + * Install signal handlers plus schedules the main wrapper + * around the "run" method. + * + * @param main_cb the "run" method which coontains 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.. + * + * @return FIXME: not sure what 'is.result' is at this stage. */ int TALER_TESTING_setup (TALER_TESTING_Main main_cb, - void *main_cb_cls) + void *main_cb_cls, + const char *config_filename, + struct GNUNET_OS_Process *exchanged) { struct TALER_TESTING_Interpreter is; struct MainContext main_ctx = { .main_cb = main_cb, .main_cb_cls = main_cb_cls, - .is = &is + /* needed to init the curl ctx */ + .is = &is, + /* needed to read values like exchange port + * number and construct the exchange url. The + * port number _could_ have been passed here, but + * we prefer to stay "general" as other values might + * need to be passed around in the future. */ + .config_filename = config_filename }; struct GNUNET_SIGNAL_Context *shc_chld; - + /* zero-ing the state */ 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); + shc_chld = GNUNET_SIGNAL_handler_install + (GNUNET_SIGCHLD, &sighandler_child_death); GNUNET_SCHEDULER_run (&main_wrapper, &main_ctx); GNUNET_SIGNAL_handler_uninstall (shc_chld); GNUNET_DISK_pipe_close (sigpipe); sigpipe = NULL; + + /*FIXME: ?? */ return is.result; } diff --git a/src/exchange-lib/testing_api_trait_blinding_key.c b/src/exchange-lib/testing_api_trait_blinding_key.c index d23fd93b2..c9415cae9 100644 --- a/src/exchange-lib/testing_api_trait_blinding_key.c +++ b/src/exchange-lib/testing_api_trait_blinding_key.c @@ -40,22 +40,22 @@ */ int TALER_TESTING_get_trait_blinding_key (const struct TALER_TESTING_Command *cmd, - const char *selector, + unsigned int index, struct TALER_DenominationBlindingKeyP **blinding_key) { return cmd->traits (cmd->cls, (void **) blinding_key, TALER_TESTING_TRAIT_BLINDING_KEY, - selector); + index); } struct TALER_TESTING_Trait -TALER_TESTING_make_trait_blinding_key (const char *selector, +TALER_TESTING_make_trait_blinding_key (unsigned int index, const struct TALER_DenominationBlindingKeyP *blinding_key) { struct TALER_TESTING_Trait ret = { - .selector = selector, + .index = index, .trait_name = TALER_TESTING_TRAIT_BLINDING_KEY, .ptr = (const void *) blinding_key }; diff --git a/src/exchange-lib/testing_api_trait_coin_priv.c b/src/exchange-lib/testing_api_trait_coin_priv.c index 6ec4170ea..522f72859 100644 --- a/src/exchange-lib/testing_api_trait_coin_priv.c +++ b/src/exchange-lib/testing_api_trait_coin_priv.c @@ -2,18 +2,21 @@ 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 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. + 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 + 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 main interpreter loop for testcases @@ -34,28 +37,31 @@ * Obtain a coin private key from a @a cmd. * * @param cmd command to extract trait from - * @param selector which coin to pick if @a cmd has multiple on offer + * @param selector which coin to pick if @a cmd has multiple on + * offer * @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, - const char *selector, - struct TALER_CoinSpendPrivateKeyP **coin_priv) +TALER_TESTING_get_trait_coin_priv + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + struct TALER_CoinSpendPrivateKeyP **coin_priv) { return cmd->traits (cmd->cls, (void **) coin_priv, TALER_TESTING_TRAIT_COIN_PRIVATE_KEY, - selector); + index); } struct TALER_TESTING_Trait -TALER_TESTING_make_trait_coin_priv (const char *selector, - const struct TALER_CoinSpendPrivateKeyP *coin_priv) +TALER_TESTING_make_trait_coin_priv + (unsigned int index, + const struct TALER_CoinSpendPrivateKeyP *coin_priv) { struct TALER_TESTING_Trait ret = { - .selector = selector, + .index = index, .trait_name = TALER_TESTING_TRAIT_COIN_PRIVATE_KEY, .ptr = (const void *) coin_priv }; diff --git a/src/exchange-lib/testing_api_trait_denom_pub.c b/src/exchange-lib/testing_api_trait_denom_pub.c index 3113818bb..22d9a346b 100644 --- a/src/exchange-lib/testing_api_trait_denom_pub.c +++ b/src/exchange-lib/testing_api_trait_denom_pub.c @@ -2,16 +2,18 @@ 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 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. + 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 + 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/> */ /** @@ -34,28 +36,39 @@ * Obtain a denomination public key from a @a cmd. * * @param cmd command to extract trait from - * @param selector which coin to pick if @a cmd has multiple on offer + * @param selector which coin to pick if @a cmd has multiple on + * offer * @param denom_pub[out] set to the blinding key of the coin * @return #GNUNET_OK on success */ int -TALER_TESTING_get_trait_denom_pub (const struct TALER_TESTING_Command *cmd, - const char *selector, - struct TALER_EXCHANGE_DenomPublicKey **denom_pub) +TALER_TESTING_get_trait_denom_pub + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + struct TALER_EXCHANGE_DenomPublicKey **denom_pub) { return cmd->traits (cmd->cls, (void **) denom_pub, TALER_TESTING_TRAIT_DENOM_PUB, - selector); + index); } +/** + * Make a trait for a denomination public key. + * + * @param selector in case the trait provides multiple + * objects, this parameter extracts a particular one. + * @param denom_pub pointer to the data to be returned from + * this trait + */ struct TALER_TESTING_Trait -TALER_TESTING_make_trait_denom_pub (const char *selector, - const struct TALER_EXCHANGE_DenomPublicKey *denom_pub) +TALER_TESTING_make_trait_denom_pub + (unsigned int index, + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub) { struct TALER_TESTING_Trait ret = { - .selector = selector, + .index = index, .trait_name = TALER_TESTING_TRAIT_DENOM_PUB, .ptr = (const void *) denom_pub }; @@ -63,5 +76,4 @@ TALER_TESTING_make_trait_denom_pub (const char *selector, return ret; } - /* end of testing_api_trait_denom_pub.c */ diff --git a/src/exchange-lib/testing_api_trait_denom_sig.c b/src/exchange-lib/testing_api_trait_denom_sig.c index 66785c7f4..9578277ce 100644 --- a/src/exchange-lib/testing_api_trait_denom_sig.c +++ b/src/exchange-lib/testing_api_trait_denom_sig.c @@ -40,22 +40,22 @@ */ int TALER_TESTING_get_trait_denom_sig (const struct TALER_TESTING_Command *cmd, - const char *selector, + unsigned int index, struct TALER_DenominationSignature **denom_sig) { return cmd->traits (cmd->cls, (void **) denom_sig, TALER_TESTING_TRAIT_DENOM_SIG, - selector); + index); } struct TALER_TESTING_Trait -TALER_TESTING_make_trait_denom_sig (const char *selector, +TALER_TESTING_make_trait_denom_sig (unsigned int index, const struct TALER_DenominationSignature *denom_sig) { struct TALER_TESTING_Trait ret = { - .selector = selector, + .index = index, .trait_name = TALER_TESTING_TRAIT_DENOM_SIG, .ptr = (const void *) denom_sig }; diff --git a/src/exchange-lib/testing_api_trait_fresh_coin.c b/src/exchange-lib/testing_api_trait_fresh_coin.c new file mode 100644 index 000000000..985591ba3 --- /dev/null +++ b/src/exchange-lib/testing_api_trait_fresh_coin.c @@ -0,0 +1,74 @@ +/* + 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" + +/** + * Obtain a "number" value from @a cmd. + * + * @param cmd command to extract trait from + * @param selector which coin to pick if @a cmd has multiple on + * offer + * @param fresh_coins[out] will point to array of fresh coins + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_get_trait_fresh_coins + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + struct FreshCoin **fresh_coins) +{ + return cmd->traits (cmd->cls, + (void **) fresh_coins, + TALER_TESTING_TRAIT_FRESH_COINS, + index); +} + +/** + * @param selector associate the object with this "tag" + * @param fresh_coins the array of fresh coins to offer + * + * @return the trait, to be put in the traits array of the command + */ +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/exchange-lib/testing_api_trait_key_peer.c b/src/exchange-lib/testing_api_trait_key_peer.c new file mode 100644 index 000000000..d4e207c54 --- /dev/null +++ b/src/exchange-lib/testing_api_trait_key_peer.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_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" + +/** + * NOTE: calling it "peer" key to make clear it is _not a coin_ + * key. + */ + +#define TALER_TESTING_TRAIT_KEY_PEER "key-peer" + +/** + * Obtain a private key from a "peer". Used e.g. to obtain + * a merchant's priv to sign a /track request. + * + * @param index (tipically zero) which key to return if they + * exist in an array. + * @param selector which coin to pick if @a cmd has 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, + (void **) priv, + TALER_TESTING_TRAIT_KEY_PEER, + index); +} + +/** + * @param index (tipically zero) which key to return if they + * exist in an array. + * @param priv which object should be returned + * + * @return the trait, to be put in the traits array of the command + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_peer_key + (unsigned int index, + 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; +} + +/* end of testing_api_trait_key_peer.c */ diff --git a/src/exchange-lib/testing_api_trait_number.c b/src/exchange-lib/testing_api_trait_number.c new file mode 100644 index 000000000..8f011dcae --- /dev/null +++ b/src/exchange-lib/testing_api_trait_number.c @@ -0,0 +1,74 @@ +/* + 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_NUMBER "number" + +/** + * Obtain a "number" value from @a cmd. + * + * @param cmd command to extract trait from + * @param selector which coin to pick if @a cmd has multiple on + * offer + * @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, + unsigned int **n) +{ + return cmd->traits (cmd->cls, + (void **) n, + TALER_TESTING_TRAIT_NUMBER, + index); +} + +/** + * @param selector associate the object with this "tag" + * @param n which object should be returned + * + * @return the trait, to be put in the traits array of the command + */ +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_NUMBER, + .ptr = (const void *) n + }; + return ret; +} + +/* end of testing_api_trait_number.c */ diff --git a/src/exchange-lib/testing_api_trait_process.c b/src/exchange-lib/testing_api_trait_process.c index 877eca973..e3c1bdf4e 100644 --- a/src/exchange-lib/testing_api_trait_process.c +++ b/src/exchange-lib/testing_api_trait_process.c @@ -40,22 +40,22 @@ */ int TALER_TESTING_get_trait_process (const struct TALER_TESTING_Command *cmd, - const char *selector, + unsigned int index, struct GNUNET_OS_Process ***processp) { return cmd->traits (cmd->cls, (void **) processp, TALER_TESTING_TRAIT_PROCESS, - selector); + index); } struct TALER_TESTING_Trait -TALER_TESTING_make_trait_process (const char *selector, +TALER_TESTING_make_trait_process (unsigned int index, struct GNUNET_OS_Process **processp) { struct TALER_TESTING_Trait ret = { - .selector = selector, + .index = index, .trait_name = TALER_TESTING_TRAIT_PROCESS, .ptr = (const void *) processp }; diff --git a/src/exchange-lib/testing_api_trait_reserve_priv.c b/src/exchange-lib/testing_api_trait_reserve_priv.c index a849f7653..fb80a064c 100644 --- a/src/exchange-lib/testing_api_trait_reserve_priv.c +++ b/src/exchange-lib/testing_api_trait_reserve_priv.c @@ -39,22 +39,22 @@ */ int TALER_TESTING_get_trait_reserve_priv (const struct TALER_TESTING_Command *cmd, - const char *selector, + unsigned int index, struct TALER_ReservePrivateKeyP **reserve_priv) { return cmd->traits (cmd->cls, (void **) reserve_priv, TALER_TESTING_TRAIT_RESERVE_PRIVATE_KEY, - selector); + index); } struct TALER_TESTING_Trait -TALER_TESTING_make_trait_reserve_priv (const char *selector, +TALER_TESTING_make_trait_reserve_priv (unsigned int index, const struct TALER_ReservePrivateKeyP *reserve_priv) { struct TALER_TESTING_Trait ret = { - .selector = selector, + .index = index, .trait_name = TALER_TESTING_TRAIT_RESERVE_PRIVATE_KEY, .ptr = (const void *) reserve_priv }; diff --git a/src/exchange-lib/testing_api_trait_string.c b/src/exchange-lib/testing_api_trait_string.c new file mode 100644 index 000000000..308c4ea24 --- /dev/null +++ b/src/exchange-lib/testing_api_trait_string.c @@ -0,0 +1,209 @@ +/* + 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. Mostly used to offer + * stringified JSONs. + * @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_CONTRACT_TERMS "contract-terms" +#define TALER_TESTING_TRAIT_TRANSFER_SUBJECT "transfer-subject" +#define TALER_TESTING_TRAIT_AMOUNT "amount" + +/** + * Obtain contract terms from @a cmd. + * + * @param cmd command to extract trait from + * @param index always (?) zero, as one command sticks + * to one bank account + * @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 char **contract_terms) +{ + return cmd->traits (cmd->cls, + (void **) contract_terms, + TALER_TESTING_TRAIT_CONTRACT_TERMS, + index); +} + +/** + * @param index always (?) zero, as one command sticks + * to one bank account + * @param contract_terms contract terms to offer + * @return the trait, to be put in the traits array of the command + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_contract_terms + (unsigned int index, + const char *contract_terms) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_CONTRACT_TERMS, + .ptr = (const void *) contract_terms + }; + return ret; +} + + +/** + * Obtain wire details from @a cmd. + * + * @param cmd command to extract trait from + * @param index always (?) 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 char **wire_details) +{ + return cmd->traits (cmd->cls, + (void **) wire_details, + TALER_TESTING_TRAIT_WIRE_DETAILS, + index); +} + +/** + * Offer wire details in a trait. + * + * @param index always (?) zero, as one command sticks + * to one bank account + * @param wire_details wire details to offer + * @return the trait, to be put in the traits array of the command + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_wire_details + (unsigned int index, + const char *wire_details) +{ + struct TALER_TESTING_Trait ret = { + .index = index, + .trait_name = TALER_TESTING_TRAIT_WIRE_DETAILS, + .ptr = (const void *) wire_details + }; + return ret; +} + + +/** + * Obtain a transfer subject from @a cmd. + * + * @param cmd command to extract trait from + * @param index always (?) zero, as one command sticks + * to one bank transfer + * @param transfer_subject[out] where to write the wire details. + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_get_trait_transfer_subject + (const struct TALER_TESTING_Command *cmd, + unsigned int index, + char **transfer_subject) +{ + return cmd->traits (cmd->cls, + (void **) transfer_subject, + TALER_TESTING_TRAIT_TRANSFER_SUBJECT, + index); +} + +/** + * Offer wire details in a trait. + * + * @param index always (?) zero, as one command sticks + * to one bank account + * @param wire_details wire details to offer + * @return the trait, to be put in the traits array of the command + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_transfer_subject + (unsigned int index, + 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 trait 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, + (void **) amount, + TALER_TESTING_TRAIT_AMOUNT, + index); +} + +/** + * Offer amount in a trait. + * + * @param index which amount is to be picked, in case + * multiple are offered. + * @param amount the amount to offer + * @return the trait, to be put in the traits array of the command + */ +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; +} + + +/* end of testing_api_trait_string.c */ diff --git a/src/exchange-lib/testing_api_trait_wtid.c b/src/exchange-lib/testing_api_trait_wtid.c new file mode 100644 index 000000000..140a2ac3e --- /dev/null +++ b/src/exchange-lib/testing_api_trait_wtid.c @@ -0,0 +1,74 @@ +/* + 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, + struct TALER_WireTransferIdentifierRawP **wtid) +{ + return cmd->traits (cmd->cls, + (void **) wtid, + TALER_TESTING_TRAIT_WTID, + index); +} + +/** + * @param index associate the object with this index + * @param wtid which object should be returned + * + * @return the trait, to be put in the traits array of the command + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_wtid + (unsigned int index, + 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/exchange-lib/testing_api_traits.c b/src/exchange-lib/testing_api_traits.c index 3983d3209..c83c7e778 100644 --- a/src/exchange-lib/testing_api_traits.c +++ b/src/exchange-lib/testing_api_traits.c @@ -2,16 +2,18 @@ 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 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. + 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 + 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/> */ /** @@ -28,11 +30,15 @@ #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 = { - .selector = NULL, + .index = 0, .trait_name = NULL, .ptr = NULL }; @@ -40,31 +46,35 @@ TALER_TESTING_trait_end () return end; } - +/** + * Pick the chosen trait from the traits array. + * + * @param traits the traits array + * @param ret where to store the result + * @param selector which particular object in the trait should be + * returned + * + * @return GNUNET_OK if no error occurred, GNUNET_SYSERR otherwise + */ int TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits, void **ret, const char *trait, - const char *selector) + unsigned int index) { - for (unsigned int i=0; - NULL != traits[i].trait_name; - i++) + for (unsigned int i=0; NULL != traits[i].trait_name; i++) { - if ( (0 == strcmp (trait, - traits[i].trait_name)) && - ( (NULL == selector) || - (0 == strcasecmp (selector, - traits[i].selector) ) ) ) + if ( (0 == strcmp (trait, traits[i].trait_name)) && + (index == traits[i].index) ) { *ret = (void *) traits[i].ptr; return GNUNET_OK; } } - /* FIXME: log */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Trait %s/%u not found.\n", + trait, index); return GNUNET_SYSERR; } - - /* end of testing_api_traits.c */ |