diff options
Diffstat (limited to 'src/merchant-tools')
-rw-r--r-- | src/merchant-tools/Makefile.am | 23 | ||||
-rw-r--r-- | src/merchant-tools/README | 51 | ||||
-rw-r--r-- | src/merchant-tools/mitm/Makefile.in | 21 | ||||
-rw-r--r-- | src/merchant-tools/mitm/README | 25 | ||||
-rw-r--r-- | src/merchant-tools/mitm/merchant-mitm.wsgi.in | 21 | ||||
-rw-r--r-- | src/merchant-tools/mitm/setup.py | 11 | ||||
-rw-r--r-- | src/merchant-tools/mitm/taler-merchant-mitm.in | 45 | ||||
-rw-r--r-- | src/merchant-tools/mitm/talermerchantmitm/__init__.py | 0 | ||||
-rw-r--r-- | src/merchant-tools/mitm/talermerchantmitm/mitm.py | 78 | ||||
-rw-r--r-- | src/merchant-tools/taler-merchant-generate-payments.c | 1743 |
10 files changed, 2015 insertions, 3 deletions
diff --git a/src/merchant-tools/Makefile.am b/src/merchant-tools/Makefile.am index 97f448d9..2adee917 100644 --- a/src/merchant-tools/Makefile.am +++ b/src/merchant-tools/Makefile.am @@ -1,10 +1,9 @@ - - # This Makefile.am is in the public domain AM_CPPFLAGS = -I$(top_srcdir)/src/include bin_PROGRAMS = \ - taler-merchant-dbinit + taler-merchant-dbinit \ + taler-merchant-generate-payments taler_merchant_dbinit_SOURCES = \ taler-merchant-dbinit.c @@ -15,3 +14,21 @@ taler_merchant_dbinit_LDADD = \ -lgnunetutil \ -ltalerutil \ -ltalerpq + +taler_merchant_generate_payments_SOURCES = \ + taler-merchant-generate-payments.c + +taler_merchant_generate_payments_LDADD = \ + $(top_srcdir)/src/backenddb/libtalermerchantdb.la \ + $(top_srcdir)/src/lib/libtalermerchant.la \ + $(LIBGCRYPT_LIBS) \ + -ltalerfakebank \ + -ltalerexchange \ + -ltalerjson \ + -ltalerutil \ + -lgnunetjson \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + +SUBDIRS = mitm diff --git a/src/merchant-tools/README b/src/merchant-tools/README new file mode 100644 index 00000000..993a87ed --- /dev/null +++ b/src/merchant-tools/README @@ -0,0 +1,51 @@ + +** Taler Merchant Payments Generator ** + +=== INTRODUCTION === + +The tool contained in this directory is used to populate the +merchant's and exchange's database with fake payments. +It is mainly used for debugging applications that need some +payments to be in place. + +It is mandatory to give it a config file that contains information +about the setup to target. Note that the merchant and the exchange +tun by this command will use their own config files, so just few values +are required for it to run. + + +=== COFIGURATION === + +Any config file must look like the following one. + +[payments-generator] + +# where on this machine the exchange listens; note that +# this exchange must be among the ones accepted by the +# merchant +exchange = http://localexchange/ + +# where on this machine the merchant listens +merchant = http://localshop/ + +# bank's URI of the customer who withdraws coins. +# Must be known by the exchange. +bank = http://localbank/ + +# must match an instance known by the merchant +instance = FSF + +# must match the currency used by merchant and exchange +currency = EUR + + +=== INVOCATION === + +taler-merchant-generate-payments -c config/file.conf [-n ITERATIONS] + +The -n option instructs the tools about how many iteration of all +the (internal) commands we want to execute. + +** Taler Merchant Dbinit ** + +TBD diff --git a/src/merchant-tools/mitm/Makefile.in b/src/merchant-tools/mitm/Makefile.in new file mode 100644 index 00000000..4a9f33a2 --- /dev/null +++ b/src/merchant-tools/mitm/Makefile.in @@ -0,0 +1,21 @@ + +.PHONY: all +all: + true + +.PHONY: install-data +install-data: + install -m 444 -Dt @prefix@/share/taler/ merchant-mitm.wsgi + +.PHONY: install +install: install-data + pip3 install . --install-option="--prefix=@prefix@" --upgrade --no-deps + install -m 544 -Dt @prefix@/bin taler-merchant-mitm + +# need a way to make 'make check' happy in this subdir. +# This component is still too young to be tested. +.PHONY: check +check: + true +clean: + true diff --git a/src/merchant-tools/mitm/README b/src/merchant-tools/mitm/README new file mode 100644 index 00000000..662cbe39 --- /dev/null +++ b/src/merchant-tools/mitm/README @@ -0,0 +1,25 @@ + +=== INTRODUCTION === + +This directory contain a Web server that listens for +requests addressed to the exchange, relays them to the +exchange, and returns a modified response to the caller. + +The modifications are made to test error management in the +merchant, and are driven by a HTTP header that instructs the +proxy about the modifications to be made. + +=== INVOCATION === + +After a successful 'make install', a command called 'taler-merchant-mitm' +is placed under <prefix>/bin - make sure PATH points at it. + +To run the mitm, give the following commands: + +$ taler-merchant-mitm --exchange URL [--port PORT] + +The '--exchange' option is mandatory, telling the mitm where to +forward the requests addressed to the exchange. + +The second option just sets the port number where the mitm +listens, defaulting to 5000. diff --git a/src/merchant-tools/mitm/merchant-mitm.wsgi.in b/src/merchant-tools/mitm/merchant-mitm.wsgi.in new file mode 100644 index 00000000..3fb4cfbf --- /dev/null +++ b/src/merchant-tools/mitm/merchant-mitm.wsgi.in @@ -0,0 +1,21 @@ + +import sys + +if sys.version_info.major < 3: + print("The merchant mitm needs to run with Python>=3.4") + sys.exit(1) + +import os +import site +import logging + +logging.basicConfig(level=logging.INFO) + +site.addsitedir("%s/lib/python%d.%d/site-packages" % ( + "@prefix@", + sys.version_info.major, + sys.version_info.minor)) + +import talermerchantmitm.mitm + +application = talermerchantmitm.mitm.app diff --git a/src/merchant-tools/mitm/setup.py b/src/merchant-tools/mitm/setup.py new file mode 100644 index 00000000..f5eedeaa --- /dev/null +++ b/src/merchant-tools/mitm/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup, find_packages +setup(name='talermerchantmitm', + version='0.0', + description='Layer generating errors for testing', + url='git://taler.net/merchant', + author='Marcello Stanisci', + author_email='marcello.stanisci@inria.fr', + license='GPL', + packages=find_packages(), + install_requires=["Flask>=0.10"], + zip_safe=False) diff --git a/src/merchant-tools/mitm/taler-merchant-mitm.in b/src/merchant-tools/mitm/taler-merchant-mitm.in new file mode 100644 index 00000000..39a7275e --- /dev/null +++ b/src/merchant-tools/mitm/taler-merchant-mitm.in @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +""" +Stand-alone script to manage the merchant's MITM +error generator. +""" + +import argparse +import sys +import os + +parser = argparse.ArgumentParser() + +parser.add_argument('--exchange', + '-e', + help="Exchange URL", + metavar="URL", + type=str, + dest="exchange_url", + default=None) + + +parser.add_argument("--port", + "-p", + help="Port where the MITM listens", + dest="port", + type=int, + default=5000, + metavar="PORT") + +args = parser.parse_args() + +if getattr(args, 'exchange_url', None) is None: + parser.print_help() + sys.exit(1) + +uwsgi_logfmt = "%(ltime) %(proto) %(method) %(uri) %(proto) => %(status)" + +os.environ["TALER_EXCHANGE_URL"] = args.exchange_url +os.execlp("uwsgi", "uwsgi", + "--master", + "--die-on-term", + "--log-format", uwsgi_logfmt, + "--http", ":%d" % args.port, + "--wsgi-file", "@prefix@/share/taler/merchant-mitm.wsgi") diff --git a/src/merchant-tools/mitm/talermerchantmitm/__init__.py b/src/merchant-tools/mitm/talermerchantmitm/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/merchant-tools/mitm/talermerchantmitm/__init__.py diff --git a/src/merchant-tools/mitm/talermerchantmitm/mitm.py b/src/merchant-tools/mitm/talermerchantmitm/mitm.py new file mode 100644 index 00000000..c998a1c5 --- /dev/null +++ b/src/merchant-tools/mitm/talermerchantmitm/mitm.py @@ -0,0 +1,78 @@ +#This file is part of TALER +#Copyright (C) 2014, 2015, 2016, 2017 GNUnet e.V. and INRIA +# +#TALER is free software; you can redistribute it and/or modify it under the +#terms of the GNU Lesser General Public License as published by the Free Software +#Foundation; either version 2.1, 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 Lesser General Public License for more details. +# +#You should have received a copy of the GNU Lesser General Public License along with +#TALER; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/> + +# @author Marcello Stanisci +# @brief Error generator for responses coming from the exchange + +from flask import (request, + Flask, + make_response) +import requests +from urllib.parse import (urljoin, + urlencode, + urlparse, + urlunparse) +from pytaler import amount +import base64 +import os +import logging +import json +from random import randint +from datetime import datetime + +app = Flask(__name__) +app.secret_key = base64.b64encode(os.urandom(64)).decode('utf-8') +logger = logging.getLogger(__name__) +exchange_url = os.environ.get("TALER_EXCHANGE_URL") +assert(None != exchange_url) + +def track_transaction(resp): + return resp.text + +def track_transfer(resp): + return resp.text + +def keys(resp): + try: + keys = resp.json() + # Put here data perturbation logic + return json.dumps(keys) + except Exception: + return resp.text + +@app.route('/', defaults={'path': ''}) +@app.route('/<path:path>', methods=["GET", "POST"]) +def all(path): + body = request.get_json() + url = list(urlparse(request.url)) + xurl = urlparse(exchange_url) + url[0] = xurl[0] + url[1] = xurl[1] + url = urlunparse(url) + if "POST" == request.method: + r = requests.post(urljoin(url, path), json=body) + else: + r = requests.get(urljoin(url, path), json=body) + dispatcher = { + "track_transaction": track_transaction, + "track_transfer": track_transfer, + "keys": keys + } + func = dispatcher.get(request.headers.get("X-Taler-Mitm"), + lambda x: x.text) + response = make_response(func(r)) + for key, value in r.headers.items(): + if key not in ("Server", "Content-Length"): + response.headers[key] = value + return response, r.status_code diff --git a/src/merchant-tools/taler-merchant-generate-payments.c b/src/merchant-tools/taler-merchant-generate-payments.c new file mode 100644 index 00000000..625ee339 --- /dev/null +++ b/src/merchant-tools/taler-merchant-generate-payments.c @@ -0,0 +1,1743 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. and INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/> +*/ + +#include "platform.h" +#include <taler/taler_exchange_service.h> +#include <taler/taler_fakebank_lib.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_util.h> +#include <taler/taler_signatures.h> +#include "taler_merchant_service.h" +#include "taler_merchantdb_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> + +/** + * The exchange process launched by the generator + */ +static struct GNUNET_OS_Process *exchanged; + +/** + * The merchant process launched by the generator + */ +static struct GNUNET_OS_Process *merchantd; + +/** + * How many times the command list should be rerun. + */ +static unsigned int times = 1; + +/** + * Current iteration of commands + */ +static unsigned int j = 0; + +/** + * Indicates whether we use an external exchange. + * By default, the generator forks a local exchange. + */ +static int remote_exchange = 0; + +/** + * Indicates whether we use an external merchant. + * By default, the generator tries to fork a local + * merchant. + */ +static int remote_merchant = 0; + +/** + * Exchange URI to withdraw from and deposit to. + */ +static char *exchange_uri; + +/** + * Base URL of exchange's admin interface. + */ +static char *exchange_uri_admin; + +/** + * Merchant backend to get proposals from and pay. + */ +static char *merchant_uri; + +/** + * Customer's bank URI, communicated at withdrawal time + * to the exchange; must be the same as the exchange's bank. + */ +static char *bank_uri; + +/** + * Which merchant instance we use. + */ +static char *instance; + +/** + * Currency used to generate payments. + */ +static char *currency; + +/** + * Task run on timeout. + */ +static struct GNUNET_SCHEDULER_Task *timeout_task; + +/** + * Handle to access the exchange. + */ +static struct TALER_EXCHANGE_Handle *exchange; + +/** + * Main execution context for the main loop of the exchange. + */ +static struct GNUNET_CURL_Context *ctx; + +/** + * Context for running the #ctx's event loop. + */ +static struct GNUNET_CURL_RescheduleContext *rc; + +/** + * Result of the testcases, #GNUNET_OK on success. + */ +static int result; + +/** + * Pipe used to communicate child death via signal. + */ +static struct GNUNET_DISK_PipeHandle *sigpipe; + +/** + * State of the interpreter loop. + */ +struct InterpreterState +{ + /** + * Keys from the exchange. + */ + const struct TALER_EXCHANGE_Keys *keys; + + /** + * Commands the interpreter will run. + */ + struct Command *commands; + + /** + * Interpreter task (if one is scheduled). + */ + struct GNUNET_SCHEDULER_Task *task; + + /** + * Instruction pointer. Tells #interpreter_run() which + * instruction to run next. + */ + unsigned int ip; + +}; + +/** + * Opcodes for the interpreter. + */ +enum OpCode +{ + /** + * Termination code, stops the interpreter loop (with success). + */ + OC_END = 0, + + /** + * Issue a GET /proposal to the backend. + */ + OC_PROPOSAL_LOOKUP, + + /** + * Add funds to a reserve by (faking) incoming wire transfer. + */ + OC_ADMIN_ADD_INCOMING, + + /** + * Check status of a reserve. + */ + OC_WITHDRAW_STATUS, + + /** + * Withdraw a coin from a reserve. + */ + OC_WITHDRAW_SIGN, + + /** + * Issue a PUT /proposal to the backend. + */ + OC_PROPOSAL, + + /** + * Pay with coins. + */ + OC_PAY + +}; + + +/** + * Details for a exchange operation to execute. + */ +struct Command +{ + /** + * Opcode of the command. + */ + enum OpCode oc; + + /** + * Label for the command, can be NULL. + */ + const char *label; + + /** + * Which response code do we expect for this command? + */ + unsigned int expected_response_code; + + /** + * Details about the command. + */ + union + { + + /** + * Information for a #OC_WITHDRAW_SIGN command. + */ + struct + { + + /** + * Which reserve should we withdraw from? + */ + const char *reserve_reference; + + /** + * String describing the denomination value we should withdraw. + * A corresponding denomination key must exist in the exchange's + * offerings. Can be NULL if @e pk is set instead. + * The interpreter must free this value after it doesn't need it + * anymore. + */ + char *amount; + + /** + * If @e amount is NULL, this specifies the denomination key to + * use. Otherwise, this will be set (by the interpreter) to the + * denomination PK matching @e amount. + */ + const struct TALER_EXCHANGE_DenomPublicKey *pk; + + /** + * Set (by the interpreter) to the exchange's signature over the + * coin's public key. + */ + struct TALER_DenominationSignature sig; + + /** + * Set (by the interpreter) to the coin's private key. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Blinding key used for the operation. + */ + struct TALER_DenominationBlindingKeyP blinding_key; + + /** + * Withdraw handle (while operation is running). + */ + struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; + + } reserve_withdraw; + + /** + * Information for a #OC_ADMIN_ADD_INCOMING command. + */ + struct + { + + /** + * Label to another admin_add_incoming command if we + * should deposit into an existing reserve, NULL if + * a fresh reserve should be created. + */ + const char *reserve_reference; + + /** + * String describing the amount to add to the reserve. + */ + const char *amount; + + /** + * Sender's bank account details (JSON). + */ + const char *sender_details; + + /** + * Transfer details (JSON) + */ + const char *transfer_details; + + /** + * Set (by the interpreter) to the reserve's private key + * we used to fill the reserve. + */ + struct TALER_ReservePrivateKeyP reserve_priv; + + /** + * Set to the API's handle during the operation. + */ + struct TALER_EXCHANGE_AdminAddIncomingHandle *aih; + + } admin_add_incoming; + + /** + * Information for an #OC_PROPOSAL command. + */ + struct + { + + /** + * Max deposit fee accepted by the merchant. + * Given in the form "CURRENCY:X.Y". + */ + char *max_fee; + + /** + * Proposal overall price. + * Given in the form "CURRENCY:X.Y". + */ + char *amount; + + /** + * Handle to the active PUT /proposal operation, or NULL. + */ + struct TALER_MERCHANT_ProposalOperation *po; + + /** + * Full contract in JSON, set by the /contract operation. + * FIXME: verify in the code that this bit is actually proposal + * data and not the whole proposal. + */ + json_t *proposal_data; + + /** + * Proposal's signature. + */ + struct TALER_MerchantSignatureP merchant_sig; + + /** + * Proposal data's hashcode. + */ + struct GNUNET_HashCode hash; + + } proposal; + + /** + * Information for a #OC_PAY command. + * FIXME: support tests where we pay with multiple coins at once. + */ + struct + { + + /** + * Reference to the contract. + */ + const char *contract_ref; + + /** + * Reference to a reserve_withdraw operation for a coin to + * be used for the /deposit operation. + */ + const char *coin_ref; + + /** + * If this @e coin_ref refers to an operation that generated + * an array of coins, this value determines which coin to use. + */ + unsigned int coin_idx; + + /** + * Amount to pay (from the coin, including fee). + */ + const char *amount_with_fee; + + /** + * Amount to pay (from the coin, excluding fee). The sum of the + * deltas between all @e amount_with_fee and the @e + * amount_without_fee must be less than max_fee, and the sum of + * the @e amount_with_fee must be larger than the @e + * total_amount. + */ + const char *amount_without_fee; + + /** + * Deposit handle while operation is running. + */ + struct TALER_MERCHANT_Pay *ph; + + /** + * Hashcode of the proposal data associated to this payment. + */ + struct GNUNET_HashCode h_proposal_data; + + /** + * Merchant's public key + */ + struct TALER_MerchantPublicKeyP merchant_pub; + + } pay; + + + + } details; + +}; + +/** + * Function run when the test times out. + * + * @param cls NULL + */ +static void +do_timeout (void *cls) +{ + timeout_task = NULL; + GNUNET_SCHEDULER_shutdown (); +} + +/** + * The generator failed, return with an error code. + * + * @param is interpreter state to clean up + */ +static void +fail (struct InterpreterState *is) +{ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Interpreter failed at step %s (#%u)\n", + is->commands[is->ip].label, + is->ip); + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); +} + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct InterpreterState` + */ +static void +interpreter_run (void *cls); + +/** + * Run the next command with the interpreter. + * + * @param is current interpeter state. + */ +static void +next_command (struct InterpreterState *is) +{ + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + +/** + * Callback that works PUT /proposal's output. + * + * @param cls closure + * @param http_status HTTP response code, 200 indicates success; + * 0 if the backend's reply is bogus (fails to follow the protocol) + * @param ec taler-specific error code + * @param obj the full received JSON reply, or + * error details if the request failed + * @param proposal_data the order + additional information provided by the + * backend, NULL on error. + * @param sig merchant's signature over the contract, NULL on error + * @param h_contract hash of the contract, NULL on error + */ +static void +proposal_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const json_t *obj, + const json_t *proposal_data, + const struct TALER_MerchantSignatureP *sig, + const struct GNUNET_HashCode *hash) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + + cmd->details.proposal.po = NULL; + switch (http_status) + { + case MHD_HTTP_OK: + cmd->details.proposal.proposal_data = json_incref ((json_t *) proposal_data); + cmd->details.proposal.merchant_sig = *sig; + cmd->details.proposal.hash = *hash; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Hashed proposal, '%s'\n", + GNUNET_h2s (hash)); + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "unexpected status code from /proposal: %u. Step %u\n", + http_status, + is->ip); + json_dumpf (obj, stderr, 0); + GNUNET_break (0); + fail (is); + return; + } + next_command (is); +} + +/** + * Function called with the result of a /pay 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 + * @param obj the received JSON reply, should be kept as proof (and, in case of errors, + * be forwarded to the customer) + */ +static void +pay_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const json_t *obj) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + struct PaymentResponsePS mr; + struct GNUNET_CRYPTO_EddsaSignature sig; + struct GNUNET_HashCode h_proposal_data; + const char *error_name; + unsigned int error_line; + + cmd->details.pay.ph = NULL; + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + json_dumpf (obj, stderr, 0); + fail (is); + return; + } + if (MHD_HTTP_OK == http_status) + { + /* Check signature */ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("sig", &sig), + GNUNET_JSON_spec_fixed_auto ("h_proposal_data", &h_proposal_data), + GNUNET_JSON_spec_end () + }; + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + &error_name, + &error_line)) + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Parser failed on %s:%u\n", + error_name, + error_line); + fail (is); + return; + } + mr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK); + mr.purpose.size = htonl (sizeof (mr)); + mr.h_proposal_data = h_proposal_data; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_PAYMENT_OK, + &mr.purpose, + &sig, + &cmd->details.pay.merchant_pub.eddsa_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Merchant signature given in response to /pay invalid\n"); + fail (is); + return; + } + + } + + next_command (is); +} + +/** + * 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 ec taler-specific error code, #TALER_EC_NONE on success + * @param full_response full response from the exchange (for logging, in case of errors) + */ +static void +add_incoming_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const json_t *full_response) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + + cmd->details.admin_add_incoming.aih = NULL; + if (MHD_HTTP_OK != http_status) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%s", + json_dumps (full_response, JSON_INDENT (2))); + fail (is); + return; + } + next_command (is); +} + +/** + * Find a command by label. + * + * @param is interpreter state to search + * @param label label to look for + * @return NULL if command was not found + */ +static const struct Command * +find_command (const struct InterpreterState *is, + const char *label) +{ + unsigned int i; + const struct Command *cmd; + + if (NULL == label) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Attempt to lookup command for empty label\n"); + return NULL; + } + for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++) + if ( (NULL != cmd->label) && + (0 == strcmp (cmd->label, + label)) ) + return cmd; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command not found: %s\n", + label); + return NULL; +} + +/** + * Function called upon completion of our /reserve/withdraw request. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + * 0 if the exchange's reply is bogus (fails to follow the protocol) + * @param ec taler-specific error code + * @param sig signature over the coin, NULL on error + * @param full_response full response from the exchange (for logging, in case of errors) + */ +static void +reserve_withdraw_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_DenominationSignature *sig, + const json_t *full_response) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + + cmd->details.reserve_withdraw.wsh = NULL; + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + json_dumpf (full_response, stderr, 0); + GNUNET_break (0); + fail (is); + return; + } + switch (http_status) + { + case MHD_HTTP_OK: + if (NULL == sig) + { + GNUNET_break (0); + fail (is); + return; + } + cmd->details.reserve_withdraw.sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature); + break; + case MHD_HTTP_PAYMENT_REQUIRED: + /* nothing to check */ + break; + default: + /* Unsupported status code (by test harness) */ + GNUNET_break (0); + break; + } + next_command (is); +} + +/** + * Find denomination key matching the given amount. + * + * @param keys array of keys to search + * @param amount coin value to look for + * @return NULL if no matching key was found + */ +static const struct TALER_EXCHANGE_DenomPublicKey * +find_pk (const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_Amount *amount) +{ + unsigned int i; + struct GNUNET_TIME_Absolute now; + struct TALER_EXCHANGE_DenomPublicKey *pk; + char *str; + + now = GNUNET_TIME_absolute_get (); + for (i=0;i<keys->num_denom_keys;i++) + { + pk = &keys->denom_keys[i]; + if ( (0 == TALER_amount_cmp (amount, + &pk->value)) && + (now.abs_value_us >= pk->valid_from.abs_value_us) && + (now.abs_value_us < pk->withdraw_valid_until.abs_value_us) ) + return pk; + } + /* do 2nd pass to check if expiration times are to blame for failure */ + str = TALER_amount_to_string (amount); + for (i=0;i<keys->num_denom_keys;i++) + { + pk = &keys->denom_keys[i]; + if ( (0 == TALER_amount_cmp (amount, + &pk->value)) && + ( (now.abs_value_us < pk->valid_from.abs_value_us) || + (now.abs_value_us > pk->withdraw_valid_until.abs_value_us) ) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Have denomination key for `%s', but with wrong expiration range %llu vs [%llu,%llu)\n", + str, + (unsigned long long) now.abs_value_us, + (unsigned long long) pk->valid_from.abs_value_us, + (unsigned long long) pk->withdraw_valid_until.abs_value_us); + GNUNET_free (str); + return NULL; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No denomination key for amount %s found\n", + str); + GNUNET_free (str); + return NULL; +} + +/** + * Allocates and return a string representing a order. + * In this process, this function gives the order those + * prices specified by the user. Please NOTE that any amount + * must be given in the form "XX.YY". + * + * @param max_fee merchant's allowed max_fee + * @param amount total amount for this order + */ +json_t * +make_order (char *maxfee, + char *total) +{ + struct TALER_Amount tmp_amount; + json_t *total_j; + json_t *maxfee_j; + json_t *ret; + unsigned long long id; + + TALER_string_to_amount (maxfee, &tmp_amount); + maxfee_j = TALER_JSON_from_amount (&tmp_amount); + TALER_string_to_amount (total, &tmp_amount); + total_j = TALER_JSON_from_amount (&tmp_amount); + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &id, + sizeof (id)); + ret = json_pack ("{s:O, s:s, s:s, s:s, s:s, s:O, s:s, s:[{s:s}]}", + "max_fee", maxfee_j, + "order_id", TALER_b2s (&id, sizeof (id)), + "timestamp", "/Date(42)/", + "refund_deadline", "/Date(0)/", + "pay_deadline", "/Date(9999999999)/", + "amount", total_j, + "summary", "payments generator..", + "products", "description", "ice cream"); + + GNUNET_assert (NULL != ret); + return ret; +} + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct InterpreterState` + */ +static void +interpreter_run (void *cls) +{ + const struct GNUNET_SCHEDULER_TaskContext *tc; + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + const struct Command *ref; + struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_Amount amount; + struct GNUNET_TIME_Absolute execution_date; + json_t *sender_details; + json_t *transfer_details; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Interpreter runs command %u/%s(%u)\n", + is->ip, + cmd->label, + cmd->oc); + + is->task = NULL; + tc = GNUNET_SCHEDULER_get_task_context (); + if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) + { + fprintf (stderr, + "Test aborted by shutdown request\n"); + fail (is); + return; + } + + switch (cmd->oc) + { + case OC_END: + + j++; + + if (j < times) + { + is->ip = 0; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Rewinding the interpreter.\n"); + + GNUNET_SCHEDULER_add_now (&interpreter_run, + is); + return; + } + result = GNUNET_OK; + GNUNET_SCHEDULER_shutdown (); + + return; + + case OC_PAY: + { + struct TALER_MERCHANT_PayCoin pc; + const char *order_id; + struct GNUNET_TIME_Absolute refund_deadline; + struct GNUNET_TIME_Absolute pay_deadline; + struct GNUNET_TIME_Absolute timestamp; + struct GNUNET_HashCode h_wire; + struct TALER_MerchantPublicKeyP merchant_pub; + struct TALER_MerchantSignatureP merchant_sig; + struct TALER_Amount total_amount; + struct TALER_Amount max_fee; + const char *error_name; + unsigned int error_line; + + /* get proposal */ + ref = find_command (is, + cmd->details.pay.contract_ref); + GNUNET_assert (NULL != ref); + merchant_sig = ref->details.proposal.merchant_sig; + GNUNET_assert (NULL != ref->details.proposal.proposal_data); + { + /* Get information that need to be replied in the deposit permission */ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("order_id", &order_id), + GNUNET_JSON_spec_absolute_time ("refund_deadline", &refund_deadline), + GNUNET_JSON_spec_absolute_time ("pay_deadline", &pay_deadline), + GNUNET_JSON_spec_absolute_time ("timestamp", ×tamp), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), + GNUNET_JSON_spec_fixed_auto ("H_wire", &h_wire), + TALER_JSON_spec_amount ("amount", &total_amount), + TALER_JSON_spec_amount ("max_fee", &max_fee), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (ref->details.proposal.proposal_data, + spec, + &error_name, + &error_line)) + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Parser failed on %s:%u\n", + error_name, + error_line); + fail (is); + return; + } + cmd->details.pay.merchant_pub = merchant_pub; + } + + { + const struct Command *coin_ref; + memset (&pc, 0, sizeof (pc)); + coin_ref = find_command (is, + cmd->details.pay.coin_ref); + GNUNET_assert (NULL != ref); + switch (coin_ref->oc) + { + case OC_WITHDRAW_SIGN: + pc.coin_priv = coin_ref->details.reserve_withdraw.coin_priv; + pc.denom_pub = coin_ref->details.reserve_withdraw.pk->key; + pc.denom_sig = coin_ref->details.reserve_withdraw.sig; + pc.denom_value = coin_ref->details.reserve_withdraw.pk->value; + break; + default: + GNUNET_assert (0); + } + + if (GNUNET_OK != + TALER_string_to_amount (cmd->details.pay.amount_without_fee, + &pc.amount_without_fee)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u\n", + cmd->details.pay.amount_without_fee, + is->ip); + fail (is); + return; + } + + if (GNUNET_OK != + TALER_string_to_amount (cmd->details.pay.amount_with_fee, + &pc.amount_with_fee)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u\n", + cmd->details.pay.amount_with_fee, + is->ip); + fail (is); + return; + } + } + + cmd->details.pay.ph + = TALER_MERCHANT_pay_wallet (ctx, + merchant_uri, + instance, + &ref->details.proposal.hash, + &total_amount, + &max_fee, + &merchant_pub, + &merchant_sig, + timestamp, + refund_deadline, + pay_deadline, + &h_wire, + exchange_uri, + order_id, + 1 /* num_coins */, + &pc /* coins */, + &pay_cb, + is); + } + if (NULL == cmd->details.pay.ph) + { + GNUNET_break (0); + fail (is); + return; + } + return; + + + case OC_PROPOSAL: + { + json_t *order; + json_t *merchant_obj; + + order = make_order (cmd->details.proposal.max_fee, + cmd->details.proposal.amount); + + + if (NULL == order) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to create the order at command #%u\n", + is->ip); + fail (is); + return; + } + + GNUNET_assert (NULL != (merchant_obj = json_pack ("{s:{s:s}}", + "merchant", + "instance", + instance))); + + + GNUNET_assert (-1 != json_object_update (order, merchant_obj)); + + cmd->details.proposal.po + = TALER_MERCHANT_order_put (ctx, + merchant_uri, + order, + &proposal_cb, + is); + json_decref (order); + if (NULL == cmd->details.proposal.po) + { + GNUNET_break (0); + fail (is); + return; + } + return; + } + + case OC_ADMIN_ADD_INCOMING: + if (NULL != + cmd->details.admin_add_incoming.reserve_reference) + { + ref = find_command (is, + cmd->details.admin_add_incoming.reserve_reference); + GNUNET_assert (NULL != ref); + GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc); + cmd->details.admin_add_incoming.reserve_priv + = ref->details.admin_add_incoming.reserve_priv; + } + else + { + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + + priv = GNUNET_CRYPTO_eddsa_key_create (); + cmd->details.admin_add_incoming.reserve_priv.eddsa_priv = *priv; + GNUNET_free (priv); + } + GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.admin_add_incoming.reserve_priv.eddsa_priv, + &reserve_pub.eddsa_pub); + if (GNUNET_OK != + TALER_string_to_amount (cmd->details.admin_add_incoming.amount, + &amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u\n", + cmd->details.admin_add_incoming.amount, + is->ip); + fail (is); + return; + } + + execution_date = GNUNET_TIME_absolute_get (); + GNUNET_TIME_round_abs (&execution_date); + sender_details = json_loads (cmd->details.admin_add_incoming.sender_details, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == sender_details) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse sender details `%s' at %u\n", + cmd->details.admin_add_incoming.sender_details, + is->ip); + fail (is); + return; + } + json_object_set (sender_details, "bank_uri", json_string (bank_uri)); + + transfer_details = json_loads (cmd->details.admin_add_incoming.transfer_details, + JSON_REJECT_DUPLICATES, + NULL); + + if (NULL == transfer_details) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse transfer details `%s' at %u\n", + cmd->details.admin_add_incoming.transfer_details, + is->ip); + fail (is); + return; + } + cmd->details.admin_add_incoming.aih + = TALER_EXCHANGE_admin_add_incoming (exchange, + exchange_uri_admin, + &reserve_pub, + &amount, + execution_date, + sender_details, + transfer_details, + &add_incoming_cb, + is); + json_decref (sender_details); + json_decref (transfer_details); + if (NULL == cmd->details.admin_add_incoming.aih) + { + GNUNET_break (0); + fail (is); + return; + } + return; + + case OC_WITHDRAW_SIGN: + GNUNET_assert (NULL != + cmd->details.reserve_withdraw.reserve_reference); + ref = find_command (is, + cmd->details.reserve_withdraw.reserve_reference); + GNUNET_assert (NULL != ref); + GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc); + if (NULL != cmd->details.reserve_withdraw.amount) + { + if (GNUNET_OK != + TALER_string_to_amount (cmd->details.reserve_withdraw.amount, + &amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u\n", + cmd->details.reserve_withdraw.amount, + is->ip); + fail (is); + return; + } + cmd->details.reserve_withdraw.pk = find_pk (is->keys, + &amount); + } + if (NULL == cmd->details.reserve_withdraw.pk) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to determine denomination key at %u\n", + is->ip); + fail (is); + return; + } + + /* create coin's private key */ + { + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + + priv = GNUNET_CRYPTO_eddsa_key_create (); + cmd->details.reserve_withdraw.coin_priv.eddsa_priv = *priv; + GNUNET_free (priv); + } + GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.reserve_withdraw.coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &cmd->details.reserve_withdraw.blinding_key, + sizeof (cmd->details.reserve_withdraw.blinding_key)); + + cmd->details.reserve_withdraw.wsh + = TALER_EXCHANGE_reserve_withdraw (exchange, + cmd->details.reserve_withdraw.pk, + &ref->details.admin_add_incoming.reserve_priv, + &cmd->details.reserve_withdraw.coin_priv, + &cmd->details.reserve_withdraw.blinding_key, + &reserve_withdraw_cb, + is); + if (NULL == cmd->details.reserve_withdraw.wsh) + { + GNUNET_break (0); + fail (is); + return; + } + return; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unknown command, OC: %d, label: %s.\n", + cmd->oc, + cmd->label); + fail (is); + } +} + +/** + * Functions of this type are called to provide the retrieved signing and + * denomination keys of the exchange. No TALER_EXCHANGE_*() functions should + * be called in this callback. + * + * @param cls closure + * @param keys information about keys of the exchange + */ +static void +cert_cb (void *cls, + const struct TALER_EXCHANGE_Keys *keys) +{ + struct InterpreterState *is = cls; + + /* check that keys is OK */ +#define ERR(cond) do { if(!(cond)) break; GNUNET_break (0); GNUNET_SCHEDULER_shutdown(); return; } while (0) + ERR (NULL == keys); + ERR (0 == keys->num_sign_keys); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Read %u signing keys\n", + keys->num_sign_keys); + ERR (0 == keys->num_denom_keys); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Read %u denomination keys\n", + keys->num_denom_keys); +#undef ERR + + /* run actual tests via interpreter-loop */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Certificate callback invoked, starting interpreter\n"); + is->keys = keys; + + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + +/** + * Signal handler called for SIGCHLD. Triggers the + * respective handler by writing to the trigger pipe. + */ +static void +sighandler_child_death () +{ + static char c; + int old_errno = errno; /* back-up errno */ + + GNUNET_break (1 == + GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle + (sigpipe, GNUNET_DISK_PIPE_END_WRITE), + &c, sizeof (c))); + errno = old_errno; /* restore errno */ +} + +/** + * Function run when the test terminates (good or bad). + * Cleans up our state. + * + * @param cls the interpreter state. + */ +static void +do_shutdown (void *cls) +{ + struct InterpreterState *is = cls; + struct Command *cmd; + + if (NULL != timeout_task) + { + GNUNET_SCHEDULER_cancel (timeout_task); + timeout_task = NULL; + } + + for (unsigned int i=0;OC_END != (cmd = &is->commands[i])->oc;i++) + switch (cmd->oc) + { + case OC_END: + GNUNET_assert (0); + break; + + case OC_PAY: + if (NULL != cmd->details.pay.ph) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MERCHANT_pay_cancel (cmd->details.pay.ph); + cmd->details.pay.ph = NULL; + } + break; + + case OC_PROPOSAL: + if (NULL != cmd->details.proposal.po) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MERCHANT_proposal_cancel (cmd->details.proposal.po); + cmd->details.proposal.po = NULL; + } + if (NULL != cmd->details.proposal.proposal_data) + { + json_decref (cmd->details.proposal.proposal_data); + cmd->details.proposal.proposal_data = NULL; + } + break; + + case OC_WITHDRAW_SIGN: + if (NULL != cmd->details.reserve_withdraw.wsh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_EXCHANGE_reserve_withdraw_cancel (cmd->details.reserve_withdraw.wsh); + cmd->details.reserve_withdraw.wsh = NULL; + } + if (NULL != cmd->details.reserve_withdraw.sig.rsa_signature) + { + GNUNET_CRYPTO_rsa_signature_free (cmd->details.reserve_withdraw.sig.rsa_signature); + cmd->details.reserve_withdraw.sig.rsa_signature = NULL; + } + GNUNET_free_non_null (cmd->details.reserve_withdraw.amount); + break; + + case OC_ADMIN_ADD_INCOMING: + if (NULL != cmd->details.admin_add_incoming.aih) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_EXCHANGE_admin_add_incoming_cancel (cmd->details.admin_add_incoming.aih); + cmd->details.admin_add_incoming.aih = NULL; + } + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Shutdown: unknown instruction %d at %u (%s)\n", + cmd->oc, + i, + cmd->label); + break; + } + + if (NULL != is->task) + { + GNUNET_SCHEDULER_cancel (is->task); + is->task = NULL; + } + GNUNET_free (is); + if (NULL != exchange) + { + TALER_EXCHANGE_disconnect (exchange); + exchange = NULL; + } + if (NULL != ctx) + { + GNUNET_CURL_fini (ctx); + ctx = NULL; + } + if (NULL != rc) + { + GNUNET_CURL_gnunet_rc_destroy (rc); + rc = NULL; + } +} + +/** + * Take currency and the part after ":" in the + * "CURRENCY:XX.YY" format, and return a string + * in the format "CURRENCY:XX.YY". + * + * @param currency currency + * @param rpart float numbers after the ":", in string form + * @return pointer to allocated and concatenated "CURRENCY:XX.YY" + * formatted string. + * + */ +char * +concat_amount (char *currency, char *rpart) +{ + char *str; + + GNUNET_asprintf (&str, "%s:%s", + currency, rpart); + return str; +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be + * NULL!) + * @param config configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *config) +{ + struct InterpreterState *is; + unsigned int cnt; + char *wget_cmd; + + if (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_string (config, + "payments-generator", + "exchange", + &exchange_uri)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "payments-generator", + "exchange"); + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_string (config, + "payments-generator", + "exchange_admin", + &exchange_uri_admin)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "payments-generator", + "exchange_admin"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + if (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_string (config, + "payments-generator", + "merchant", + &merchant_uri)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "payments-generator", + "merchant"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + if (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_string (config, + "payments-generator", + "bank", + &bank_uri)) + { + + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "payments-generator", + "bank"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + if (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_string (config, + "payments-generator", + "instance", + &instance)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "payments-generator", + "instance"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + if (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_string (config, + "payments-generator", + "currency", + ¤cy)) + { + + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "payments-generator", + "currency"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + if (!remote_exchange) + { + exchanged = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-httpd", + "taler-exchange-httpd", + NULL); + if (NULL == exchanged) + { + fprintf (stderr, + "Failed to run taler-exchange-httpd. Check your PATH.\n"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + fprintf (stderr, + "Waiting for taler-exchange-httpd to be ready\n"); + cnt = 0; + + GNUNET_asprintf (&wget_cmd, "wget -q -t 1 -T 1 %skeys -o /dev/null -O /dev/null", exchange_uri); + + do + { + fprintf (stderr, "."); + sleep (1); + cnt++; + if (cnt > 60) + { + fprintf (stderr, + "\nFailed to start taler-exchange-httpd\n"); + GNUNET_OS_process_kill (exchanged, + SIGKILL); + GNUNET_OS_process_wait (exchanged); + GNUNET_OS_process_destroy (exchanged); + GNUNET_SCHEDULER_shutdown (); + return; + } + } + while (0 != system (wget_cmd)); + GNUNET_free (wget_cmd); + + fprintf (stderr, "\n"); + } + + + if (!remote_merchant) + { + merchantd = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-merchant-httpd", + "taler-merchant-httpd", + "-L", "DEBUG", + NULL); + if (NULL == merchantd) + { + fprintf (stderr, + "Failed to run taler-merchant-httpd. Check your PATH.\n"); + GNUNET_OS_process_kill (exchanged, + SIGKILL); + GNUNET_OS_process_wait (exchanged); + GNUNET_OS_process_destroy (exchanged); + + GNUNET_SCHEDULER_shutdown (); + return; + } + /* give child time to start and bind against the socket */ + fprintf (stderr, + "Waiting for taler-merchant-httpd to be ready\n"); + cnt = 0; + GNUNET_asprintf (&wget_cmd, "wget -q -t 1 -T 1 %s -o /dev/null -O /dev/null", merchant_uri); + + do + { + fprintf (stderr, "."); + sleep (1); + cnt++; + if (cnt > 60) + { + fprintf (stderr, + "\nFailed to start taler-merchant-httpd\n"); + GNUNET_OS_process_kill (merchantd, + SIGKILL); + GNUNET_OS_process_wait (merchantd); + GNUNET_OS_process_destroy (merchantd); + GNUNET_OS_process_kill (exchanged, + SIGKILL); + GNUNET_OS_process_wait (exchanged); + GNUNET_OS_process_destroy (exchanged); + + GNUNET_SCHEDULER_shutdown (); + return; + } + } + while (0 != system (wget_cmd)); + fprintf (stderr, "\n"); + GNUNET_free (wget_cmd); + } + + struct Command commands[] = + { + /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config */ + { .oc = OC_ADMIN_ADD_INCOMING, + .label = "create-reserve-1", + .expected_response_code = MHD_HTTP_OK, + .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"account_number\":62, \"uuid\":1 }", + .details.admin_add_incoming.transfer_details = "{ \"uuid\": 1}", + .details.admin_add_incoming.amount = concat_amount (currency, "5.01") }, + + /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config */ + { .oc = OC_ADMIN_ADD_INCOMING, + .label = "create-reserve-2", + .expected_response_code = MHD_HTTP_OK, + .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"account_number\":62, \"uuid\":1 }", + .details.admin_add_incoming.transfer_details = "{ \"uuid\": 1}", + .details.admin_add_incoming.amount = concat_amount (currency, "5.01") }, + /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config */ + { .oc = OC_ADMIN_ADD_INCOMING, + .label = "create-reserve-3", + .expected_response_code = MHD_HTTP_OK, + .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"account_number\":62, \"uuid\":1 }", + .details.admin_add_incoming.transfer_details = "{ \"uuid\": 1}", + .details.admin_add_incoming.amount = concat_amount (currency, "5.01") }, + + /* Withdraw a 5 EUR coin, at fee of 1 ct */ + { .oc = OC_WITHDRAW_SIGN, + .label = "withdraw-coin-1", + .expected_response_code = MHD_HTTP_OK, + .details.reserve_withdraw.reserve_reference = "create-reserve-1", + .details.reserve_withdraw.amount = concat_amount (currency, "5") }, + + /* Withdraw a 5 EUR coin, at fee of 1 ct */ + { .oc = OC_WITHDRAW_SIGN, + .label = "withdraw-coin-2", + .expected_response_code = MHD_HTTP_OK, + .details.reserve_withdraw.reserve_reference = "create-reserve-2", + .details.reserve_withdraw.amount = concat_amount (currency, "5") }, + + /* Withdraw a 5 EUR coin, at fee of 1 ct */ + { .oc = OC_WITHDRAW_SIGN, + .label = "withdraw-coin-3", + .expected_response_code = MHD_HTTP_OK, + .details.reserve_withdraw.reserve_reference = "create-reserve-3", + .details.reserve_withdraw.amount = concat_amount (currency, "5") }, + + /* Create proposal */ + { .oc = OC_PROPOSAL, + .label = "create-proposal-1", + .expected_response_code = MHD_HTTP_OK, + .details.proposal.max_fee = concat_amount (currency, "0.5"), + .details.proposal.amount = concat_amount (currency, "0.5") }, + + /* Create proposal */ + { .oc = OC_PROPOSAL, + .label = "create-proposal-2", + .expected_response_code = MHD_HTTP_OK, + .details.proposal.max_fee = concat_amount (currency, "0.5"), + .details.proposal.amount = concat_amount (currency, "0.5") }, + + /* Create proposal */ + { .oc = OC_PROPOSAL, + .label = "create-proposal-3", + .expected_response_code = MHD_HTTP_OK, + .details.proposal.max_fee = concat_amount (currency, "0.5"), + .details.proposal.amount = concat_amount (currency, "5.0") }, + + { .oc = OC_PAY, + .label = "deposit-simple-1", + .expected_response_code = MHD_HTTP_OK, + .details.pay.contract_ref = "create-proposal-1", + .details.pay.coin_ref = "withdraw-coin-1", + .details.pay.amount_with_fee = concat_amount (currency, "5"), + .details.pay.amount_without_fee = concat_amount (currency, "4.99") }, + + { .oc = OC_PAY, + .label = "deposit-simple-2", + .expected_response_code = MHD_HTTP_OK, + .details.pay.contract_ref = "create-proposal-2", + .details.pay.coin_ref = "withdraw-coin-2", + .details.pay.amount_with_fee = concat_amount (currency, "5"), + .details.pay.amount_without_fee = concat_amount (currency, "4.99") }, + + { .oc = OC_PAY, + .label = "deposit-simple-3", + .expected_response_code = MHD_HTTP_OK, + .details.pay.contract_ref = "create-proposal-3", + .details.pay.coin_ref = "withdraw-coin-3", + .details.pay.amount_with_fee = concat_amount (currency, "5"), + .details.pay.amount_without_fee = concat_amount (currency, "4.99") }, + + { .oc = OC_END, + .label = "end-of-commands"} + }; + + + is = GNUNET_new (struct InterpreterState); + is->commands = GNUNET_malloc (sizeof (commands)); + memcpy (is->commands, + commands, + sizeof (commands)); + + ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, + &rc); + GNUNET_assert (NULL != ctx); + rc = GNUNET_CURL_gnunet_rc_create (ctx); + exchange = TALER_EXCHANGE_connect (ctx, + exchange_uri, + &cert_cb, + is, + TALER_EXCHANGE_OPTION_END); + GNUNET_assert (NULL != exchange); + timeout_task + = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, 150), + &do_timeout, NULL); + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + is); +} + + +int +main (int argc, + char *argv[]) +{ + struct GNUNET_SIGNAL_Context *shc_chld; + struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_uint ('n', + "times", + "TIMES", + "How many times the commands should be run.", + ×), + GNUNET_GETOPT_option_flag ('e', + "remote-exchange", + "Do not fork any exchange", + &remote_exchange), + GNUNET_GETOPT_option_flag ('m', + "remote-merchant", + "Do not fork any merchant", + &remote_merchant), + GNUNET_GETOPT_OPTION_END + }; + + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("taler-merchant-generate-payments", + "DEBUG", + NULL); + result = GNUNET_SYSERR; + 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); + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, + "taler-merchant-generate-payments", + "Populates DB with fake payments", + options, + &run, NULL)) + return 77; + + + GNUNET_SIGNAL_handler_uninstall (shc_chld); + shc_chld = NULL; + GNUNET_DISK_pipe_close (sigpipe); + if (!remote_merchant && NULL != merchantd) + { + GNUNET_OS_process_kill (merchantd, + SIGTERM); + GNUNET_OS_process_wait (merchantd); + GNUNET_OS_process_destroy (merchantd); + } + if (!remote_exchange && NULL != exchanged) + { + GNUNET_OS_process_kill (exchanged, + SIGTERM); + GNUNET_OS_process_wait (exchanged); + GNUNET_OS_process_destroy (exchanged); + } + if (77 == result) + return 77; + return (GNUNET_OK == result) ? 0 : 1; +} |