summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2019-01-11 21:27:34 +0100
committerChristian Grothoff <christian@grothoff.org>2019-01-11 21:43:15 +0100
commit54fc83ee6b910d482948c6ec8185df7aab1b0cb1 (patch)
tree10c04cad1392659a9ccef469271f866e393ebb48 /src/lib
parent57ab9f9fdba607fcc3817adf58f37c5390f8d220 (diff)
downloadexchange-54fc83ee6b910d482948c6ec8185df7aab1b0cb1.tar.gz
exchange-54fc83ee6b910d482948c6ec8185df7aab1b0cb1.tar.bz2
exchange-54fc83ee6b910d482948c6ec8185df7aab1b0cb1.zip
fix cyclic dependency by combining exchange-lib and auditor-lib directories
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Makefile.am255
-rw-r--r--src/lib/afl-generate.sh34
-rw-r--r--src/lib/auditor_api_curl_defaults.c75
-rw-r--r--src/lib/auditor_api_curl_defaults.h41
-rw-r--r--src/lib/auditor_api_deposit_confirmation.c384
-rw-r--r--src/lib/auditor_api_exchanges.c245
-rw-r--r--src/lib/auditor_api_handle.c527
-rw-r--r--src/lib/auditor_api_handle.h71
-rw-r--r--src/lib/backoff.h38
-rw-r--r--src/lib/baseline/admin_add_incoming.req7
-rw-r--r--src/lib/baseline/deposit.req8
-rw-r--r--src/lib/baseline/keys.req7
-rw-r--r--src/lib/baseline/refresh_link.req5
-rw-r--r--src/lib/baseline/refresh_melt.req8
-rw-r--r--src/lib/baseline/refresh_reveal.req7
-rw-r--r--src/lib/baseline/reserve_status.req4
-rw-r--r--src/lib/baseline/reserve_withdraw.req7
-rw-r--r--src/lib/baseline/wire.req5
-rw-r--r--src/lib/baseline/wire_sepa.req5
-rw-r--r--src/lib/baseline/wire_test.req5
-rw-r--r--src/lib/exchange_api_common.c353
-rw-r--r--src/lib/exchange_api_curl_defaults.c75
-rw-r--r--src/lib/exchange_api_curl_defaults.h41
-rw-r--r--src/lib/exchange_api_deposit.c597
-rw-r--r--src/lib/exchange_api_handle.c1779
-rw-r--r--src/lib/exchange_api_handle.h103
-rw-r--r--src/lib/exchange_api_payback.c374
-rw-r--r--src/lib/exchange_api_refresh.c1678
-rw-r--r--src/lib/exchange_api_refresh_link.c444
-rw-r--r--src/lib/exchange_api_refund.c416
-rw-r--r--src/lib/exchange_api_reserve.c1203
-rw-r--r--src/lib/exchange_api_track_transaction.c367
-rw-r--r--src/lib/exchange_api_track_transfer.c388
-rw-r--r--src/lib/exchange_api_wire.c442
-rw-r--r--src/lib/test_auditor_api.c549
-rw-r--r--src/lib/test_auditor_api.conf202
-rw-r--r--src/lib/test_auditor_api_expire_reserve_now.conf4
-rw-r--r--src/lib/test_exchange_api.conf203
-rw-r--r--src/lib/test_exchange_api_expire_reserve_now.conf4
-rw-r--r--src/lib/test_exchange_api_home/.config/taler/account-1.json5
-rw-r--r--src/lib/test_exchange_api_home/.config/taler/account-2.json4
-rw-r--r--src/lib/test_exchange_api_home/.config/taler/sepa.json9
-rw-r--r--src/lib/test_exchange_api_home/.config/taler/test.json8
-rw-r--r--src/lib/test_exchange_api_home/.config/taler/x-taler-bank.json4
-rw-r--r--src/lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv1
-rw-r--r--src/lib/test_exchange_api_keys_cherry_picking.conf161
-rw-r--r--src/lib/test_exchange_api_keys_cherry_picking_extended.conf5
-rw-r--r--src/lib/test_exchange_api_keys_cherry_picking_extended_2.conf5
-rw-r--r--src/lib/test_exchange_api_keys_cherry_picking_new.c233
-rw-r--r--src/lib/test_exchange_api_new.c1001
-rwxr-xr-xsrc/lib/test_exchange_api_overlapping_keys_bug.c135
-rw-r--r--src/lib/test_exchange_api_twisted.c399
-rw-r--r--src/lib/test_exchange_api_twisted.conf169
-rw-r--r--src/lib/testing_api_cmd_bank_check.c379
-rw-r--r--src/lib/testing_api_cmd_batch.c208
-rw-r--r--src/lib/testing_api_cmd_check_keys.c165
-rw-r--r--src/lib/testing_api_cmd_deposit.c576
-rw-r--r--src/lib/testing_api_cmd_exec_aggregator.c166
-rw-r--r--src/lib/testing_api_cmd_exec_auditor-sign.c232
-rw-r--r--src/lib/testing_api_cmd_exec_keyup.c169
-rw-r--r--src/lib/testing_api_cmd_exec_wirewatch.c168
-rw-r--r--src/lib/testing_api_cmd_fakebank_transfer.c756
-rw-r--r--src/lib/testing_api_cmd_payback.c498
-rw-r--r--src/lib/testing_api_cmd_refresh.c1317
-rw-r--r--src/lib/testing_api_cmd_refund.c331
-rw-r--r--src/lib/testing_api_cmd_serialize_keys.c315
-rw-r--r--src/lib/testing_api_cmd_signal.c116
-rw-r--r--src/lib/testing_api_cmd_sleep.c104
-rw-r--r--src/lib/testing_api_cmd_status.c264
-rw-r--r--src/lib/testing_api_cmd_track.c810
-rw-r--r--src/lib/testing_api_cmd_wire.c238
-rw-r--r--src/lib/testing_api_cmd_withdraw.c511
-rw-r--r--src/lib/testing_api_helpers.c1050
-rw-r--r--src/lib/testing_api_loop.c825
-rw-r--r--src/lib/testing_api_trait_amount.c82
-rw-r--r--src/lib/testing_api_trait_blinding_key.c80
-rw-r--r--src/lib/testing_api_trait_cmd.c82
-rw-r--r--src/lib/testing_api_trait_coin_priv.c78
-rw-r--r--src/lib/testing_api_trait_denom_pub.c79
-rw-r--r--src/lib/testing_api_trait_denom_sig.c80
-rw-r--r--src/lib/testing_api_trait_exchange_pub.c77
-rw-r--r--src/lib/testing_api_trait_exchange_sig.c77
-rw-r--r--src/lib/testing_api_trait_fresh_coin.c79
-rw-r--r--src/lib/testing_api_trait_json.c122
-rw-r--r--src/lib/testing_api_trait_key_peer.c127
-rw-r--r--src/lib/testing_api_trait_number.c116
-rw-r--r--src/lib/testing_api_trait_process.c83
-rw-r--r--src/lib/testing_api_trait_reserve_priv.c76
-rw-r--r--src/lib/testing_api_trait_string.c305
-rw-r--r--src/lib/testing_api_trait_wtid.c76
-rw-r--r--src/lib/testing_api_traits.c80
-rw-r--r--src/lib/testing_auditor_api_cmd_deposit_confirmation.c411
-rw-r--r--src/lib/testing_auditor_api_cmd_exchanges.c296
-rw-r--r--src/lib/testing_auditor_api_cmd_exec_auditor.c162
-rw-r--r--src/lib/testing_auditor_api_cmd_exec_auditor_dbinit.c163
-rw-r--r--src/lib/testing_auditor_api_cmd_exec_wire_auditor.c162
-rw-r--r--src/lib/testing_auditor_api_helpers.c226
97 files changed, 25416 insertions, 0 deletions
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
new file mode 100644
index 00000000..59b1f316
--- /dev/null
+++ b/src/lib/Makefile.am
@@ -0,0 +1,255 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/bank-lib
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+ libtalerexchange.la \
+ libtalertesting.la \
+ libtalerauditor.la \
+ libtalerauditortesting.la
+
+libtalerexchange_la_LDFLAGS = \
+ -version-info 4:0:0 \
+ -no-undefined
+libtalerexchange_la_SOURCES = \
+ exchange_api_curl_defaults.c exchange_api_curl_defaults.h \
+ exchange_api_common.c \
+ exchange_api_handle.c exchange_api_handle.h \
+ exchange_api_deposit.c \
+ exchange_api_payback.c \
+ exchange_api_refresh.c \
+ exchange_api_refresh_link.c \
+ exchange_api_refund.c \
+ exchange_api_reserve.c \
+ exchange_api_track_transaction.c \
+ exchange_api_track_transfer.c \
+ exchange_api_wire.c
+libtalerexchange_la_LIBADD = \
+ libtalerauditor.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+
+libtalerauditor_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libtalerauditor_la_SOURCES = \
+ auditor_api_curl_defaults.c auditor_api_curl_defaults.h \
+ auditor_api_handle.c auditor_api_handle.h \
+ auditor_api_deposit_confirmation.c \
+ auditor_api_exchanges.c
+libtalerauditor_la_LIBADD = \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+if HAVE_LIBCURL
+libtalerauditor_la_LIBADD += -lcurl
+else
+if HAVE_LIBGNURL
+libtalerauditor_la_LIBADD += -lgnurl
+endif
+endif
+
+
+libtalertesting_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libtalertesting_la_SOURCES = \
+ exchange_api_curl_defaults.c \
+ testing_api_cmd_exec_aggregator.c \
+ testing_api_cmd_exec_wirewatch.c \
+ testing_api_cmd_exec_keyup.c \
+ testing_api_cmd_exec_auditor-sign.c \
+ testing_api_cmd_fakebank_transfer.c \
+ testing_api_cmd_withdraw.c \
+ testing_api_cmd_wire.c \
+ testing_api_cmd_refund.c \
+ testing_api_cmd_status.c \
+ testing_api_cmd_deposit.c \
+ testing_api_cmd_sleep.c \
+ testing_api_cmd_refresh.c \
+ testing_api_cmd_track.c \
+ testing_api_cmd_bank_check.c \
+ testing_api_cmd_payback.c \
+ testing_api_cmd_signal.c \
+ testing_api_cmd_check_keys.c \
+ testing_api_cmd_batch.c \
+ testing_api_cmd_serialize_keys.c \
+ testing_api_helpers.c \
+ testing_api_loop.c \
+ testing_api_traits.c \
+ testing_api_trait_blinding_key.c \
+ testing_api_trait_coin_priv.c \
+ testing_api_trait_denom_pub.c \
+ testing_api_trait_denom_sig.c \
+ testing_api_trait_exchange_pub.c \
+ testing_api_trait_exchange_sig.c \
+ testing_api_trait_json.c \
+ testing_api_trait_process.c \
+ testing_api_trait_reserve_priv.c \
+ testing_api_trait_number.c \
+ testing_api_trait_fresh_coin.c \
+ testing_api_trait_string.c \
+ testing_api_trait_key_peer.c \
+ testing_api_trait_wtid.c \
+ testing_api_trait_amount.c \
+ testing_api_trait_cmd.c
+libtalertesting_la_LIBADD = \
+ libtalerexchange.la \
+ $(top_builddir)/src/wire/libtalerwire.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+libtalerauditortesting_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libtalerauditortesting_la_SOURCES = \
+ testing_auditor_api_helpers.c \
+ testing_auditor_api_cmd_deposit_confirmation.c \
+ testing_auditor_api_cmd_exchanges.c \
+ testing_auditor_api_cmd_exec_auditor.c \
+ testing_auditor_api_cmd_exec_auditor_dbinit.c \
+ testing_auditor_api_cmd_exec_wire_auditor.c
+libtalerauditortesting_la_LIBADD = \
+ libtalerauditor.la \
+ libtalerexchange.la \
+ libtalertesting.la \
+ $(top_builddir)/src/wire/libtalerwire.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+
+if HAVE_LIBCURL
+libtalerexchange_la_LIBADD += -lcurl
+else
+if HAVE_LIBGNURL
+libtalerexchange_la_LIBADD += -lgnurl
+endif
+endif
+
+check_PROGRAMS = \
+ test_exchange_api_keys_cherry_picking_new \
+ test_exchange_api_overlapping_keys_bug \
+ test_exchange_api_new \
+ test_auditor_api
+
+if HAVE_TWISTER
+ check_PROGRAMS += \
+ test_exchange_api_twisted
+
+test_exchange_api_twisted_SOURCES = \
+ test_exchange_api_twisted.c
+test_exchange_api_twisted_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ libtalertesting.la \
+ libtalerexchange.la \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -ltalertwistertesting \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson
+
+endif
+
+AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
+
+TESTS = \
+ $(check_PROGRAMS)
+
+test_exchange_api_new_SOURCES = \
+ test_exchange_api_new.c
+test_exchange_api_new_LDADD = \
+ libtalertesting.la \
+ libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson
+
+test_exchange_api_overlapping_keys_bug_SOURCES = \
+ test_exchange_api_overlapping_keys_bug.c
+test_exchange_api_overlapping_keys_bug_LDADD = \
+ libtalertesting.la \
+ libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson
+
+test_exchange_api_keys_cherry_picking_new_SOURCES = \
+ test_exchange_api_keys_cherry_picking_new.c
+test_exchange_api_keys_cherry_picking_new_LDADD = \
+ libtalertesting.la \
+ libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson
+
+test_auditor_api_SOURCES = \
+ test_auditor_api.c
+test_auditor_api_LDADD = \
+ libtalerauditortesting.la \
+ libtalerauditor.la \
+ libtalertesting.la \
+ libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson
+
+
+
+EXTRA_DIST = \
+ test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv \
+ test_exchange_api_home/.config/taler/test.json \
+ test_exchange_api_home/.config/taler/sepa.json \
+ test_exchange_api.conf \
+ test_exchange_api_keys_cherry_picking.conf \
+ test_exchange_api_keys_cherry_picking_extended.conf \
+ test_auditor_api.conf \
+ test_auditor_api_expire_reserve_now.conf
diff --git a/src/lib/afl-generate.sh b/src/lib/afl-generate.sh
new file mode 100644
index 00000000..b0afcab3
--- /dev/null
+++ b/src/lib/afl-generate.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# This file is part of TALER
+# Copyright (C) 2015 GNUnet e.V.
+#
+# TALER is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Affero General Public License as published by the Free Software
+# Foundation; either version 3, or (at your option) any later version.
+#
+# TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License along with
+# TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+#
+#
+# This will generate testcases in a directory 'afl-tests', which can then
+# be moved into src/exchange/afl-tests/ to be run during exchange-testing.
+#
+# This script uses American Fuzzy Loop (AFL) to fuzz the exchange to
+# automatically create tests with good coverage. You must install
+# AFL and set AFL_HOME to the directory where AFL is installed
+# before running. Also, a directory "baseline/" should exist with
+# templates for inputs for AFL to fuzz. These can be generated
+# by running wireshark on loopback while running 'make check' in
+# this directory. Save each HTTP request to a new file.
+#
+# Note that you want to switch 'TESTRUN = NO' and pre-init the
+# database before running this, otherwise it will be awfully slow.
+#
+# Must be run from this directory.
+#
+$AFL_HOME/afl-fuzz -i baseline/ -m 250 -o afl-tests/ -f /tmp/afl-input taler-exchange-httpd -i -f /tmp/afl-input -d test-exchange-home/ -C
diff --git a/src/lib/auditor_api_curl_defaults.c b/src/lib/auditor_api_curl_defaults.c
new file mode 100644
index 00000000..d5b92400
--- /dev/null
+++ b/src/lib/auditor_api_curl_defaults.c
@@ -0,0 +1,75 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file auditor-lib/curl_defaults.c
+ * @brief curl easy handle defaults
+ * @author Florian Dold
+ */
+
+#include "auditor_api_curl_defaults.h"
+
+
+/**
+ * Get a curl handle with the right defaults
+ * for the exchange lib. In the future, we might manage a pool of connections here.
+ *
+ * @param url URL to query
+ */
+CURL *
+TAL_curl_easy_get (const char *url)
+{
+ CURL *eh;
+
+ eh = curl_easy_init ();
+
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_ENCODING,
+ "deflate"));
+#ifdef CURLOPT_TCP_FASTOPEN
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TCP_FASTOPEN,
+ 1L));
+#endif
+ {
+ /* Unfortunately libcurl needs chunk to be alive until after
+ curl_easy_perform. To avoid manual cleanup, we keep
+ one static list here. */
+ static struct curl_slist *chunk = NULL;
+ if (NULL == chunk)
+ {
+ /* With POST requests, we do not want to wait for the
+ "100 Continue" response, as our request bodies are usually
+ small and directy sending them saves us a round trip.
+
+ Clearing the expect header like this disables libcurl's
+ default processing of the header.
+
+ Disabling this header is safe for other HTTP methods, thus
+ we don't distinguish further before setting it. */
+ chunk = curl_slist_append (chunk, "Expect:");
+ }
+ GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HTTPHEADER, chunk));
+ }
+
+ return eh;
+}
diff --git a/src/lib/auditor_api_curl_defaults.h b/src/lib/auditor_api_curl_defaults.h
new file mode 100644
index 00000000..3be5816b
--- /dev/null
+++ b/src/lib/auditor_api_curl_defaults.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file auditor-lib/curl_defaults.h
+ * @brief curl easy handle defaults
+ * @author Florian Dold
+ */
+
+#ifndef _TALER_CURL_DEFAULTS_H
+#define _TALER_CURL_DEFAULTS_H
+
+
+#include "platform.h"
+#include <gnunet/gnunet_curl_lib.h>
+
+
+/**
+ * Get a curl handle with the right defaults
+ * for the auditor lib. In the future, we might manage a pool of connections here.
+ *
+ * @param url URL to query
+ */
+CURL *
+TAL_curl_easy_get (const char *url);
+
+#endif /* _TALER_CURL_DEFAULTS_H */
diff --git a/src/lib/auditor_api_deposit_confirmation.c b/src/lib/auditor_api_deposit_confirmation.c
new file mode 100644
index 00000000..99c855fb
--- /dev/null
+++ b/src/lib/auditor_api_deposit_confirmation.c
@@ -0,0 +1,384 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file auditor-lib/auditor_api_deposit_confirmation.c
+ * @brief Implementation of the /deposit request of the auditor's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "auditor_api_handle.h"
+#include "taler_signatures.h"
+#include "auditor_api_curl_defaults.h"
+
+
+/**
+ * @brief A DepositConfirmation Handle
+ */
+struct TALER_AUDITOR_DepositConfirmationHandle
+{
+
+ /**
+ * The connection to auditor this request handle will use
+ */
+ struct TALER_AUDITOR_Handle *auditor;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * JSON encoding of the request to POST.
+ */
+ char *json_enc;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_AUDITOR_DepositConfirmationResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /deposit-confirmation request.
+ *
+ * @param cls the `struct TALER_AUDITOR_DepositConfirmationHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param djson parsed JSON result, NULL on error
+ */
+static void
+handle_deposit_confirmation_finished (void *cls,
+ long response_code,
+ const void *djson)
+{
+ const json_t *json = djson;
+ struct TALER_AUDITOR_DepositConfirmationHandle *dh = cls;
+
+ dh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the auditor is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Nothing really to verify, auditor says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ dh->cb (dh->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (json),
+ json);
+ TALER_AUDITOR_deposit_confirmation_cancel (dh);
+}
+
+
+/**
+ * Verify signature information about the deposit-confirmation.
+ *
+ * @param h_wire hash of merchant wire details
+ * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor)
+ * @param timestamp timestamp when the contract was finalized, must not be too far in the future
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the auditor (can be zero if refunds are not allowed); must not be after the @a wire_deadline
+ * @param amount_without_fee the amount confirmed to be wired by the exchange to the merchant
+ * @param coin_pub coin’s public key
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT
+ * @param exchange_pub the public key of the exchange that matches @a exchange_sig
+ * @param master_pub master public key of the exchange
+ * @param ep_start when does @a exchange_pub validity start
+ * @param ep_expire when does @a exchange_pub usage end
+ * @param ep_end when does @a exchange_pub legal validity end
+ * @param master_sig master signature affirming validity of @a exchange_pub
+ * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not
+ */
+static int
+verify_signatures (const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract_terms,
+ struct GNUNET_TIME_Absolute timestamp,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Absolute ep_start,
+ struct GNUNET_TIME_Absolute ep_expire,
+ struct GNUNET_TIME_Absolute ep_end,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TALER_DepositConfirmationPS dc;
+ struct TALER_ExchangeSigningKeyValidityPS sv;
+
+ dc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT);
+ dc.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS));
+ dc.h_contract_terms = *h_contract_terms;
+ dc.h_wire = *h_wire;
+ dc.timestamp = GNUNET_TIME_absolute_hton (timestamp);
+ dc.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
+ TALER_amount_hton (&dc.amount_without_fee,
+ amount_without_fee);
+ dc.coin_pub = *coin_pub;
+ dc.merchant = *merchant_pub;
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT,
+ &dc.purpose,
+ &exchange_sig->eddsa_signature,
+ &exchange_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ TALER_LOG_WARNING ("Invalid signature on /deposit-confirmation request!\n");
+ {
+ TALER_LOG_DEBUG ("... amount_without_fee was %s\n",
+ TALER_amount2s (amount_without_fee));
+ }
+
+ return GNUNET_SYSERR;
+ }
+ sv.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY);
+ sv.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS));
+ sv.master_public_key = *master_pub;
+ sv.start = GNUNET_TIME_absolute_hton (ep_start);
+ sv.expire = GNUNET_TIME_absolute_hton (ep_expire);
+ sv.end = GNUNET_TIME_absolute_hton (ep_end);
+ sv.signkey_pub = *exchange_pub;
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY,
+ &sv.purpose,
+ &master_sig->eddsa_signature,
+ &master_pub->eddsa_pub))
+ {
+ GNUNET_break (0);
+ TALER_LOG_WARNING ("Invalid signature on exchange signing key!\n");
+ return GNUNET_SYSERR;
+ }
+ if (0 == GNUNET_TIME_absolute_get_remaining (ep_end).rel_value_us)
+ {
+ GNUNET_break (0);
+ TALER_LOG_WARNING ("Exchange signing key is no longer valid!\n");
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Submit a deposit-confirmation permission to the auditor and get the
+ * auditor's response. Note that while we return the response
+ * verbatim to the caller for further processing, we do already verify
+ * that the response is well-formed. If the auditor's reply is not
+ * well-formed, we return an HTTP status code of zero to @a cb.
+ *
+ * We also verify that the @a exchange_sig is valid for this deposit-confirmation
+ * request, and that the @a master_sig is a valid signature for @a
+ * exchange_pub. Also, the @a auditor must be ready to operate (i.e. have
+ * finished processing the /version reply). If either check fails, we do
+ * NOT initiate the transaction with the auditor and instead return NULL.
+ *
+ * @param auditor the auditor handle; the auditor must be ready to operate
+ * @param h_wire hash of merchant wire details
+ * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor)
+ * @param timestamp timestamp when the contract was finalized, must not be too far in the future
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the auditor (can be zero if refunds are not allowed); must not be after the @a wire_deadline
+ * @param amount_without_fee the amount confirmed to be wired by the exchange to the merchant
+ * @param coin_pub coin’s public key
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT
+ * @param exchange_pub the public key of the exchange that matches @a exchange_sig
+ * @param master_pub master public key of the exchange
+ * @param ep_start when does @a exchange_pub validity start
+ * @param ep_expire when does @a exchange_pub usage end
+ * @param ep_end when does @a exchange_pub legal validity end
+ * @param master_sig master signature affirming validity of @a exchange_pub
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_AUDITOR_DepositConfirmationHandle *
+TALER_AUDITOR_deposit_confirmation (struct TALER_AUDITOR_Handle *auditor,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract_terms,
+ struct GNUNET_TIME_Absolute timestamp,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ const struct TALER_Amount *amount_without_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Absolute ep_start,
+ struct GNUNET_TIME_Absolute ep_expire,
+ struct GNUNET_TIME_Absolute ep_end,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_AUDITOR_DepositConfirmationResultCallback cb,
+ void *cb_cls)
+{
+ struct TALER_AUDITOR_DepositConfirmationHandle *dh;
+ struct GNUNET_CURL_Context *ctx;
+ json_t *deposit_confirmation_obj;
+ CURL *eh;
+
+ (void) GNUNET_TIME_round_abs (&timestamp);
+ (void) GNUNET_TIME_round_abs (&refund_deadline);
+ (void) GNUNET_TIME_round_abs (&ep_start);
+ (void) GNUNET_TIME_round_abs (&ep_expire);
+ (void) GNUNET_TIME_round_abs (&ep_end);
+ GNUNET_assert (GNUNET_YES ==
+ MAH_handle_is_ready (auditor));
+ if (GNUNET_OK !=
+ verify_signatures (h_wire,
+ h_contract_terms,
+ timestamp,
+ refund_deadline,
+ amount_without_fee,
+ coin_pub,
+ merchant_pub,
+ exchange_pub,
+ exchange_sig,
+ master_pub,
+ ep_start,
+ ep_expire,
+ ep_end,
+ master_sig))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+
+ deposit_confirmation_obj
+ = json_pack ("{s:o, s:o," /* H_wire, h_contract_terms */
+ " s:o, s:o," /* timestamp, refund_deadline */
+ " s:o, s:o," /* amount_without_fees, coin_pub */
+ " s:o, s:o," /* merchant_pub, exchange_sig */
+ " s:o, s:o," /* master_pub, ep_start */
+ " s:o, s:o," /* ep_expire, ep_end */
+ " s:o}", /* master_sig */
+ "H_wire", GNUNET_JSON_from_data_auto (&h_wire),
+ "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms),
+ "timestamp", GNUNET_JSON_from_time_abs (timestamp),
+ "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline),
+ "amount_without_fee", TALER_JSON_from_amount (amount_without_fee),
+ "coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
+ "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub),
+ "exchange_sig", GNUNET_JSON_from_data_auto (exchange_sig),
+ "master_pub", GNUNET_JSON_from_data_auto (master_pub),
+ "ep_start", GNUNET_JSON_from_time_abs (ep_start),
+ "ep_expire", GNUNET_JSON_from_time_abs (ep_expire),
+ "ep_end", GNUNET_JSON_from_time_abs (ep_end),
+ "master_sig", GNUNET_JSON_from_data_auto (master_sig));
+ if (NULL == deposit_confirmation_obj)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ dh = GNUNET_new (struct TALER_AUDITOR_DepositConfirmationHandle);
+ dh->auditor = auditor;
+ dh->cb = cb;
+ dh->cb_cls = cb_cls;
+ dh->url = MAH_path_to_url (auditor, "/deposit-confirmation");
+
+ eh = TAL_curl_easy_get (dh->url);
+ GNUNET_assert (NULL != (dh->json_enc =
+ json_dumps (deposit_confirmation_obj,
+ JSON_COMPACT)));
+ json_decref (deposit_confirmation_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for deposit-confirmation: `%s'\n",
+ dh->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ dh->json_enc));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ strlen (dh->json_enc)));
+ ctx = MAH_handle_to_context (auditor);
+ dh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_deposit_confirmation_finished,
+ dh);
+ return dh;
+}
+
+
+/**
+ * Cancel a deposit-confirmation permission request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param deposit-confirmation the deposit-confirmation permission request handle
+ */
+void
+TALER_AUDITOR_deposit_confirmation_cancel (struct TALER_AUDITOR_DepositConfirmationHandle *deposit_confirmation)
+{
+ if (NULL != deposit_confirmation->job)
+ {
+ GNUNET_CURL_job_cancel (deposit_confirmation->job);
+ deposit_confirmation->job = NULL;
+ }
+ GNUNET_free (deposit_confirmation->url);
+ GNUNET_free (deposit_confirmation->json_enc);
+ GNUNET_free (deposit_confirmation);
+}
+
+
+/* end of auditor_api_deposit_confirmation.c */
diff --git a/src/lib/auditor_api_exchanges.c b/src/lib/auditor_api_exchanges.c
new file mode 100644
index 00000000..770eedda
--- /dev/null
+++ b/src/lib/auditor_api_exchanges.c
@@ -0,0 +1,245 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file auditor-lib/auditor_api_exchanges.c
+ * @brief Implementation of the /exchanges request of the auditor's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "auditor_api_handle.h"
+#include "taler_signatures.h"
+#include "auditor_api_curl_defaults.h"
+
+/**
+ * How many exchanges do we allow a single auditor to
+ * audit at most?
+ */
+#define MAX_EXCHANGES 1024
+
+
+/**
+ * @brief A ListExchanges Handle
+ */
+struct TALER_AUDITOR_ListExchangesHandle
+{
+
+ /**
+ * The connection to auditor this request handle will use
+ */
+ struct TALER_AUDITOR_Handle *auditor;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_AUDITOR_ListExchangesResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /deposit-confirmation request.
+ *
+ * @param cls the `struct TALER_AUDITOR_ListExchangesHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param djson parsed JSON result, NULL on error
+ */
+static void
+handle_exchanges_finished (void *cls,
+ long response_code,
+ const void *djson)
+{
+ const json_t *json = djson;
+ const json_t *ja;
+ unsigned int ja_len;
+ struct TALER_AUDITOR_ListExchangesHandle *leh = cls;
+
+ leh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ ja = json_object_get (json,
+ "exchanges");
+ if ( (NULL == ja) ||
+ (! json_is_array (ja)) )
+ {
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+
+ ja_len = json_array_size (ja);
+ if (ja_len > MAX_EXCHANGES)
+ {
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ {
+ struct TALER_AUDITOR_ExchangeInfo ei[ja_len];
+ int ok;
+
+ ok = GNUNET_YES;
+ for (unsigned int i=0;i<ja_len;i++)
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_pub", &ei[i].master_pub),
+ GNUNET_JSON_spec_string ("exchange_url", &ei[i].exchange_url),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (ja,
+ i),
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ok = GNUNET_NO;
+ break;
+ }
+ }
+ if (GNUNET_YES != ok)
+ break;
+ leh->cb (leh->cb_cls,
+ response_code,
+ TALER_EC_NONE,
+ ja_len,
+ ei,
+ json);
+ leh->cb = NULL;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the auditor is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ if (NULL != leh->cb)
+ leh->cb (leh->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (json),
+ 0,
+ NULL,
+ json);
+ TALER_AUDITOR_list_exchanges_cancel (leh);
+}
+
+
+/**
+ * Submit an /exchanges request to the auditor and get the
+ * auditor's response. If the auditor's reply is not
+ * well-formed, we return an HTTP status code of zero to @a cb.
+ *
+ * @param auditor the auditor handle; the auditor must be ready to operate
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_AUDITOR_ListExchangesHandle *
+TALER_AUDITOR_list_exchanges (struct TALER_AUDITOR_Handle *auditor,
+ TALER_AUDITOR_ListExchangesResultCallback cb,
+ void *cb_cls)
+{
+ struct TALER_AUDITOR_ListExchangesHandle *leh;
+ struct GNUNET_CURL_Context *ctx;
+ CURL *eh;
+
+ GNUNET_assert (GNUNET_YES ==
+ MAH_handle_is_ready (auditor));
+
+ leh = GNUNET_new (struct TALER_AUDITOR_ListExchangesHandle);
+ leh->auditor = auditor;
+ leh->cb = cb;
+ leh->cb_cls = cb_cls;
+ leh->url = MAH_path_to_url (auditor, "/exchanges");
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for list-exchanges: `%s'\n",
+ leh->url);
+ eh = TAL_curl_easy_get (leh->url);
+ ctx = MAH_handle_to_context (auditor);
+ leh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_NO,
+ &handle_exchanges_finished,
+ leh);
+ return leh;
+}
+
+
+/**
+ * Cancel a deposit-confirmation permission request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param deposit-confirmation the deposit-confirmation permission request handle
+ */
+void
+TALER_AUDITOR_list_exchanges_cancel (struct TALER_AUDITOR_ListExchangesHandle *leh)
+{
+ if (NULL != leh->job)
+ {
+ GNUNET_CURL_job_cancel (leh->job);
+ leh->job = NULL;
+ }
+ GNUNET_free (leh->url);
+ GNUNET_free (leh);
+}
+
+
+/* end of auditor_api_exchanges.c */
diff --git a/src/lib/auditor_api_handle.c b/src/lib/auditor_api_handle.c
new file mode 100644
index 00000000..aca591dc
--- /dev/null
+++ b/src/lib/auditor_api_handle.c
@@ -0,0 +1,527 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file auditor-lib/auditor_api_handle.c
+ * @brief Implementation of the "handle" component of the auditor's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "taler_signatures.h"
+#include "auditor_api_handle.h"
+#include "auditor_api_curl_defaults.h"
+#include "backoff.h"
+
+/**
+ * Which revision of the Taler auditor protocol is implemented
+ * by this library? Used to determine compatibility.
+ */
+#define TALER_PROTOCOL_CURRENT 0
+
+/**
+ * How many revisions back are we compatible to?
+ */
+#define TALER_PROTOCOL_AGE 0
+
+
+/**
+ * Log error related to CURL operations.
+ *
+ * @param type log level
+ * @param function which function failed to run
+ * @param code what was the curl error code
+ */
+#define CURL_STRERROR(type, function, code) \
+ GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \
+ function, __FILE__, __LINE__, curl_easy_strerror (code));
+
+/**
+ * Stages of initialization for the `struct TALER_AUDITOR_Handle`
+ */
+enum AuditorHandleState
+{
+ /**
+ * Just allocated.
+ */
+ MHS_INIT = 0,
+
+ /**
+ * Obtained the auditor's versioning data and version.
+ */
+ MHS_VERSION = 1,
+
+ /**
+ * Failed to initialize (fatal).
+ */
+ MHS_FAILED = 2
+};
+
+
+/**
+ * Data for the request to get the /version of a auditor.
+ */
+struct VersionRequest;
+
+
+/**
+ * Handle to the auditor
+ */
+struct TALER_AUDITOR_Handle
+{
+ /**
+ * The context of this handle
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * The URL of the auditor (i.e. "http://auditor.taler.net/")
+ */
+ char *url;
+
+ /**
+ * Function to call with the auditor's certification data,
+ * NULL if this has already been done.
+ */
+ TALER_AUDITOR_VersionCallback version_cb;
+
+ /**
+ * Closure to pass to @e version_cb.
+ */
+ void *version_cb_cls;
+
+ /**
+ * Data for the request to get the /version of a auditor,
+ * NULL once we are past stage #MHS_INIT.
+ */
+ struct VersionRequest *vr;
+
+ /**
+ * Task for retrying /version request.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * /version data of the auditor, only valid if
+ * @e handshake_complete is past stage #MHS_VERSION.
+ */
+ struct TALER_AUDITOR_VersionInformation vi;
+
+ /**
+ * Retry /version frequency.
+ */
+ struct GNUNET_TIME_Relative retry_delay;
+
+ /**
+ * Stage of the auditor's initialization routines.
+ */
+ enum AuditorHandleState state;
+
+};
+
+
+/* ***************** Internal /version fetching ************* */
+
+/**
+ * Data for the request to get the /version of a auditor.
+ */
+struct VersionRequest
+{
+ /**
+ * The connection to auditor this request handle will use
+ */
+ struct TALER_AUDITOR_Handle *auditor;
+
+ /**
+ * The url for this handle
+ */
+ char *url;
+
+ /**
+ * Entry for this request with the `struct GNUNET_CURL_Context`.
+ */
+ struct GNUNET_CURL_Job *job;
+
+};
+
+
+/**
+ * Release memory occupied by a version request.
+ * Note that this does not cancel the request
+ * itself.
+ *
+ * @param vr request to free
+ */
+static void
+free_version_request (struct VersionRequest *vr)
+{
+ GNUNET_free (vr->url);
+ GNUNET_free (vr);
+}
+
+
+/**
+ * Free version data object.
+ *
+ * @param vi data to free (pointer itself excluded)
+ */
+static void
+free_version_info (struct TALER_AUDITOR_VersionInformation *vi)
+{
+ GNUNET_free_non_null (vi->version);
+ vi->version = NULL;
+}
+
+
+/**
+ * Decode the JSON in @a resp_obj from the /version response and store the data
+ * in the @a key_data.
+ *
+ * @param[in] resp_obj JSON object to parse
+ * @param check_sig #GNUNET_YES if we should check the signature
+ * @param[out] vi where to store the results we decoded
+ * @param[out] vc where to store version compatibility data
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error (malformed JSON)
+ */
+static int
+decode_version_json (const json_t *resp_obj,
+ int check_sig,
+ struct TALER_AUDITOR_VersionInformation *vi,
+ enum TALER_AUDITOR_VersionCompatibility *vc)
+{
+ unsigned int age;
+ unsigned int revision;
+ unsigned int current;
+ const char *ver;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("version",
+ &ver),
+ GNUNET_JSON_spec_fixed_auto ("master_public_key",
+ &vi->auditor_pub),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (JSON_OBJECT != json_typeof (resp_obj))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* check the version */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (resp_obj,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (3 != sscanf (vi->version,
+ "%u:%u:%u",
+ &current,
+ &revision,
+ &age))
+ {
+ GNUNET_break_op (0);
+ free_version_info (vi);
+ return GNUNET_SYSERR;
+ }
+ vi->version = GNUNET_strdup (ver);
+ *vc = TALER_AUDITOR_VC_MATCH;
+ if (TALER_PROTOCOL_CURRENT < current)
+ {
+ *vc |= TALER_AUDITOR_VC_NEWER;
+ if (TALER_PROTOCOL_CURRENT < current - age)
+ *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
+ }
+ if (TALER_PROTOCOL_CURRENT > current)
+ {
+ *vc |= TALER_AUDITOR_VC_OLDER;
+ if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current)
+ *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Initiate download of /version from the auditor.
+ *
+ * @param cls auditor where to download /version from
+ */
+static void
+request_version (void *cls);
+
+
+/**
+ * Callback used when downloading the reply to a /version request
+ * is complete.
+ *
+ * @param cls the `struct VersionRequest`
+ * @param response_code HTTP response code, 0 on error
+ * @param resp_obj parsed JSON result, NULL on error
+ */
+static void
+version_completed_cb (void *cls,
+ long response_code,
+ const void *gresp_obj)
+{
+ const json_t *resp_obj = gresp_obj;
+ struct VersionRequest *vr = cls;
+ struct TALER_AUDITOR_Handle *auditor = vr->auditor;
+ enum TALER_AUDITOR_VersionCompatibility vc;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received version from URL `%s' with status %ld.\n",
+ vr->url,
+ response_code);
+ vc = TALER_AUDITOR_VC_PROTOCOL_ERROR;
+ switch (response_code)
+ {
+ case 0:
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ free_version_request (vr);
+ auditor->vr = NULL;
+ GNUNET_assert (NULL == auditor->retry_task);
+ auditor->retry_delay = EXCHANGE_LIB_BACKOFF (auditor->retry_delay);
+ auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay,
+ &request_version,
+ auditor);
+ return;
+ case MHD_HTTP_OK:
+ if (NULL == resp_obj)
+ {
+ response_code = 0;
+ break;
+ }
+ if (GNUNET_OK !=
+ decode_version_json (resp_obj,
+ GNUNET_YES,
+ &auditor->vi,
+ &vc))
+ {
+ response_code = 0;
+ break;
+ }
+ auditor->retry_delay = GNUNET_TIME_UNIT_ZERO;
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ break;
+ }
+ if (MHD_HTTP_OK != response_code)
+ {
+ auditor->vr = NULL;
+ free_version_request (vr);
+ auditor->state = MHS_FAILED;
+ free_version_info (&auditor->vi);
+ /* notify application that we failed */
+ auditor->version_cb (auditor->version_cb_cls,
+ NULL,
+ vc);
+ return;
+ }
+
+ auditor->vr = NULL;
+ free_version_request (vr);
+ auditor->state = MHS_VERSION;
+ /* notify application about the key information */
+ auditor->version_cb (auditor->version_cb_cls,
+ &auditor->vi,
+ vc);
+}
+
+
+/* ********************* library internal API ********* */
+
+
+/**
+ * Get the context of a auditor.
+ *
+ * @param h the auditor handle to query
+ * @return ctx context to execute jobs in
+ */
+struct GNUNET_CURL_Context *
+MAH_handle_to_context (struct TALER_AUDITOR_Handle *h)
+{
+ return h->ctx;
+}
+
+
+/**
+ * Check if the handle is ready to process requests.
+ *
+ * @param h the auditor handle to query
+ * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
+ */
+int
+MAH_handle_is_ready (struct TALER_AUDITOR_Handle *h)
+{
+ return (MHS_VERSION == h->state) ? GNUNET_YES : GNUNET_NO;
+}
+
+
+/**
+ * Obtain the URL to use for an API request.
+ *
+ * @param h handle for the auditor
+ * @param path Taler API path (i.e. "/deposit-confirmation")
+ * @return the full URL to use with cURL
+ */
+char *
+MAH_path_to_url (struct TALER_AUDITOR_Handle *h,
+ const char *path)
+{
+ return MAH_path_to_url2 (h->url,
+ path);
+}
+
+
+/**
+ * Obtain the URL to use for an API request.
+ *
+ * @param base_url base URL of the auditor (i.e. "http://auditor/")
+ * @param path Taler API path (i.e. "/deposit-confirmation")
+ * @return the full URL to use with cURL
+ */
+char *
+MAH_path_to_url2 (const char *base_url,
+ const char *path)
+{
+ char *url;
+
+ if ( ('/' == path[0]) &&
+ (0 < strlen (base_url)) &&
+ ('/' == base_url[strlen (base_url) - 1]) )
+ path++; /* avoid generating URL with "//" from concat */
+ GNUNET_asprintf (&url,
+ "%s%s",
+ base_url,
+ path);
+ return url;
+}
+
+
+/* ********************* public API ******************* */
+
+
+/**
+ * Initialise a connection to the auditor. Will connect to the
+ * auditor and obtain information about the auditor's master public
+ * key and the auditor's auditor. The respective information will
+ * be passed to the @a version_cb once available, and all future
+ * interactions with the auditor will be checked to be signed
+ * (where appropriate) by the respective master key.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the auditor
+ * @param version_cb function to call with the auditor's version information
+ * @param version_cb_cls closure for @a version_cb
+ * @return the auditor handle; NULL upon error
+ */
+struct TALER_AUDITOR_Handle *
+TALER_AUDITOR_connect (struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ TALER_AUDITOR_VersionCallback version_cb,
+ void *version_cb_cls)
+{
+ struct TALER_AUDITOR_Handle *auditor;
+
+ auditor = GNUNET_new (struct TALER_AUDITOR_Handle);
+ auditor->ctx = ctx;
+ auditor->url = GNUNET_strdup (url);
+ auditor->version_cb = version_cb;
+ auditor->version_cb_cls = version_cb_cls;
+ auditor->retry_task = GNUNET_SCHEDULER_add_now (&request_version,
+ auditor);
+ return auditor;
+}
+
+
+/**
+ * Initiate download of /version from the auditor.
+ *
+ * @param cls auditor where to download /version from
+ */
+static void
+request_version (void *cls)
+{
+ struct TALER_AUDITOR_Handle *auditor = cls;
+ struct VersionRequest *vr;
+ CURL *eh;
+
+ auditor->retry_task = NULL;
+ GNUNET_assert (NULL == auditor->vr);
+ vr = GNUNET_new (struct VersionRequest);
+ vr->auditor = auditor;
+ vr->url = MAH_path_to_url (auditor,
+ "/version");
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting version with URL `%s'.\n",
+ vr->url);
+ eh = TAL_curl_easy_get (vr->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT,
+ (long) 300));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ vr));
+ vr->job = GNUNET_CURL_job_add (auditor->ctx,
+ eh,
+ GNUNET_NO,
+ &version_completed_cb,
+ vr);
+ auditor->vr = vr;
+}
+
+
+/**
+ * Disconnect from the auditor
+ *
+ * @param auditor the auditor handle
+ */
+void
+TALER_AUDITOR_disconnect (struct TALER_AUDITOR_Handle *auditor)
+{
+ if (NULL != auditor->vr)
+ {
+ GNUNET_CURL_job_cancel (auditor->vr->job);
+ free_version_request (auditor->vr);
+ auditor->vr = NULL;
+ }
+ free_version_info (&auditor->vi);
+ if (NULL != auditor->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (auditor->retry_task);
+ auditor->retry_task = NULL;
+ }
+ GNUNET_free (auditor->url);
+ GNUNET_free (auditor);
+}
+
+
+/* end of auditor_api_handle.c */
diff --git a/src/lib/auditor_api_handle.h b/src/lib/auditor_api_handle.h
new file mode 100644
index 00000000..c3c73f5c
--- /dev/null
+++ b/src/lib/auditor_api_handle.h
@@ -0,0 +1,71 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file auditor-lib/auditor_api_handle.h
+ * @brief Internal interface to the handle part of the auditor's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_auditor_service.h"
+
+
+/**
+ * Get the context of a auditor.
+ *
+ * @param h the auditor handle to query
+ * @return ctx context to execute jobs in
+ */
+struct GNUNET_CURL_Context *
+MAH_handle_to_context (struct TALER_AUDITOR_Handle *h);
+
+
+/**
+ * Check if the handle is ready to process requests.
+ *
+ * @param h the auditor handle to query
+ * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
+ */
+int
+MAH_handle_is_ready (struct TALER_AUDITOR_Handle *h);
+
+
+/**
+ * Obtain the URL to use for an API request.
+ *
+ * @param h the auditor handle to query
+ * @param path Taler API path (i.e. "/deposit-confirmation")
+ * @return the full URL to use with cURL
+ */
+char *
+MAH_path_to_url (struct TALER_AUDITOR_Handle *h,
+ const char *path);
+
+
+/**
+ * Obtain the URL to use for an API request.
+ *
+ * @param base_url base URL of the auditor (i.e. "http://auditor/")
+ * @param path Taler API path (i.e. "/deposit-confirmation")
+ * @return the full URL to use with cURL
+ */
+char *
+MAH_path_to_url2 (const char *base_url,
+ const char *path);
+
+
+/* end of auditor_api_handle.h */
diff --git a/src/lib/backoff.h b/src/lib/backoff.h
new file mode 100644
index 00000000..2af41e7c
--- /dev/null
+++ b/src/lib/backoff.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/backoff.h
+ * @brief backoff computation for the exchange lib
+ * @author Florian Dold
+ */
+
+
+#ifndef _TALER_BACKOFF_H
+#define _TALER_BACKOFF_H
+
+#include "platform.h"
+#include <gnunet/gnunet_time_lib.h>
+
+/**
+ * Random exponential backoff used in the exchange lib.
+ */
+#define EXCHANGE_LIB_BACKOFF(r) GNUNET_TIME_randomized_backoff ( \
+ (r), \
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 2));
+
+#endif
diff --git a/src/lib/baseline/admin_add_incoming.req b/src/lib/baseline/admin_add_incoming.req
new file mode 100644
index 00000000..677678b5
--- /dev/null
+++ b/src/lib/baseline/admin_add_incoming.req
@@ -0,0 +1,7 @@
+POST /admin/add/incoming HTTP/1.1
+Host: localhost:8081
+Accept: */*
+Content-Type: application/json
+Content-Length: 220
+
+{"reserve_pub":"TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0","amount":{"currency":"EUR","value":5,"fraction":10000},"execution_date":"/Date(1442821651)/","wire":{"type":"TEST","bank":"source bank","account":42}} \ No newline at end of file
diff --git a/src/lib/baseline/deposit.req b/src/lib/baseline/deposit.req
new file mode 100644
index 00000000..a400796f
--- /dev/null
+++ b/src/lib/baseline/deposit.req
@@ -0,0 +1,8 @@
+POST /deposit HTTP/1.1
+Host: localhost:8081
+Accept: */*
+Content-Type: application/json
+Content-Length: 1658
+Expect: 100-continue
+
+{"ub_sig":"51SPJSSDESGPR80A40M74WV140520818ECG26E9M8S0M6CSH6X334GSN8RW30D9G8MT46CA660W34GSG6MT4AD9K8GT3ECSH6MVK0E2374V38H1M8MR4CDJ66MWK4E1S6MR3GCT28CV32H1Q8N23GCHG70S36C1K8MS3GCSN8RV36D9S710KGD9K6GWKEGJ28GRM4CJ56X1K6DJ18D2KGHA46D13GDA66GVK4GHJ8N13AE9J8RVK6GT184S48E1K6X336G9Q8N142CJ4692M6EA16GRKJD9N6523ADA36X13GG9G70TK6DHN68R36CT18GR4CDSJ6CW3GCT364W46CSR8RV42GJ474SMADSH851K4H9Q8GS42CHS8RV3GCSJ64V46DSN8RSM6HHN6N246D9S6934AH9P6X23JGSH652K0DJ5612KJGA26N242CH35452081918G2J2G0","timestamp":"/Date(1442821652)/","f":{"currency":"EUR","value":5,"fraction":0},"wire":{"type":"TEST","bank":"dest bank","account":42},"coin_pub":"JXWK4NS0H2W4V4BETQ90CCEDADP6QQ3MV3YZ7RV2KXEM8PWXE8Q0","H_wire":"YQED9FDYPKK2QQYB3FS19Y15ZMKBAXJP2C73CXASAF1KM6ZYY723TEJ3HBR6D864A7X5W58G92QJ0A9PFMZNB81ZP9NJAQQCCABM4RG","h_contract_terms":"1CMEEFQ5S4QJGGAMVYFV07XQRHQA311CR2MTRNC5M9KZV6ETDV1SY00WJFEV2CG9BXQTEQPZAF8A54C2HX32TZCN20VBGPFPS2Z16B0","merchant_pub":"C36TEXQXFW00170C2EJ66ZR0000CX9VPZNZG00109NX020000000","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","refund_deadline":"/Date(0)/","coin_sig":"X16E0DP8C2BJNVNX09G24FFC5GA4W7RN2YXZP9WJTAN9BY6B4GMA39QNYR51XNNEZ3H1J7TP0K9G55JZ8V7WS7CZMD7E64HWYBFWM00"}
diff --git a/src/lib/baseline/keys.req b/src/lib/baseline/keys.req
new file mode 100644
index 00000000..a9503a86
--- /dev/null
+++ b/src/lib/baseline/keys.req
@@ -0,0 +1,7 @@
+GET /keys HTTP/1.1
+User-Agent: Wget/1.16.3 (linux-gnu)
+Accept: */*
+Accept-Encoding: identity
+Host: 127.0.0.1:8081
+Connection: Keep-Alive
+
diff --git a/src/lib/baseline/refresh_link.req b/src/lib/baseline/refresh_link.req
new file mode 100644
index 00000000..acf3dff5
--- /dev/null
+++ b/src/lib/baseline/refresh_link.req
@@ -0,0 +1,5 @@
+GET /refresh/link?coin_pub=WQHES0X5XK43VBG1Y8FXR2YEJM04HQVMDTCS07MH691XWADG8QCG HTTP/1.1
+Host: localhost:8081
+Accept: */*
+Content-Type: application/json
+
diff --git a/src/lib/baseline/refresh_melt.req b/src/lib/baseline/refresh_melt.req
new file mode 100644
index 00000000..98b5b638
--- /dev/null
+++ b/src/lib/baseline/refresh_melt.req
@@ -0,0 +1,8 @@
+POST /refresh/melt HTTP/1.1
+Host: localhost:8081
+Accept: */*
+Content-Type: application/json
+Content-Length: 34136
+Expect: 100-continue
+
+{"new_denoms":["51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0"],"transfer_pubs":[["J65E5480S6ZVPBA5HV9D4APYXFS527DAJGN5HKZ5EWP89EFSKGK0"],["TYZZG5HSXTYJMA9XD3EMTX2S36V7F4VPR1RHQPKJSTWXJD2KPDAG"],["2MN2X2X7P6GJCN09Z6ZDF2R9W1W3BSYJER7FTBSDDSWMRJ7DG0DG"]],"melt_coins":[{"value_with_fee":{"currency":"EUR","value":4,"fraction":0},"coin_pub":"WQHES0X5XK43VBG1Y8FXR2YEJM04HQVMDTCS07MH691XWADG8QCG","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","denom_sig":"51SPJSSDESGPR80A40M74WV140520818ECG26DJ384R4CCHP8RTK6DJ46X344C9M6S1KEDSQ8923EE9P84SKAH9R84SMAGT670SM4GT66123EDJ46RVK0E1N65336H226H2K8CA38CVKCH9N711K2H1S6X134GHJ6924CGJ68RRMAH9P6MT30H23651KCD1N6X0M8GA58CS34E9K70V32E1S8MTK8GJ270WK0HA368V3GDHM68RK2CHS610MAH1H8RR42CT58H342DSG8D1MCC1J6N0KEHHS6WVM2GHS60WM8G9G6GTMAC258D338HHP8MWM2GSS8MW3GCA270TK6G9G6WRM2GA56RSMCC9R88T3JEA270SK0HJ284R3CC9Q6WVK0DT570TKJE9P68SK0CSG88T3CCHJ60R48C9N611KAD9N6WVMAC9N70T46GH35452081918G2J2G0","confirm_sig":"ZKYCWKVTVQCWMWTAQRNRSKGQP6AC14VF7W525ZQYFZ6J2T1GM7PE7945W3HNW32S0MZBX8K65VQPXH83NMQR2SGTY7DX2T60RKHFP00"}],"secret_encs":[["DCZPFTHF1GZRXFVSYJ4AK1HH4QE46T8F60X1AQ6T6BF1WHBNTJ3FEGH7Y1TH5A6YT10GFYGV12S8V9MB4HEKHRYBBMR7JJ9KB264TNR"],["ANBQS74BACGAWEC0F0654HYGZB3NBSNXVD4KZY4TRP4JMRADJE2Y5BYV513Y37PPMR6V5P200FEAHTZ5TS3R9G9MBYM6Z144N853PQG"],["1X6P2JR301MPR5JYMKPV1HXAFXKH9M09HWVQ4PS6JNK1K6E8ZSMW1C6NS79CPFP443QBVEW9SRWXXJQT91EHFKJ8007AB4S64SPWN3G"]],"coin_evs":[["1XB4A97X8ATPH1KG5NAMJGJY258AGK0MKVJRHN4NGKPRD17DQM0CMTT351FB7T56NTVA9E5VTH9Q12A10C3YVDEYJB4B66EC1NPXNPMNSVTBJ7HGM4DXFB03K1BXK247QK581CGE54286BTK6B5VW0FTS4AZ3NQP43V9E6VV1HTKG1CM5WQJEY14TAC8P2YSV2XH61RG4E84R","JVNGX2AR684RF4VRYFSS69WNMJBDDS9Y4AXFRX5JRFES5QBTMM51SB84BN61S5W0R7PZGK09NQ8QMX7Z90AE0JJH76J7DCM94DKWMV88K966NEA2CNMS50PG6D0K73619K67S9HJ0PQTQH8YD91P4CQQZTRPV7Y2RHEGMBB2G67ZBVVH1S9Q0HYX69Y2B3J2WYFMH3BJESVGE","6MZ6ZDF26MSAZN3VK3QDY62MD3AXZTK8K46A7ZFNBVCR33AE6H3A309F822EKPHT2YTJB6PMSPSJ3ZAE99Z5Q2J8F87X5N723J713A0QR87KYJ2NZ3PFCS5CC8FXJYK6YXZ4ND6FHNJRJZZZZRXE05VP14XW1BZFYZGQQZ8J05FYKY8AYFNCZ50WT7W8M83XS50GTYJGQTNPT","Q0JWR4WRPYT7TCNCV47RJ1FW3HEFQABN9EE27DBMDB4FYAVK8X9KM38HH9KN8QW28RXFXAKT3MGHXPGRWCY9P313CPR5Z2XSR3ZGKFHQHTP1A02SSD0DHH12RCV5TJ7M86WXWT7F00CQ8HV5H90MFEDX6J2X9JNX26NC0PRH0YXHGW7XVWXF00ZEJCGJDEZ1EVXA5BNW3SAEY","B20PSGCM77EVSXEC6T41N1T55KX76B5K3X38VBQ8NSAN2BKNH0W4DQGVZQTBA2588YCRW1TN9G721GP7ZCP9H32BBC9HM3XFTNJ4QBV2FMM7KSG6T0H418EBNBGKZXMTD9ZDVV6A2TEBMXSEKYVK9HCZT8EEQ7E44DWBDHN274N9D4ZDEFCZXNF0MKBM3TYZGHJ5T71T88CE4","F95WT8Y2WRXGK07MY3FMV1Y6PS44CMP59KK8G82EKW3Z0Y6C6QZ2CVC4A6DF9N3E6HW8SQ3CF4FSD1QC2DN5E9DM5G762DW491BAE5RC2A3YPGJTZ3XBFJ3R3NSRDRZMHR6Y755QDRZZEVFTEPN7MZ90SE6KTQS80CHNMTX15NMZPNFJSCQSEJ9RQJKZBH9ZZFMNXT8BXM7K4","1B8GKDJYJYCZ8Y2HCETV6EWA8FRVR0AAD7TRXFXBHNF7G10CZYDFTKBJ8X0J0M7VR1KQV67SF5VMM7VVG5ZQ4F9QEKRV3GSBB4M75CAXSEXSHDT36RJ6A98EKP3EZXT65HX2K788ZE4SVE5N2NJ8Q26P318PK9X69YWB4W7R1F1F9WHZ8WKJ69G5YZ1T9WR1N8CXTJASXW3A8","FV0Y6B21F01D5EH31R80BHG7GKA08C7HN3FYDMGQE6HJ2Y9ZSVJ17HR8Y22WGPN98T08E6XP4PZNMJ3RHAFK8DAVW3AYM5VNPHV3JSB5B64F0H60EMQNA59A2QAFRV3FCHJQATY87SGT9E67HRVE5QDP9HJC7A351WM55VV5MW3T251VVZ8N5V2GVX6MD14MR9ZX9TBS4N30J","K8QSPQTEV9YZC7MC6AT9R8Y0QGG4PT1V45KSSZR2YY9SBA3QEQG7R6YR4RKEKR5DGDM2K4DN9K128TQMMPFWZR8N95F29Q8E2WBD22F5SRFPJ2KJA6SEZ6RMPCHG6C4BJEV7PZAEVTNJDK8T79PJCB5B97D5WQ52TT2DB6Q4S599D61AA54FCVM9R3AB03PDTYZ0PCKBMJ23G","1655VA0GT3ASG2JVEZ93BWE01YDEA70ZW2FD1W3AZTD4B4WK9GYPMM9Y6KXDGDDB2TMDF5CWXNAX0B7E396E1NH3GCK8KFHMGXNPG0CTD2KNW9X3QB4HKK68JM2WGMF05VJ6K4G76R4A10JWPEZTD554TSFYVEM0355C9E5P9W5GFFFYQMTAXG43V8GP7P4BDBD81Q9G87MXG","J3T1MSDB9ZHCCNAYDXEQAMETNSCX5DJYQBCXGREMYAWAQ8AJCY5JV00HHX31DVT7X5S25CEX88707E81X4KNCN7RBRFMFNP8QZHFZHXNS6MN7BD4MGRVQEC50EWH6N6EFY1GA039KYJKX24XMGZXKB27H5R0RKN7YC00EKN99SGKF3YN7DPR8880MG457438G1YJR7BGRCG7J","DX73NQ14FVC978B1HP03MWBD83YT6NVP607WB126RAJPKPB11GSWB7H9W0QJSH9WD4SN9JJE6FDMQZTMJXCN1AKDBKKVPWW9VBEAE0YVCG64S16TXMSF5FTWNXYG7RDTHK03BXDGVXSPZFE7A2F6FPK9WCBBZSMKB8EJFGJK1PKKBHVA1Q2MX91NK32XPF00WFWZCC3N72JY2","PKSMQXMCT9H53FHGKVVPPTTHJ0M5X4SSR0XARN34VV7DX9ZA9N8379NVQKN4E5V6JXAPEVGPREWD720RZCVB90DDHRFW8GG6G434GR1A77SVPG9GKP2DEKBVP3CZ9WDMQD9RFZZ2Z3ZQ34YVD2J1Y4AZ52ADY5FXPB06W9NQ9NH7AQVHTPMSTP181MZM507M777SAPSCR9J8M","1XV578XYBQYYSV9V8ZRACRG3JTN103BZFPF5JXHF8YDCCCJM9XS343JYM8N6Q3J8D0NF2T8TPZKCB32MC1GXWZZRRWQW77JSSYXGA8136DNFYTCKB8EP277KBXAYXBW0ZX7S13J5NDGR7ZJ7QTAAPTEAG9MX6QBT9Z7ZZ1MECV7Z0D7GE7Z6KWASE5T4SW0MFHKVD1EAFNG0P","86PDN3KMT8AW8TVXEBGG3199YSEDMQX91QW82QVZ9TPYCNS3YKK3NCK5J64VKPKGJGQJP8E3RX97NZ4RDQ0PR21QS93MTWXSHKCTK2KTH7TQPKVQSWPGVC8CZDEGD9X484HJKMQG56RP1MZE7AWKK9C5ZK7MSN21GTG1KNF591Q8DVX0TM06BD8VGF6QNC745CFRMRSYHXK7P","KGXXZAECQ6DETR97HRYKFC4A1WAKC3S0FYNHS16J342WMPPP78NHWZXSEPN04XEQY6QNR9ECWZNXY6BZHXWAW2EV42295GBVA4R2J2KAK9EF7MR2789EHY0MVQJME1ADGEPGCQ02WHWT3C173SPDKW1DA5C8HBF847KV00A3F3ZM2HE18N5MFFQXBB014HK0MC4NW6G19AESR","92G786WTFN4BP3Z2RX1Q79ZPHGMPYC21K646XA8AB97V103DSJY0S4Y1FHHSHYQ8WZPWF0W07VY6CDG66438K8E8DWN7CCSB6R8KSC2VNAQBVC1ZD2H9VG33309B6QEKK5PYAGJZN5NG0CJXW9RFGPCNJJZW6KHM4TCMDJVKV9P953Q0TWVM2YZWK37504QJ41MZ4KXN52M30"],["NG0RYVSNPXP4B2NEYCTVC5A0EW5PFFW8V3PH9X5FFQ2VHCETMWDPZ75HBSC8QDBCWZ6EV9SEN1SMEMXABK3YTXBDDXYTN0JNKX1ME9T71JJTZZQ7XA1890DZR3ZAMD0XW8GZENDNAP4R5B80DQYFDEENSB141DZ0HG3A3FZA7JMCSBY0Y0PB2AXNS25Q6CQ5WA7GWCWA09PD2","R70NVRHFT6HCP69K8QQM9HQX0CZQYG09VV2VA7RKCVFDRTNH44XZHRBEJTBW1ASDS7WYA47PBVPB9T77B32ER6V2745Y8B1P22CZ5T70WTE6Y4HSS60CM5SFB6QT1VXTVW9G0DAC3XY2QDX6TKKQA1HX3RB0GPJH47D08KB1Y4EP1SXW851CFNEE373YJHBNYXMSE3MJBCQFP","JGZK4NS2E8D1CTR8ZAR5CHGN7T09Q41P545ZNNB7RMYV0CZ9CQWJWZZWRQZDRDCHV5PQS9G6WZB37CHQMZ7Y207D23FQAPMN6CP30CXYEQ449R3V7B8BYGJ84VYW60201B4343GG75TTTN947VVEYA3BWKRY4M5XCXSPAGW1TTAH9VAE8CBXBKAKJESX8XWS4W5PYBG8793TR","CEVHPNNSWR438ZWEK27YRYBKZXVBT8AM9QP2KXWC59ZSM8Q7J0GZNXZYBES04RB7BJY4D5YCPT3VRT7EPK0576809CG98X5SPTQPSR4TZ0R6DWMXH9893FCTVJX2A4H2EGSGP3Q6H53FD4CSDBMZFZVACXPMW1CR07A1GG7XFXSK1BP1BTGMBQD0818G6BC6SBFTXS1Z68PD8","72BFMSPJ57Q1B88JS6NX8YETQP0GSSRHXM25ZBGNW06YGYJ7FT1D7KVGF4PRKKQK8KN4MGHK5DXRQDTNEJSRZYGAN6DV5QAW9PACT7FQQXG6G4JS7J129VXFQP3DYTV0455Q37H5YPVGMWXZ4GNBQJ86HWWD670XY8JAR4WTYHX8FWJXZW57Q94KK79SD51HBJH1SJ4Q5C04W","2MDED6PYFV264D0SETN9MD4F0SADGVD75J8Y16DTSABNPW09AKE7A5F4QNB2124WDQ0HW4SGHCX26NAG46Q960Y01VYSX0GQHVKESSMEAZ2426GJ8KR0E6KTAXDZY6HZ6DRSPXXGZ07YZT2XF696CP8215HE92RGT7A50H5XXV2FDGFWJXYNCB7QND27ZWYWTK0KTCRYT104Y","267QZPCB0QGC63V9E75Y41J5285Q3FX6KQHM08MAKZ248X9WJK5H6H903C0T02W4G3EA22E0C1MHCWP5VRAEC8D0J0VYW9JNCH9YGPQJ3P941AADABVXG4M2K724C8VCFZQNXYJAMY4Q65WYHW6VYPN9H1K7ZABPAZX82SSSBPYKJZBREJ15MSS50R9HJ6FWW7T9P8ES015PY","7W8HPWAGBVFK0KTWG02RRTWKAE4W0PJH76GH9JWD7H48BS7CVD1T38QBX7HJVC6PJEAJ5NGP7MSJ9D5H1FD2T9Q5X4YQMQ1F80H4G8GRGXFKS863G48HN15D72EZSM9V8M75FXCH3639PHENNV35GA6TWPRS70HEJSBAX9A52DWTB63ZCSZXFGACZEQFGR0EKEE0AZB3HKE2J","JMGGS2DPDKNQKTM593M69Z1R331654KR6N12Q7EZPX20BMHV1P9JZZ8HNN6VHQ76YKWNMW9HR3173C2J66R8EVVWZAR001Z18F9HJ9YBVSM1Y94PJSM57GNVRNN0GH11A3ZRCA4WBKFC64A4RE6KTYMMJKJNHTR9A75QC7JWG5YY9D0J8TM9KXQW8495V702VS0E40121HNHY","REH6B17HSBJGMMPYKG7EA1J4G1D7H95N20P647AWHJKY3VA5BDZKN84FEG0G0FK6TKNN8D0TDFQ41610SK9CBR27SFD16PKEZ7AP97NB9ENPQRQ9PEKR76KB6VFWP666MJBTQJ5R7WGKTTE3ZN5Z2VWY2CPB437746CYBN52G2P2Q7V6SDGXV5HPYJBKXESS1JMVH3EHDC988","MRE9Q877Z18VMFNH1PWNH9N3Z5YE2SDZCVVFBZAA41SSRC2F81D6F9TCZP2AGYAEXWM3977KWJ6SV215ZJ24RAWA94NCKEFQ1CMW8MYJ063EP68NVVPH51SFVYQVHYC45MGNEXK61R9KFK6RWYV5Y6CHMAYZTW5M17WBSHBZ79F4DSWHVVY7774FX3WEEVM8EZ0WMJWY99A1P","2CM59NJVJ68NYX5ZJM2ZX7JK8EW7JXAVE0H2D1ZTX9X6X72P9768WK5GG3ED7MGEYGFQBK5YSSJ0DM68DRPDPTAHQGWTGJ7E79GAW96BXHYV64BQXXEV9YQ0XR84G8VZWQ8Z7T9VX7VQ0HHX1SAS3NGNZAH1H03KPFNAZMNMGV3H4Q3PC96JHGAP5JNDWA1FAFJ410REB33D0","2KEXQVJHACQX7V1XGF8PA6DV0EVAKFNSA037YJ3BP91NNQB60ZBAEFT9DKVW2M19R51B8HGQ2RCBSWBRHD0RFC95TYGJVKNJSCMQPXV7SM6A1JH18DFPWB1AJBS4SSVJXZ9V72TY2P8Q8ZTBMGCYERXCT4QJMASTQM1T22C9AXMMPZ1BHKNB2FBJ3TY9AQP4ZGHRSRBKR75E4","5Q9ZFEFF6C6XPGM78EXAWJF2CKJVJ0SPA113XT7R5JTRW7MKE70TETKK1MBRSTF566AEZB2CBX6CR10J6DPAQ92GC6XYK27A52KQGQHR5W346RGF28WE2PPC8S3XWTGCGVP318AKKTC8GZQ62FRYZVPTVF07HWYHDESQFKYNSDRNV8Q5KPGRQM27SK3CX14RA2K9KZ0NJJ0FE","7ZK7H90WBS080M9TH8XH9DNENKQ78YHYSXWF16VRM76YDKP4RG97DEM1DBX84VXH98M64CJS1GYTDKG4XA8XCNSZ4KQ12GAXCJC168DBR8Q4M29X2Q70WNR7BZDNH0DQJMR154M0C1NH2EHJBKA76PNZT2369PT9Q4961EBCY5FE85EKCX7584GXBMHQF1GRDVCCDVY7TH210","CFSSPFE27VKZHPS7D48AAHE0C2XKXS774CDPFE530P5C4EFZVZEP1PTS3NYB7KBNHNAGMZX8YZJVTNJAB1DE7GCRG43Q2JVS15RGJC4K9PDA0Q2MG4DGRX1CM28HE8CB1XWVW7MCDJHPNE28NWN0CYHDES8GHAZ6Y50NNZRN31FV8C28V0PRNZK4WFAPJ4EGV5NQPHGSZAWNG","9XJB29QY06J1DYBXQRV9F7M3T11JTGSZHX8D0Y80Q07M646VTHNFKNA19CG84BA2DKRE1D5DS3M72QH29S9EZEADW6PCS1FXGCMGZ0RQXT5ASSX7VBQ40NGPYY6PFE6CYV45YYEGPGJ2DCQCNYP2Q9E78SBD53QTQVYZG7W3QE8EASEPSZXP1VVB1TYYHN0B1BZGY2JKGWWPY"],["CSATAT8AQRB91XKHJD1B7MK1DPVPV991WZM4Y2NF0G8SNQSR391KBDGHR0AWGZ8V6EYXKNGTACT9GRBWWAPYTDPWFZ5QPY9GY8Z4YDXRK1QW30YT6JMAYQM2TH36XP81G2X55785ZJWCRXZ9QQDGGMMM8QZTWN9TPW8JXH4XM8A0DPG5354PF3782GVC8J8DXD7KY88KT0FZY","4MG8W9X1M5361HRRG6W9QF13EQDRFWVAQGMWJKEESZ3FBCWP7QQGQBBDT3ZYE3XARBVGJAVFQRQPAYA1PC4CQYRX0CZTS0E4QZB3ACMXG9XJ91VPP7X0ZG08WXWMT02M9V4HCHJ7MJQZ7EWCRJAG7E14BQ465R123P6QFGY770GN0BBNT040S3XPC88CD8X3D8497DE2YA1MT","DCSBAHV955VGACX5WE2N02QZSPQ9N375DCS0W3298N8KF59YHVTS7FYJ1NE011VWF8ET7F6SQZHW1NWJZW3XYWN2ZMWXCRGH80CD6N8YA6EE8Z2N2PW4T65M0DDX4YFXZPG61E7JJGK03VTDW5J8WS2F2R88GNNJMWBQCYBWWTZ34H7J5XAPBBY3AF4M6A8C0RAHPX0FS044W","5CKW4V17Q2XAV54F07X0C2NZCRZ21B9M79JM37F10DZJ3YMY9WPEWSYEDC6DP1QZ2DRRAXDCFG1WZH3C0Z58Y26XGSC28R9Q3ZFSKZHS0F0DC08JWY01W9GPECCN0CP0Q19N7GWX7HAFVR0PR89P0DTBPJ02NKCG14HZK933C6C62AFS8GFWNWN7FJ042YVSH69BAM9FQX4JT","G3J5ZS034N9TKESCEBQJQVXJMYB7HX48QT7QTEZA688019KSSCFVDG2TD963JA9NQF2G80Q3J1853PRB8QD2WFWDSMPP9X4FXKAEH79Q2X8K2KZJ120848KY421H81WAA1AMPZ6APD67RDRBKM0H5GN974TBQ8S5ENFRJSNTJRJEDY57GRHAN8V2R2PCNCY1VWH92EN1MJA4P","BFTC88T9TMD6EQPMBN6M7ZES14AGZMK8CYF0BQ7JAPPAPKXRTW83Q2ZF5270PG9WN0SET02R2HHFEM4J66GDWK2GYCF6GBZWF0GAH9ZC0AHVT3JW2XWDZ2X1DH2WB9Y10A7QB83S3E1S5DQEJXD6AS4J6HCBJBZJPC1EJZP3E5R5ANY1EYZ7P1692WSNABA0KWZS5T05DK51M","HFBFB0NVHM82461FBYT7SKWR1R38QD6MPMTSZXQAAH7CKVSKE21M4KZMWE3EF2C0FFJ6QGZPFWYXM58NANQMHP293WNJD5NYM31ZGV3ZS8SDP9DK1ZVSF5WXW3PEKYCAR3C7J6QZKAT1CVQF86P6M3S7XPVN8EV212SG4ENANH650EF7H5RDBN29PRREWB5JA4PSX9TQR1Y92","1JT0M6407RNBHQFZ0SKEPHQW2ZTZTQ5X7BZ1Q1FBQVR9QBH3EP20CH1KJSP71PC8Z4WFKJ97442BWG991RJC0XE7Y71PW9SXMPX4MG1SVN371ZG3GNCBX3Z9AZAAR9PGHS46AVG1RH71X1WQZ7W9HGQPRYDPCMQBQ7N2PD0AH8EJMT2ASXKPNK09S9H9B0ZTYMB5KK2VB7C72","E5PWK8V063470P271354CCZ3JS5H6GB8GWPDCDB125CVT4YHR3MGTYAFTDJP38F2WSNERY5KNE9VK85N70WC6VZ22WF7MABK2FJZM3179NHBV92FQC19BR21002AZHHE4A171QYFX69NXV3XA90HY27ECQ28YCPZNKEPQXXVCNHVSFJJE8553M68KM81EW45GXEV3797W2KX4","JQGN8RXXYMHG8MD2GSNQ6NQ8QCH8A6WDT0S0WY4ZP5424RGY975300CQC1C575K619FY2JN71RQ62RM6W2TBVZX0Z5B8A2PQ2MTN6W486AHBJKX6ST9HA05EQ9VMJN3PGDSF1GACENFK8CA1EW4KNG185PG6CP7YY1TNX3FZ43H020K8SYTQWMPM6FAZ5N4MA6CQND1ZJ6AC0","NJ5PM2Z4VNYKG6X93KWH0GQZNMBKHYM2FWX0BSNPGJ18Q7R32YC9FPC2Z45MPJ5EZY4E3FD0CD67TB9K4879SQ33SCG4Z9BARNQVWXMK975GRGAB4306D1W7PF1QD1MTVW79DHY3M2VVC01R4T41M3TXYTD1X3JF1PBGC4T63MF9MJS4KZTS03Z84AY6C9WK9RTNHXN7R0VJC","1ERPPXVG2NJ4RHCD8QED1E25R6QCAKKZWRV3EAVS4Y17ZDTXK46TSBJ1MF4HN26K3FHRE3PVBV4DA1RJ36R70BSAHSW7KTEGJNR8M5FBJ7ZDT907X2H0RMPCDANGGT9Q8VWYKBAHQ1T72S0ZAEC079SKV16XWTRX698P3P2TCJQDT7EA0G67N82YFPRE7YVGFKTMYXYC7EH1E","91CHR983Z222JN1695J11YY836RHP6J02SSZ8T5ACZ15X0D1YAW8VTCMECW3CPMCHG2RWTS5C30HSRWTYHAE3J3F61Q5N34ZSBVFPWJMN8WSFTAFEG7WZDWCM1VJZX5KZGXF7RTCS09MHJ0WRMCGC18WSR2SDV64P9HE1WTNCD8KT65JGQ3ZPWAWVJNJS63YNTGDPZ4RHTZ78","8Y2GMEETX3N9XTAB124JYKMHQH29009WJ5KF6V31WWZ99GXWZJHC5C03P3CKMB4R8M7ARQ3XD7AHPJTY2EKKPPF9QXWN7685HDTPT2SBCQ67XHHRA2RJ5E1S4VFP2YB1H0W1D9RE4C6WDT7FNVWXFHXAEQAJRJ577QNJD8G4ZSARSJ2SP6625AW9VQVVRFS0FWMBWJZNHXGWE","33MH8RQ5PXQFB0Q2T0H30VM9G4J0ZQHB3JHH8MWF5K3F3KDBAR20ZG4APHCTF1XRG530W7K2476P7BNFKENP2WXJ4HAW6RS91EQKGYD6PEHRYCVF8JJTMWR32WFVPV5Z2M1FWKNNC5N4CC3MK7S2K1V05PQMZ347KYD45FW7Y4KKJJ89TDPT7SFSQP1A0J00SN3MMTSEGXMVC","HR05CWNNQ5VBAF0WNHP8DZGN3S0DGYJ37EYP8S57MK8KDXQHXTPV2SXNJ2M5SZMS43BR168FJTB1SC9EW9P9YT7DYRBRM7G0VSD86P4QBG42NH06XYCCF7TVN9H9EB5K1G07M2Y3TT4WBAZ2Z350PV67ZANG43054GZA4N08BN2WEDFN94BNFKF45C10C3FFG77D9P3PDMGVG","JS43E6R3YEVTRZGRTS3RF0F9PCW7NBTWB6XEV146AXNTSKEKCDQ8CX76GX6HA06N0RTA699M99FXMD4JX04VY961YXJ18G96Z1AEEJ5BR5A8GYEWSNJSR5Y55ETBCKHBHR6CJ8A3659Y45FNG7H4C1K44WZ520PE4NAQVJ9QS1H2RWQ5ZZ8H8J8WGBDQC6DQYRTQHR0WF2156"]],"link_encs":[["7A1HVQDZ7C2M4K913020KMYGK4K86PM30APZ8AH5ERT5Z83QZPJRTGEFXF0TGYH36R1N36N3VKB5V845FK33ZW3R4G01X6CZNE37XHXE707T0TA4DYGYNX295QSVF5VEPD8QMK1J7ZAGDS27QP9H1QVK3NRBTZVRZ23XAV642CGEFS1ZMPWEDSHWECQ4G6CFK1V0K2118BQYS41R9P05NZ18PY3Z1FWNZH472HTKWT74KC752S0W1C2ASM428009","VN0WT37PVBAEYW3GH2ZGZ6X37R0XATMMGHJ86G953NVD6TE608Z3A6P9PG9XAFAGPQ13GSQ435C8763NR5T28RQHH4JB9N578TW7FHA5Z4Z6MDS6CMFK9A6ZGGT35G28NH6YG55EZR6GCM5E2ZKA8A9TR8Z8C6N4BTEVJCCJRWQ5MG8W4TR5N3YNPA7NAM49TYECT56BHJPCD6DHJ3XQF2XTZQTCECDJWZG4TXK3DEZM7Y55XFJ49C7F1P17MWQJ","88RWAYX9765MBR4KBM2BN9NSQ5KHWFC0AQZ6B0RF617VF5CSJJZBB69XEVDFC12K5D0T06KBE71BHK9E5ZTEWD5FK4Z2JVKD6WV6QBFHQ8Y1ZPP7Y2EYB9XFW7S9NVM27FNX8RKWTAJ45FFYKJPNR7MX5HZZ77B25ZB57Q0NJB1E7ECH4QP5BKZG63CF5M75VA633AJC7A4XWR8JJTTQDZ40KJMXE15X9KPABVZM6HSGF19P3N2SE2VSSWFXF3XH","MBPTJC2E7C7YQMAKCJN8EYBX1Z0CG8Z35G5VW3AZH3PAM6DF32AXKFX06Y9686R1CC1TP5BWX94DMN78RXSZGXFTEKPK3RSEVWQZCBR6BWB7YEEJZTV0C5MGREJE73RP40730074SPPW6CW665XV5TJA5329ZM0XC0KJ3MR9V70TDCE3M9DMB1JXPN3M97A74XG6KNJ57FKDQH4C4NV3Z3MJ276Y9MDAPDF7GM7MW7AQ7T1Y98RJ4H6163WCSN6V","Q2YNH71E69EDZ1NF8PFRS66PJYZ360SPQVQK73422944P34FGDZVJPMF59Y5KVJSB8VTJRPVMBJQX5Q8M8BHEJX1YAHDXAAWBTTBFM12CACZT72CYH6S80TJ8GYGS9ED1HBBWWT8RTZGMD8R4NMBJJRED107EASZJTH4SW32FJ04X62EA7YAMAJFTPCC5E6QJ43F0H592Y3J81NXQS66P6GQXKQMCN5W3FX8ZJ3160V542YVE2FNV3X7WD88FVX3","5H1650VJX95VFVA0G8ZNGWBCACCZ53D6SNJ2S4QVD27X2128KWC4MXRH8B3FK89QXV07NTEAJEXGDZC8BSYW11JZGF7H3D2YVXX8ZEEZBHWESCN7TNSE9AQK5NBV709PNHP9KHE0K7KEJTY2GY08CWAN1G9CQ3ZPF71NEHC7C0C47GYNVHX9PTXAYH2ZKQJSRNNSERC62CFX7NQD5TPF6WFEYC9R1AB3TWEQQF3FKY5EAE04EAEQJWSW0ZFGRCZZ","F5DZP7M59MGX6YJ3M3CEXVSJ0FGD3Q3EVHSQDSDDKSFPJ7ZBN60SYEBB293PDCFZVKC7K4HZ54E3XM7G3GECK2ENZYRCQMS6PS7VM7DYKF7S56KP0N4SE6X69Z6EDA309XPHHPDGRG1593SZ63YN63M815C2JP9M5E7H4FWYWQQNA9C81FQFNH48NVF43G3A54EEM3JV6VKB7X9H3SESC7YRA862NZF42C2FP5MCSACPQBSX0HV2Y6QRZW53BTW2","HJ9AV53HDRMNPEJJPSEPJV98XXVK1D93WT9CQNNY7B0GKY2TTAW55C83EHJQ6SHMRYEWQPBS096ZKRDFHNM5YJF09V0NFEYTTJGSMFCDYKW5GYCZRH3FGZHKEMNN3S98WV5KWDG5PT0S9TCYTDW074WPNMTH9V9KE08E6N8ATBX62PZ2X326TT3N15RG3ZKHQ09360981ANE6MVMTM2J4RXPFMA936KTXAS0EF4FTTGZA1GHS4FNG0JRFBXTNF0D","BXGHTGZ5EWJY0XKATMGECZB8QM74WH1RY4FB4KZP0VQ4STWVBY0NMQ8832YWB04GQ2B50Q5RTD61091HH1R3S2DK6QD7VJYE9BZ4EEQJTDTSSQT86DQEK2QPTXQ8W39WBTAT8PKBT037JM9DCYAC4CRPGTQWBZK3M1Q8BZHRVXYVGGVC5GWG3YQX075EQCSD9Y18N08JKNFHJKVZH6X27ABDHC323HQS30VS15BD65GZ35MH4HBFT7CAHPDZ5C4D","D3EF8ZKVNK6PCTC7KWGDGTSA3804J3N76R186NKQC3YJPW9P9B6CXDN9910S5RNEGST7MSY566PFZY2NP95WZNJTMG8988H3HFVXTXK2TST59AFBNDNTGAK3Y149WVWNDAVZ3G4NM9R75HSWC75BGNDRJ1F6DQ8QACZRAN9E4K6J3NZ3BNG5A2JY48TVXEC3E6TEDMF3E4Q9QSWQXPEHE48TJB5J7Y98WTDDWWV3S186BGM5V06PTGS3V2QRMRW1","YXS4AQF8X7VR35BBA8RBRMCJCSRJ1K2820ERJV75TZ5X3XBB4S5A1EKAGQHRF6KENMX4GEM3NT30K20FD9W03B79AKARV0KK6SMW0W1DY68KWGF69JH95RCHXCSVVV8XGVC48CBW1ZBVPFXS1BEJRDBHJV5229R5CZ9AXC3DNSS7S2QYENQZT7ZSKM9VKYGN604GT55SXD0NXJ0QCVT6TG8MTGTTKSM9D1DFKGDRMP251FCEZVG4XGZSVHKGVHGY","2QYSG97X93Z5TR1JT9FJJG05T1ZKZCPKZ4FNTBNF3DT2WPJR12QF1EPB5NSYQK068XFRKK46YGTT5GX6QEJJZ2B61A35RM30HNTP9S794V2TSME26S4RG32M6AYCNZFR6YEXBKF828ER3HMS1XAF8H475HQSM4M0004GN2S2EHMBXR62TYBEYE20GB123X5V9B31W9650S1E833Y6AH8SKQRE5JY1AK0NATPP6DBKWRC3FTEV6QBDVDGB8B3FKVB","XHTD1E2BZMY311GRBP3PJ6TNJ5S9MMWYZEPFKBF98X6Z3B9GK8EKYZ9DMN0CR9JYQ95KVWETVEKGWMF3V9VT3KK29WGDBPCX1V44J09E2AA1V72A6Y6B6KXPPESB3SM2TH0DZZC1AYQG7TNAMWTQ028ECEZAW30DE3S4ZWKE78ZZJVKP4ZXFXWN3JTRY2TX112B3TRYH94GNYJ1JKGHVG55ESYQTFN7VPPW8GMRSJRVNJZ09S4P37G21HBH46NCK","2N7GXE2T60XCGFPMTS8YJZ64G6STTNPQ4R31CCRJ9Q3F7KJK9RNW4JAQ0VP0A0SCQGR12HJ09NW0TMWKFP46XHQWRJP9WMQA8TXPP6F1SR9XQX7A8KB73EGH3BPN8X5N7YY95M7ZBD1F4SJK24HR0P0GV6BMEEQFTE86QGTP67Z7KC1JPANYM1B3MX9Z9CVESD83WX4VG459X6YR2HR8T576M0EV5K73P9397H25ZSP0NXSAMGY87R0DCQM1GFR3","7WKT07H36FAWM8W3X54AN2VAGJC5DWKP2MJMHES1NA2VFVJD3WAWJJCNWARWP17SQSEWS38HF36A63BNR5Q0Z2R7VETVASYVEYRFQYFGW3KXCVT5AS2ZHJCDMWRJ0N5EQXP0J7MNMCCCQZ8F304SKWWMAQJ8EB5Z7KKM1T0Z8WQHZACR8YTATF4XSXSSJVPGGCXNC47B11QM4RN3HDGXF9MBZZMKASBZRF8AA2EH4M1VFG8NR243ZE3ZKK3818C7","FKZQB344EWN58WPNVZ0BR5A7D30ZR58HZN0PQPVJBKWK3F4A4DPNEV3Q8QXR70E8V5WT4JX8AG9H3X5RZ74DQPKH7RT4M4747NY98P4J09R58JP7BR58KA8Q0AHS6069GHNWC0SX9GF33RV9SA5ZYA75CV37V1N5GRAPX6A0SWR76B6HYSPA94G5PY02R9B05W8Y5MDWDEY70NW4ZXWG2ZH3S68SSNP6SASAJV85KMH6CH7FZXTDYQTAR0HDB4K7","3073VTHEC9679DTAPZZ2J52N4PXFJ2B5JDQQ4FEPWCDFWMTXJK4KTSVMSF6DNMY8BH3EJDS192CK16JZG95JGDGYXMYBAFCY4X77AGA33DZSNXT32VWNVKKP6PN5E7JWRDJT8SSCRJ6C90P9YZ31SFF4PPKFPSVZFRC3PD87CKT8M9D6XDWN61D0PMXKMWGJVSNQDR8NP86MJYE8KGHSX0SWDV2J9N3X9212CXV1JDQNS3DFJ7XEX38EEKWQDSJP"],["8HRTEYDE0W9T678MM8X20QADH4XHBW7JTHJVFDJDANF90K84P7FK7TN0KJGE5BW52VQQFB10YNEQ8S9J3WVRV229RSMC9FKNRJ60968J536EYP24C5WNACGATG6AG8N4YNDQNQQM8V2KQ6DX2XVEH3Z86W9ZYKW7SS4V4B2WM1ZT9WA80SM1H73H9JN62GMMQE37XHVQZ212836WQJMFFKR29ZSYZAE45FJ14AQMP6YKF56M4F1PSATNFZ62VC87","R2CW58HWX89YHVRJ8WYRJ1A2GSC24H1BQW38VGBXP0MXYZS939BWPG8KQM4ED9M19QVXKZ4RC21RN62EC7E1SSAF5VW0BFBGER1FWW3ZHJ78FFF1TNJGRVBE2PF18W1ZXMATHZ12RRKDPSYT81YTGRGAYZ71GHYD1SKYD711P2J80ZTKC6YCWJ6172Z14VWVFA6H8JWWA8VYZ4XTARPXZRWR1Y1YE4232XYZ8X6A8HHWQXS5ZNSM0WPK8BW0KSTA","DE3DY2F0TCKEXVV65EHTWFKKAB2ANTDS435K869BXNVA8NQH4JA6X3WM0H5VAVFT3RCAVQ1WYCRWZ2PKDABAFEXF8RP1R4ABX04ZZS4XPB9K4R1T5KK2HX7GMWQX8RZ16Q40C44DJ70YTHVBD0BM6AF2R5G86SRZ8KERPS21RMPYWYBBWM8DR5YS1D3HM6CEJE6ZX6K398XJGK59E3FCHYJX0B2SCRKJB5E5G1S6PAB3QXKZ2SDGGQR7F8XSESZR","ZNXFM7CDW0M2XRNBTD1GM4FDG74G2XDMTF53N7R71J93YPMPXTT4G6CVZM9YZHCK6GQ35P9CHEKNWK9Q3VESZCYR7TDMMQYYFBNPPM8E4CVHNHFZSBSAHP6MMSWQJ07GZA9FRW7ZGDH6ZNEWNVMQK0V8GV0035C8PP2SYMH66FWMA24E3D15RMJSF4C817TD7SD4F7WG6RCBQ6Y0DDHAY4VW1TB5V4W55J3N0GEMX7AJ98T0TSZQRS6R9PSP1JA2","2J8G5KH2TYWDYHA2KX2CEPW58CRDHK05YX5TER84CGAEBAQASD9Q0ED8R67VBVMDQE2C9NG7V4T3QZ2VRPJPYGQXFPCXBQF99FXK1GBMEGCVP8Y4S4TTN8NJCRBTY6CXA03KQ00JKVJMVSWA85969C2F8YAG8GS5TBPF3D8ZT2C56D2ZD65ZB7F0E11S7B2RWWES7TXQ7HKXDWEBFMYTC2WNGZT1NET0JG21MP03KPS6BEMAX5Q5B139QJNCW8F2","K1605K54DR7EXXPBSWWFNJMXQE6QZS3BN3WWVBF2Y83WD42NMB9FZJG0HXPXVGVJTV1EFEXBAV7PY28FAXTVJHAZ06CDD13ZF7JE12D1BN6T6AT4RHN6N00A2ZJQTE1VCWGBF49Y5B75ZXNS7F9K352AR5A5ZXG21KV7VNT2ZPJ2B1YQG11QY05W920TEGSC8TW0J41HN035ZHCG8KBF5QN5RMGKSVZQV708K7DPVYM7NC2V7X2ZHZ44CBCPEXHD","MGQFD9DMYZD61RHS6KFGC5YGRG2Q56CYNX4GYBGASQ1P69N76M6P4CA3WZV163VFR71JPAXPK11BFCVVCS3A9E59WAR6R0ZWXXN5P9XJXD7DSA1PWCS0JVJ851BCMPWE54M23JGHNRJ03VE76HW8ZH45B6WGG809T8T2HQRBHBPFN3WC9NSE7NY07YWH6V3MFMVPQ2GZJ6QJP4C9D2AGM88H71PD0H65YZY07HD72F3VV1QBJX6T1GPY3NYPEHH3","KWJQX6FGX9XTEXEXYVWJ657TGZHYWHX24KEE1XA5TW5NVVQMQGQVC6HE6GA4RBC1MM0S1GWN7Q838QQEP5DQNDZS7VDBSEP7SPHC832WRN492FN8CEVSJ01AAE36HRXNM8DN6JPK160DJ4M842W9STXQRWPPF94S6HER2WZX330EZ9CECD2NKWTPTYSDGA5FVZS2GC9ERQGYP4YKZWZ8DXRJWCNVE1G7BYYPWNTHPVX0EW5F9GHT4MR29YG9YMRS","50FNFP9CQNHME6EDC0JFNXDVHW58CMC0TJRRZ0FGAYK0MAYQ9EV5WZEYBDV5Y967H72AQBJ28GXD394BDW949KGEMNSAWYE3YX5GSZZNZ9CCXEDKJJSC7W1P1Z04B9VAAM07SPWN4EMREMR9R4XXCD014YHPMMKP64C58X93FCSFJNW8XDF5FW0S2KZJHSP8FGRNQ6D62CQJCVPS615D4N52XNPWTFS1R379GVKSZ4FRR6SRTT4R3PA271HTX3ZQ","NA6QQDXM6SB52138M8CN3E4ZY7P0P9QS9AJ6EGNKYYK58XTRR4D4BANF0ARSTVZGMSJ7FP803B87J21SR2KT35V5K4FEHWVQ9WB9F9ZFMREFAA3SP3NWXNYJQQS0YA2K3RRAWJYJ2XSPQERXW71702QAEQRZQPMGXCMDKFNY9V7Q77147RQ7M87G8ZHZHX83D9C6QHKWGKTSQ0R66MZRCX29C6P6A5EGHACWVXD32Z8SA1ZPNJBDZY0PNJJM68D0","4M00E1KQVQ5KTJPXTCT3EYFN1SH975ZASNAWN7Y0NB12ANR6VZ1163GKCK9WBQJ34Q9P0G763PQ0B68WCJT2KXYXX5X3AV91BPDGGWBYB220HQ52V5VES6H7D75KYS176MA27G09X9PAGTQJ78VQHDAZCYZ5TS82D7HM5GT618605NVEYTYQYT3BA38R6V0RHYGE0CHNEXZTM0TFGDXQZ3P6CTNMYPPPM4E4PMCCQC4XR2S6CGVWYE1TXN0DC59S","N98TSJ6HGBS2N3K3MRPW6WEHBP409B4DC7PW8GMHM2QM2VGC0XQE1AQQ9AT1J4MZAK0B3RAMWF74XZT0D1CBDFABDY8M2M3X6QM7M4C8Z2DV7B2KHF3GBV4FMEG2PWAPW97QJ47383BT5XEYC4KQNXNK1K22NXYQJK0W5GS64KRFZWWKENZABG8VYZ7AYDAXMPVRSCQCCDE1RS6V8MDAVP65SAB59NSM0QQMBAPVBDTWD0P3HCZMG4F17QGTYZYP","ABT20WXHNAHYANMGCVGR9VC1QEQMS5YYRMMPS8GE04RK7XDF4CFXV9HEC1TAN77PBZJXAGX8GVZ8Y0068T84M1DQ90F2VEZGHR1JBBJ91S6RER8FKC301STT9MSF0CYBQTG5GAZK948305FBT6QRFX5BYS4NB5ZX86YMTNZ01V5C26W6Q6T78E5MHC7PH21SQ2VERSMDBWC640068HDX4PCJWBV7809DBSHR2ASJBMM32C49SG4EYVW74H4385QA","M0HBDKZV4TAYEBJJFBMMV55735T3F114RQMHBW2FRTDNNEM6NVBYR65Q25GJ1XNZJV0J7CY55CWMNEARHS2F9TYZPM0BVFZ429DVB2BTX8ZJ6Y9M1RSMVZZZM3GVE865PFEYCVG40AVC5T01Q8XGW1FDZKZTXBVCR5J7H24870S3BJB820X7MQMXDHNNT7VA4XZQJRN96JNHFC3RWB8R2X6Z4BZA9AYETNHP180MY0CJB8CJ8P9DTFHFRAY4M8WE","HV2FVXRE3NGW0WR4XMX0QZX2Z0B7MQB2ANCC95GY0FY9WD74D6CSM11YC0HNMBYCX8CTX7QJS28FGK9FC2RP79M8MKBGHHP0E3SK33AHRQ6457TSMARYKRY2SNSTV7JC8Z1KPV55EH5EH9C736G42J9W84E61JRWG8PMD24V1TS0MJZWKNP1QKSET7N40VAJXZE6B2QDPH2D7F26S4SND52B8KS1HB0NH762F41QE22ANVSXV6S2TN73Y5MXTB9S","70M04PP2WVXGK3NJCMPXGQQCCCDTRW0AF5F8N8A605DHY4CKBFFXX9F5Y512JS3CY6MD9T7K1X0ANT3N5VJRWPF9Y1S97EA2NAZ2W783DEP32Q1M3Y93VBHKCBQNM9M456VSMZA16E8SP3EGPJ9K7QVE0KYV0VWFJRXCA2CWDNNFDEKEJ6GK164SKGZ5MV1QQEDM258SKTQQ0EER895QRAA50PHNNQF1Z8MGHMZCV9WP0ZSJASVK63E4Z073TTVD","799TV24HVN9JDQBG0WFGA5FH0RNJYFJG2XF1B93W5W571CRPMEAHCAKA294YT63VV0K7C46AQYF1AVS58ZJ58CESDYWMY770P95EXPM5H38NV3F3RDTDB7EX5Q9XN0X8QRW37BTN8VBQ4PZ6Z1Y4539C7QGE2F012RBKGK47BST1TQP3XA427H1CDGT8HJBHBY8BD4TH1N7EE0XZF2QV6AJ0JCPWD0H6TKPVMGH1QYKVEKEPQBX20S8K502AYGKQ"],["23NQ8CW42D7FAY7FD7636SFA7THRQA2EGJYMFYF75FA1ZATVSMPPYSHTEC58J84PKDHRD0KPFMK811CTSGW0W274D8A3E5FKEX6P5AZ2ST0K9PK65ZP6R6CR8RMWSGQQX5VAJ2YVJ3RNV20C7YJA0CDCJNQX3JKGRX5NRY8GY3RNA38HG3SM0NW85AKZ0SXP1XRZRY0A0T3SM6E4Q6PTJMJZJ677BRZHXAGB7BEMSK6KDERXHVN9AGN4J9SZ5AY0","ZNZFHJ4X5T79G4DXBX8TPG0RV8SV4B7ZKV56CAN893MZ6Z00CNFPH6KPPXTKM8MB6MTPSCHEA0PRGRHSAKPBSAVR019GB9E75TFSXV452ZQN8Q54ZH9BJPKMAMB26X8BDQKGYK01FAWRWZXJ1TW9GVPJ9TS03WKQS64TVRNH17TTFVED3K8MFMDC9SFSJ4FZC2J19XTSWASXSY4BDE4T8Z2VGWEGDTYF0PNPMHDN2FQN2T25T6TA2DW702HM9MBC","SCC6Y84J99YQ598PR67G5VDT1N9E6S2VEZ22QWD7PQDX108ZZJ2AK16YF9PS8WSAP1PCPM0AYD1R9522XZNGWEHX92RE3R0WVM43A38450XH0EHY6XYKCK0NWJJ9XKN0FG4GACRF6X1WG4Y7AYW6FBFMBKHWDHP8BCTT0J9Q9XXZEBC88C57DMG18ABP6S248JAX9JCN0GZ4Q25AX6XYA2W2KD7CW97H23H11NS47XMC0GFKF7FDZK11JQXASJMT","PJBV9WQ064WKS9WVV0JWN3JHGNCY82T42FC0ANKNV5SEE8BBPJTQ6HXM76D8FAH00A6GNB2HMRYAAS0ZTGESRE2JFDD5HFAWW2EX9BN1QA7T6EB1G4PBFFZXVD3AN3GZKK72A4VT5AFM9QF7GHEXVWC29AHH5HH3ZHYQ1AR5TB5G7CNRCM8GA1Q61P574H0G2DWJJ35XE79D3G6SEWPCPB3VZS89B3XY9A1HV86R6BVY6C48D7FHXEA903AVVSMP","7HS65FGXB1Y1E39DHB5J0SRX8KDS7K6YBNP1XAPBQQNBM8N2T1QAVVS37XG15A2ABWK9J2CP16WCWNMG1ZCS6KEY98DXRNKNEQ099M9P392D00R8HDE1Y8C918PV0DVXKM3141CYJFXTQCYM9A5ZYKDEXNF09NTWHNDT3F990R6EWYS0TT4F4YDW9605SWTPJ2G29CKQNP13N1KKFRJS4BME8VE800YH6HTCSWTPXQ19TFXCHET6PF9EZE00RF9D","39HPW4NZCE7T1TF3ZFW6FGSSSETD2NMY5ED7T49572WAZ5FBJJSGGK8G45CQSSS488KZ2WRVM2N79ZKD78AX08X7TAW7DGWKB5KX9KRFCAF7ZZJQP5W6A1652XYNV2NWR51MS30ME0Y6RPXCMVC7K2B3HK5TFQ893HE8JJJSD2TF3AS9RGMPRBXXPCTC1R3Y5MZJQ79RKV70PVW65MA00RPMC27VGBWW5MA7FWK4X8JWQ8PHB9666MVM64AZJZSC","WFPTT5ZYQZNF2TYKSMFT6D7RP85980PTXNP6PS1HY3J5Y4ZY8DEVNPGFDP85SQ2DST4QQ1EHQQ9CHP9T1Q25XSGNKAHF9QAXX6N029SNH3C0MTG5VAQWQMCSPAYBW08EKFJS560JXX1AT9JCD0CJXZTX1P7TC4XD1JJCBDYKQ592CJZDKBCVMDN2CAG7RNXVSKQ8EWCRM7NCEF3YVWBJ7SQRE9B3BB067SVSRJQ9SB0PTGRP93WRNQ2JEGC584K8","P0VHPX17WERWA5G19P5NV2ZBWXGX3YWFTYHMSWQ2C5H82TPQ08BNDPE813NYWWT231EMTY7VRW2ZS8S447T4K5BCD7S0YQX2D92G5VRP10YTC57P16QZ37T0QZ9JENVPT6665TZ97JERST3M8SG7BD3ZDM5H0S9KCQTXXDJCR9NB1WHNJFTYZ0DJYJFDNZMTKREQG3R5VF5DQXXWKJ2GVKZG6ED2VTJR11MKYTQBMXSKP50RJYEZMVCZQ8H5KZBV","VDS1SGX5A31KBPTBPBR82C4ZD5FG2XRDM5KGVAR7X8XGXP7ZMGREXS9CYFKA9SZ3GMF0H7X4H733C81HPNDDC2NPF4443CB0KY0XSMJ34SA6TY99SE0NTZ96RZ36CZP77HRQQ93WC2EN3FBGS9WQMZEQ7GKNZSH6RBGXMF237ZGRN4FPRGX771VXY5DQXFMPTFYRDHCPWJR9FZKF7TQT0MB1K412PRFHSHM9S8FZJCHYR2PP443WF9SZWGNM2CV8","J8KEHBYBAQVMTR1XTNDY23VSTQVW5CYE2EDNRE9DBP56DV1PSC2XXFH8KKHXT8D3TM5G6MCHE0JWNHN6417G696MHY5NZ6CRAYYBNR4YBPHDF94CHBMZSXH6KD3N0AVW3F0KK88DPA9G2CZ8C82QTV8Z8C8WRRWAHRH0X6SEGK4BJPEB3WEGRMD9AFYTXCPQ0N7BXK83052V4HD6W7AB0AE7EQ789XF6C8AD4TN7ARF7MJDMTCY624B6QR5K15Y5","R5D4QJ0HM2A9E8WEVZYKG9V612D1ZRRB6RCGK4S8MDBM7NRBP7DDJGV1H7SN0JEZJSTW7DGVA7WRK0Z8HSH6VGB2NZ2CRZKZTTX15W58EB2XKJKZJYA0AR5MKTNHPP7D39PFF8BP39PJX9VRFHGXMWB84GHVMND6S2M2WWVHMBVNK2D19TJ1240NZFV6GP64416JT0QW06ZQMXVHVD4GFRESTSVBH1G78AMJ972RAHBF4DM0PJXY0PTXYWQSP2GZ","KZ0R0MPKXYKS13894TAC8SMNGYQ9F1EB9VN9S4P7VJ8KDXJEREYR64HDRAP4HBRY9D50YDKDPE9AHMW9ETR4CXK3E0YQJQ2YGPAWJ823GH7KHJXK1GTFSZ3TERT1VB31FGXC0950TY0MAHESMPHNRXSG9738C0CEJ5Z1TNBVV48WJCH2JPCKAPDMTSFYFAECZ28WJ7J9EW459NNT5H4D0DB5ZZ2CY16CCC8PBC0K4ZYP1Y2ZW5MK0N0MJTXGQ49T","YVH178SVB7QK1HM2HVSDGBZ1QJN9NRTG3RXMC9YVS74VXTW719NRFMAXKJHVZJE77HNRW53S8DSD3QTYDZ9ZNZ1185GZXP0NNP40NQFYJQSBHWVE7WDPR5KZT42BWQE1P20B3F5HCHV3VD1ZCGCZ1NF5K5P4WKT01K7XKXNGFTX9HY5YAG30THBY4S3Q3CPMQBVCBXDHW929DHRBZWHZGGGBCTVNRRNDA7J0EVBAVYYHES2PDK9YW5STCG83S5AB","MA1RKAT9SCJSXJG7T5P1QF1VW9GRBCYK03P8Z55KSCK4SN1K0MFSXR3YERD4VA5QQ1N7KKHPDAPNYHQ6QW2TPBVM932A5G42VEKZ9CH5ZH7346RKDT8BMNSFYQ799W6042CCJ3JSESFYMER8JHZNBVFWFGMKMJJ642BCP9PPA9H7B0XSSGK2CZYWK3RQX5WKZK4V40AMTRJ9JRZNX7QKXRN9W464J3X3BRYHR8Y70C42HH0HVW86CPNVKWE0Q36C","K8CP0BM60F6P218ZMC4HW307DNN3FHHEG3XMWN55KEZR4RQ86ADXGJF2FKD7QW2TVQ3AWN2KVJ6QMP6SFJHF11C6T68FWKWMQVGQWYY28591XTQ3D78FB9N06T2W3R6EERTHRQNK7X9WP2BV3VM43A39YXVJ0W0BHJ0XSZQENN218ZR4GTVDX48788WXFD6064ZXGMWGD2EKWMS4BBVWK59FVZPGDRQD6EN3TRA784HBBWE831JWBEVX3YGPX9T0","QC2WWZPKJDCFZA65QKSGKW7YTAS9RP0W4H0FP96Z5TGCCD0K9FXFX4F75MQEDNKCM0B34D8ZR46CHR97HMMEB3W2T8W6P8X5289Z729365HVDQWCGHMJSGM7AHA1HS6ZND695X67D42H7XB7NQ5X57XG2S32R767YPQJERZ0ESG3S01NGNQ0P62KM3AT28JX41GMHPEA92V1Z80RYS8GWE9CGZ6K66NVGW72SN8DJGDFVC7771HP93X6DYN0TF63","H3P6CNRKH4BSCAWG3T9RRA89EBF1F91JEYEY4RZJQ2NNCZZASY362XHME0KNM36R4SNQD956CBW15XAXVK70TZM6TBVMHJ8KKQQQKBG4Q91ZHKAS10NEVD5B36QGYM7CMJM3DVJ00KTK0ZAM8C4YRNZDZKV5CNFGDR0DDP1P38704Y3G9KFRJ455SD39J7ZYDB5GQV17ZF2PE7GY927S1KSXV4KKXQ7FRX1VZRJGAD1F3DY4KJFRZJGAT5951JNS"]]}
diff --git a/src/lib/baseline/refresh_reveal.req b/src/lib/baseline/refresh_reveal.req
new file mode 100644
index 00000000..3fb14396
--- /dev/null
+++ b/src/lib/baseline/refresh_reveal.req
@@ -0,0 +1,7 @@
+POST /refresh/reveal HTTP/1.1
+Host: localhost:8081
+Accept: */*
+Content-Type: application/json
+Content-Length: 255
+
+{"session_hash":"V97SB8T670M9V71D1Q0KVQ4GSJCVQ5AAKTTH9QKT0ZJZZBFNZAV4NA8NMWRRGVPFEBEGB6ANCN9BPQASJ40TM4Y1C49648TJJ07PGSG","transfer_privs":[["EQKJA401A9NJ2YJDFZJ1EV8AYXBHWZB6NT5T0TWSJHVKVDM6W8A0"],["TKDJ4DF3GZVG0DGAB9E3RGBGSTANYB6JVVWXJGPMB2AY4VQNTBA0"]]} \ No newline at end of file
diff --git a/src/lib/baseline/reserve_status.req b/src/lib/baseline/reserve_status.req
new file mode 100644
index 00000000..4f988f66
--- /dev/null
+++ b/src/lib/baseline/reserve_status.req
@@ -0,0 +1,4 @@
+GET /reserve/status?reserve_pub=TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0 HTTP/1.1
+Host: localhost:8081
+Accept: */*
+
diff --git a/src/lib/baseline/reserve_withdraw.req b/src/lib/baseline/reserve_withdraw.req
new file mode 100644
index 00000000..48495025
--- /dev/null
+++ b/src/lib/baseline/reserve_withdraw.req
@@ -0,0 +1,7 @@
+POST /reserve/withdraw HTTP/1.1
+Host: localhost:8081
+Accept: */*
+Content-Type: application/json
+Content-Length: 919
+
+{"coin_ev":"Q5X8A8TCBFH7E5BMY7HSB17SHFTM1JPJGV61P2CA7Z9EXG8P2HYS69B31NZESKXHSZHNJ2DQN3CC2AWFNC6V90J577JD3TXBMAY8Y5M9V60KKT73Z1DW24JFSNAK91G1F2WT55ADP1EG7N5F9AY7A7ZJD03MPYSH0RDP7SVZS2KRPA5JRHFR4GDJ59CFNE7A43M95ZKQHQAS8","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","reserve_pub":"TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0","reserve_sig":"8427B3RTB217124EB1C37ZVJFC08KN17RHGHE9ENZQMQVJ0S11SAX6H8Z06SWCKT06DRQ9DQ8XD786XKQ94T27PYR9GC9EMT1Y02W10"} \ No newline at end of file
diff --git a/src/lib/baseline/wire.req b/src/lib/baseline/wire.req
new file mode 100644
index 00000000..a4f1d074
--- /dev/null
+++ b/src/lib/baseline/wire.req
@@ -0,0 +1,5 @@
+GET /wire HTTP/1.1
+Host: localhost:8081
+Accept: */*
+Content-Type: application/json
+
diff --git a/src/lib/baseline/wire_sepa.req b/src/lib/baseline/wire_sepa.req
new file mode 100644
index 00000000..80d3d461
--- /dev/null
+++ b/src/lib/baseline/wire_sepa.req
@@ -0,0 +1,5 @@
+GET /wire/sepa HTTP/1.1
+Host: localhost:8081
+Accept: */*
+Content-Type: application/json
+
diff --git a/src/lib/baseline/wire_test.req b/src/lib/baseline/wire_test.req
new file mode 100644
index 00000000..684352c9
--- /dev/null
+++ b/src/lib/baseline/wire_test.req
@@ -0,0 +1,5 @@
+GET /wire/test HTTP/1.1
+Host: localhost:8081
+Accept: */*
+Content-Type: application/json
+
diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c
new file mode 100644
index 00000000..6b0aa6ff
--- /dev/null
+++ b/src/lib/exchange_api_common.c
@@ -0,0 +1,353 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2017 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-lib/exchange_api_common.c
+ * @brief common functions for the exchange API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+
+
+/**
+ * Verify a coins transaction history as returned by the exchange.
+ *
+ * @param currency expected currency for the coin
+ * @param coin_pub public key of the coin
+ * @param history history of the coin in json encoding
+ * @param[out] total how much of the coin has been spent according to @a history
+ * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not
+ */
+int
+TALER_EXCHANGE_verify_coin_history (const char *currency,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ json_t *history,
+ struct TALER_Amount *total)
+{
+ size_t len;
+ int add;
+ struct TALER_Amount rtotal;
+
+ if (NULL == history)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ len = json_array_size (history);
+ if (0 == len)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ total));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &rtotal));
+ for (size_t off=0;off<len;off++)
+ {
+ json_t *transaction;
+ struct TALER_Amount amount;
+ const char *type;
+ struct GNUNET_JSON_Specification spec_glob[] = {
+ TALER_JSON_spec_amount ("amount",
+ &amount),
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_end()
+ };
+
+ transaction = json_array_get (history,
+ off);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec_glob,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ add = GNUNET_SYSERR;
+ if (0 == strcasecmp (type,
+ "DEPOSIT"))
+ {
+ struct TALER_DepositRequestPS dr;
+ struct TALER_CoinSpendSignatureP sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &dr.h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("h_wire",
+ &dr.h_wire),
+ GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
+ &dr.timestamp),
+ GNUNET_JSON_spec_absolute_time_nbo ("refund_deadline",
+ &dr.refund_deadline),
+ TALER_JSON_spec_amount_nbo ("deposit_fee",
+ &dr.deposit_fee),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &dr.merchant),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ dr.purpose.size = htonl (sizeof (dr));
+ dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
+ TALER_amount_hton (&dr.amount_with_fee,
+ &amount);
+ dr.coin_pub = *coin_pub;
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
+ &dr.purpose,
+ &sig.eddsa_signature,
+ &coin_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* TODO: check that deposit fee and coin value match
+ our expectations from /keys! */
+ add = GNUNET_YES;
+ }
+ else if (0 == strcasecmp (type,
+ "MELT"))
+ {
+ struct TALER_RefreshMeltCoinAffirmationPS rm;
+ struct TALER_CoinSpendSignatureP sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &sig),
+ GNUNET_JSON_spec_fixed_auto ("rc",
+ &rm.rc),
+ TALER_JSON_spec_amount_nbo ("melt_fee",
+ &rm.melt_fee),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rm.purpose.size = htonl (sizeof (rm));
+ rm.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
+ TALER_amount_hton (&rm.amount_with_fee,
+ &amount);
+ rm.coin_pub = *coin_pub;
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT,
+ &rm.purpose,
+ &sig.eddsa_signature,
+ &coin_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* TODO: check that deposit fee and coin value match
+ our expectations from /keys! */
+ add = GNUNET_YES;
+ }
+ else if (0 == strcasecmp (type,
+ "REFUND"))
+ {
+ struct TALER_RefundRequestPS rr;
+ struct TALER_MerchantSignatureP sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("merchant_sig",
+ &sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &rr.h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &rr.merchant),
+ GNUNET_JSON_spec_uint64 ("rtransaction_id",
+ &rr.rtransaction_id),
+ TALER_JSON_spec_amount_nbo ("refund_fee",
+ &rr.refund_fee),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rr.purpose.size = htonl (sizeof (rr));
+ rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
+ rr.coin_pub = *coin_pub;
+ TALER_amount_hton (&rr.refund_amount,
+ &amount);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
+ &rr.purpose,
+ &sig.eddsa_sig,
+ &rr.merchant.eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* NOTE: theoretically, we could also check that the given
+ merchant_pub and h_contract_terms appear in the
+ history under deposits. However, there is really no benefit
+ for the exchange to lie here, so not checking is probably OK
+ (an auditor ought to check, though). Then again, we similarly
+ had no reason to check the merchant's signature (other than a
+ well-formendess check). */
+ /* TODO: check that deposit fee and coin value match
+ our expectations from /keys! */
+ add = GNUNET_NO;
+ }
+ else if (0 == strcasecmp (type,
+ "PAYBACK"))
+ {
+ struct TALER_PaybackConfirmationPS pc;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &pc.reserve_pub),
+ GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
+ &pc.timestamp),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ pc.purpose.size = htonl (sizeof (pc));
+ pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK);
+ pc.coin_pub = *coin_pub;
+ TALER_amount_hton (&pc.payback_amount,
+ &amount);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK,
+ &pc.purpose,
+ &exchange_sig.eddsa_signature,
+ &exchange_pub.eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ add = GNUNET_YES;
+ }
+ else
+ {
+ /* signature not supported, new version on server? */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_YES == add)
+ {
+ /* This amount should be added to the total */
+ if (GNUNET_OK !=
+ TALER_amount_add (total,
+ total,
+ &amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ else
+ {
+ /* This amount should be subtracted from the total.
+
+ However, for the implementation, we first *add* up all of
+ these negative amounts, as we might get refunds before
+ deposits from a semi-evil exchange. Then, at the end, we do
+ the subtraction by calculating "total = total - rtotal" */
+ GNUNET_assert (GNUNET_NO == add);
+ if (GNUNET_OK !=
+ TALER_amount_add (&rtotal,
+ &rtotal,
+ &amount))
+ {
+ /* overflow in refund history? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+
+ /* Finally, subtract 'rtotal' from total to handle the subtractions */
+ if (GNUNET_OK !=
+ TALER_amount_subtract (total,
+ total,
+ &rtotal))
+ {
+ /* underflow in history? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Obtain meta data about an exchange (online) signing
+ * key.
+ *
+ * @param keys from where to obtain the meta data
+ * @param exchange_pub public key to lookup
+ * @return NULL on error (@a exchange_pub not known)
+ */
+const struct TALER_EXCHANGE_SigningPublicKey *
+TALER_EXCHANGE_get_exchange_signing_key_info (const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ for (unsigned int i=0;i<keys->num_sign_keys;i++)
+ {
+ const struct TALER_EXCHANGE_SigningPublicKey *spk;
+
+ spk = &keys->sign_keys[i];
+ if (0 == memcmp (exchange_pub,
+ &spk->key,
+ sizeof (struct TALER_ExchangePublicKeyP)))
+ return spk;
+ }
+ return NULL;
+}
+
+/* end of exchange_api_common.c */
diff --git a/src/lib/exchange_api_curl_defaults.c b/src/lib/exchange_api_curl_defaults.c
new file mode 100644
index 00000000..45aa36b9
--- /dev/null
+++ b/src/lib/exchange_api_curl_defaults.c
@@ -0,0 +1,75 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_curl_defaults.c
+ * @brief curl easy handle defaults
+ * @author Florian Dold
+ */
+
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Get a curl handle with the right defaults
+ * for the exchange lib. In the future, we might manage a pool of connections here.
+ *
+ * @param url URL to query
+ */
+CURL *
+TEL_curl_easy_get (const char *url)
+{
+ CURL *eh;
+
+ eh = curl_easy_init ();
+
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_ENCODING,
+ "deflate"));
+#ifdef CURLOPT_TCP_FASTOPEN
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TCP_FASTOPEN,
+ 1L));
+#endif
+ {
+ /* Unfortunately libcurl needs chunk to be alive until after
+ curl_easy_perform. To avoid manual cleanup, we keep
+ one static list here. */
+ static struct curl_slist *chunk = NULL;
+ if (NULL == chunk)
+ {
+ /* With POST requests, we do not want to wait for the
+ "100 Continue" response, as our request bodies are usually
+ small and directy sending them saves us a round trip.
+
+ Clearing the expect header like this disables libcurl's
+ default processing of the header.
+
+ Disabling this header is safe for other HTTP methods, thus
+ we don't distinguish further before setting it. */
+ chunk = curl_slist_append (chunk, "Expect:");
+ }
+ GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HTTPHEADER, chunk));
+ }
+
+ return eh;
+}
diff --git a/src/lib/exchange_api_curl_defaults.h b/src/lib/exchange_api_curl_defaults.h
new file mode 100644
index 00000000..7ebf4e96
--- /dev/null
+++ b/src/lib/exchange_api_curl_defaults.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/exchange_api_curl_defaults.h
+ * @brief curl easy handle defaults
+ * @author Florian Dold
+ */
+
+#ifndef _TALER_CURL_DEFAULTS_H
+#define _TALER_CURL_DEFAULTS_H
+
+
+#include "platform.h"
+#include <gnunet/gnunet_curl_lib.h>
+
+
+/**
+ * Get a curl handle with the right defaults
+ * for the exchange lib. In the future, we might manage a pool of connections here.
+ *
+ * @param url URL to query
+ */
+CURL *
+TEL_curl_easy_get (const char *url);
+
+#endif /* _TALER_CURL_DEFAULTS_H */
diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c
new file mode 100644
index 00000000..55b3ca6b
--- /dev/null
+++ b/src/lib/exchange_api_deposit.c
@@ -0,0 +1,597 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2018 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-lib/exchange_api_deposit.c
+ * @brief Implementation of the /deposit request of the exchange's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Deposit Handle
+ */
+struct TALER_EXCHANGE_DepositHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * JSON encoding of the request to POST.
+ */
+ char *json_enc;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_DepositResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Information the exchange should sign in response.
+ */
+ struct TALER_DepositConfirmationPS depconf;
+
+ /**
+ * Value of the /deposit transaction, including fee.
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Total value of the coin being transacted with.
+ */
+ struct TALER_Amount coin_value;
+
+};
+
+
+/**
+ * Signature of functions called with the result from our call to the
+ * auditor's /deposit-confirmation handler.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code, 200 on success
+ * @param ec taler protocol error status code, 0 on success
+ * @param json raw json response
+ */
+static void
+acc_confirmation_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const json_t *json)
+{
+ /* FIXME: clean up state, some logging on errors! */
+}
+
+
+/**
+ * Verify that the signature on the "200 OK" response
+ * from the exchange is valid.
+ *
+ * @param dh deposit handle
+ * @param json json reply with the signature
+ * @param exchange_sig[out] set to the exchange's signature
+ * @param exchange_pub[out] set to the exchange's public key
+ * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
+ */
+static int
+verify_deposit_signature_ok (const struct TALER_EXCHANGE_DepositHandle *dh,
+ const json_t *json,
+ struct TALER_ExchangeSignatureP *exchange_sig,
+ struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("sig", exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("pub", exchange_pub),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ key_state = TALER_EXCHANGE_get_keys (dh->exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT,
+ &dh->depconf.purpose,
+ &exchange_sig->eddsa_signature,
+ &exchange_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 /* #5447: replace with "for all auditors, if auditor selected for DC notification... */)
+ {
+ struct TALER_AUDITOR_DepositConfirmationHandle *dch;
+ const struct TALER_EXCHANGE_SigningPublicKey *spk;
+ struct TALER_Amount amount_without_fee;
+
+ spk = TALER_EXCHANGE_get_signing_key_details (key_state,
+ exchange_pub);
+ GNUNET_assert (NULL != spk);
+ TALER_amount_ntoh (&amount_without_fee,
+ &dh->depconf.amount_without_fee);
+ dch = TALER_AUDITOR_deposit_confirmation (NULL /* FIXME: auditor */,
+ &dh->depconf.h_wire,
+ &dh->depconf.h_contract_terms,
+ GNUNET_TIME_absolute_ntoh (dh->depconf.timestamp),
+ GNUNET_TIME_absolute_ntoh (dh->depconf.refund_deadline),
+ &amount_without_fee,
+ &dh->depconf.coin_pub,
+ &dh->depconf.merchant,
+ exchange_pub,
+ exchange_sig,
+ &key_state->master_pub,
+ spk->valid_from,
+ spk->valid_until,
+ spk->valid_legal,
+ &spk->master_sig,
+ &acc_confirmation_cb,
+ NULL /* FIXME: context! */);
+ }
+
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Verify that the signatures on the "403 FORBIDDEN" response from the
+ * exchange demonstrating customer double-spending are valid.
+ *
+ * @param dh deposit handle
+ * @param json json reply with the signature(s) and transaction history
+ * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
+ */
+static int
+verify_deposit_signature_forbidden (const struct TALER_EXCHANGE_DepositHandle *dh,
+ const json_t *json)
+{
+ json_t *history;
+ struct TALER_Amount total;
+
+ history = json_object_get (json,
+ "history");
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_verify_coin_history (dh->coin_value.currency,
+ &dh->depconf.coin_pub,
+ history,
+ &total))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_add (&total,
+ &total,
+ &dh->amount_with_fee))
+ {
+ /* clearly not OK if our transaction would have caused
+ the overflow... */
+ return GNUNET_OK;
+ }
+
+ if (0 >= TALER_amount_cmp (&total,
+ &dh->coin_value))
+ {
+ /* transaction should have still fit */
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* everything OK, proof of double-spending was provided */
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /deposit request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_DepositHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_deposit_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_DepositHandle *dh = cls;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP *es = NULL;
+ struct TALER_ExchangePublicKeyP *ep = NULL;
+ const json_t *j = response;
+
+ dh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ verify_deposit_signature_ok (dh,
+ j,
+ &exchange_sig,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ else
+ {
+ es = &exchange_sig;
+ ep = &exchange_pub;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* Double spending; check signatures on transaction history */
+ if (GNUNET_OK !=
+ verify_deposit_signature_forbidden (dh,
+ j))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ dh->cb (dh->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ es,
+ ep,
+ j);
+ TALER_EXCHANGE_deposit_cancel (dh);
+}
+
+
+/**
+ * Verify signature information about the deposit.
+ *
+ * @param dki public key information
+ * @param amount the amount to be deposited
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
+ * @param coin_pub coin’s public key
+ * @param denom_pub denomination key with which the coin is signed
+ * @param denom_sig exchange’s unblinded signature of the coin
+ * @param timestamp timestamp when the deposit was finalized
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed)
+ * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not
+ */
+static int
+verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki,
+ const struct TALER_Amount *amount,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ struct GNUNET_TIME_Absolute timestamp,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct TALER_DepositRequestPS dr;
+ struct TALER_CoinPublicInfo coin_info;
+
+ dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
+ dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS));
+ dr.h_contract_terms = *h_contract_terms;
+ dr.h_wire = *h_wire;
+ dr.timestamp = GNUNET_TIME_absolute_hton (timestamp);
+ dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
+ TALER_amount_hton (&dr.amount_with_fee,
+ amount);
+ TALER_amount_hton (&dr.deposit_fee,
+ &dki->fee_deposit);
+ dr.merchant = *merchant_pub;
+ dr.coin_pub = *coin_pub;
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
+ &dr.purpose,
+ &coin_sig->eddsa_signature,
+ &coin_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n");
+ {
+ TALER_LOG_DEBUG ("... amount_with_fee was %s\n",
+ TALER_amount2s (amount));
+ TALER_LOG_DEBUG ("... deposit_fee was %s\n",
+ TALER_amount2s (&dki->fee_deposit));
+ }
+
+ return GNUNET_SYSERR;
+ }
+
+ /* check coin signature */
+ coin_info.coin_pub = *coin_pub;
+ coin_info.denom_pub = *denom_pub;
+ coin_info.denom_sig = *denom_sig;
+ if (GNUNET_YES !=
+ TALER_test_coin_valid (&coin_info))
+ {
+ GNUNET_break_op (0);
+ TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
+ return GNUNET_SYSERR;
+ }
+ if (0 < TALER_amount_cmp (&dki->fee_deposit,
+ amount))
+ {
+ GNUNET_break_op (0);
+ TALER_LOG_WARNING ("Deposit amount smaller than fee\n");
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Submit a deposit permission to the exchange and get the exchange's response.
+ * Note that while we return the response verbatim to the caller for
+ * further processing, we do already verify that the response is
+ * well-formed (i.e. that signatures included in the response are all
+ * valid). If the exchange's reply is not well-formed, we return an
+ * HTTP status code of zero to @a cb.
+ *
+ * We also verify that the @a coin_sig is valid for this deposit
+ * request, and that the @a ub_sig is a valid signature for @a
+ * coin_pub. Also, the @a exchange must be ready to operate (i.e. have
+ * finished processing the /keys reply). If either check fails, we do
+ * NOT initiate the transaction with the exchange and instead return NULL.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param amount the amount to be deposited
+ * @param wire_deadline date until which the merchant would like the exchange to settle the balance (advisory, the exchange cannot be
+ * forced to settle in the past or upon very short notice, but of course a well-behaved exchange will limit aggregation based on the advice received)
+ * @param wire_details the merchant’s account details, in a format supported by the exchange
+ * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
+ * @param coin_pub coin’s public key
+ * @param denom_pub denomination key with which the coin is signed
+ * @param denom_sig exchange’s unblinded signature of the coin
+ * @param timestamp timestamp when the contract was finalized, must not be too far in the future
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline
+ * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_DepositHandle *
+TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Absolute wire_deadline,
+ json_t *wire_details,
+ const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ struct GNUNET_TIME_Absolute timestamp,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ TALER_EXCHANGE_DepositResultCallback cb,
+ void *cb_cls)
+{
+ const struct TALER_EXCHANGE_Keys *key_state;
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+ struct TALER_EXCHANGE_DepositHandle *dh;
+ struct GNUNET_CURL_Context *ctx;
+ json_t *deposit_obj;
+ CURL *eh;
+ struct GNUNET_HashCode h_wire;
+ struct TALER_Amount amount_without_fee;
+
+ (void) GNUNET_TIME_round_abs (&wire_deadline);
+ (void) GNUNET_TIME_round_abs (&refund_deadline);
+ GNUNET_assert (refund_deadline.abs_value_us <= wire_deadline.abs_value_us);
+ GNUNET_assert (GNUNET_YES ==
+ TEAH_handle_is_ready (exchange));
+ /* initialize h_wire */
+ if (GNUNET_OK !=
+ TALER_JSON_merchant_wire_signature_hash (wire_details,
+ &h_wire))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ key_state = TALER_EXCHANGE_get_keys (exchange);
+ dki = TALER_EXCHANGE_get_denomination_key (key_state,
+ denom_pub);
+ GNUNET_assert (NULL != dki);
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_amount_subtract (&amount_without_fee,
+ amount,
+ &dki->fee_deposit));
+ if (GNUNET_OK !=
+ verify_signatures (dki,
+ amount,
+ &h_wire,
+ h_contract_terms,
+ coin_pub,
+ denom_sig,
+ denom_pub,
+ timestamp,
+ merchant_pub,
+ refund_deadline,
+ coin_sig))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+
+ deposit_obj = json_pack ("{s:o, s:O," /* f/wire */
+ " s:o, s:o," /* H_wire, h_contract_terms */
+ " s:o, s:o," /* coin_pub, denom_pub */
+ " s:o, s:o," /* ub_sig, timestamp */
+ " s:o," /* merchant_pub */
+ " s:o, s:o," /* refund_deadline, wire_deadline */
+ " s:o}", /* coin_sig */
+ "contribution", TALER_JSON_from_amount (amount),
+ "wire", wire_details,
+ "H_wire", GNUNET_JSON_from_data_auto (&h_wire),
+ "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms),
+ "coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
+ "denom_pub", GNUNET_JSON_from_rsa_public_key (denom_pub->rsa_public_key),
+ "ub_sig", GNUNET_JSON_from_rsa_signature (denom_sig->rsa_signature),
+ "timestamp", GNUNET_JSON_from_time_abs (timestamp),
+ "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub),
+ "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline),
+ "wire_transfer_deadline", GNUNET_JSON_from_time_abs (wire_deadline),
+ "coin_sig", GNUNET_JSON_from_data_auto (coin_sig)
+ );
+ if (NULL == deposit_obj)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ dh = GNUNET_new (struct TALER_EXCHANGE_DepositHandle);
+ dh->exchange = exchange;
+ dh->cb = cb;
+ dh->cb_cls = cb_cls;
+ dh->url = TEAH_path_to_url (exchange, "/deposit");
+ dh->depconf.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS));
+ dh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT);
+ dh->depconf.h_contract_terms = *h_contract_terms;
+ dh->depconf.h_wire = h_wire;
+ dh->depconf.timestamp = GNUNET_TIME_absolute_hton (timestamp);
+ dh->depconf.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
+ TALER_amount_hton (&dh->depconf.amount_without_fee,
+ &amount_without_fee);
+ dh->depconf.coin_pub = *coin_pub;
+ dh->depconf.merchant = *merchant_pub;
+ dh->amount_with_fee = *amount;
+ dh->coin_value = dki->value;
+
+ eh = TEL_curl_easy_get (dh->url);
+ GNUNET_assert (NULL != (dh->json_enc =
+ json_dumps (deposit_obj,
+ JSON_COMPACT)));
+ json_decref (deposit_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for deposit: `%s'\n",
+ dh->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ dh->json_enc));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ strlen (dh->json_enc)));
+ ctx = TEAH_handle_to_context (exchange);
+ dh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_deposit_finished,
+ dh);
+ return dh;
+}
+
+
+/**
+ * Cancel a deposit permission request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param deposit the deposit permission request handle
+ */
+void
+TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit)
+{
+ if (NULL != deposit->job)
+ {
+ GNUNET_CURL_job_cancel (deposit->job);
+ deposit->job = NULL;
+ }
+ GNUNET_free (deposit->url);
+ GNUNET_free (deposit->json_enc);
+ GNUNET_free (deposit);
+}
+
+
+/* end of exchange_api_deposit.c */
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
new file mode 100644
index 00000000..9743b1f0
--- /dev/null
+++ b/src/lib/exchange_api_handle.c
@@ -0,0 +1,1779 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/exchange_api_handle.c
+ * @brief Implementation of the "handle" component of the exchange's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "taler_auditor_service.h"
+#include "taler_signatures.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_curl_defaults.h"
+#include "backoff.h"
+
+/**
+ * Which revision of the Taler protocol is implemented
+ * by this library? Used to determine compatibility.
+ */
+#define TALER_PROTOCOL_CURRENT 2
+
+/**
+ * How many revisions back are we compatible to?
+ */
+#define TALER_PROTOCOL_AGE 0
+
+/**
+ * Current version for (local) JSON serialization of persisted
+ * /keys data.
+ */
+#define TALER_SERIALIZATION_FORMAT_VERSION 0
+
+
+/**
+ * Log error related to CURL operations.
+ *
+ * @param type log level
+ * @param function which function failed to run
+ * @param code what was the curl error code
+ */
+#define CURL_STRERROR(type, function, code) \
+ GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \
+ function, __FILE__, __LINE__, curl_easy_strerror (code));
+
+/**
+ * Stages of initialization for the `struct TALER_EXCHANGE_Handle`
+ */
+enum ExchangeHandleState
+{
+ /**
+ * Just allocated.
+ */
+ MHS_INIT = 0,
+
+ /**
+ * Obtained the exchange's certification data and keys.
+ */
+ MHS_CERT = 1,
+
+ /**
+ * Failed to initialize (fatal).
+ */
+ MHS_FAILED = 2
+};
+
+
+/**
+ * Data for the request to get the /keys of a exchange.
+ */
+struct KeysRequest;
+
+
+/**
+ * Entry in list of ongoing interactions with an auditor.
+ */
+struct AuditorInteractionEntry
+{
+ /**
+ * DLL entry.
+ */
+ struct AuditorInteractionEntry *next;
+
+ /**
+ * DLL entry.
+ */
+ struct AuditorInteractionEntry *prev;
+
+ /**
+ * Interaction state.
+ */
+ struct TALER_AUDITOR_DepositConfirmationHandle *dch;
+};
+
+
+/**
+ * Entry in DLL of auditors used by an exchange.
+ */
+struct AuditorListEntry
+{
+ /**
+ * Next pointer of DLL.
+ */
+ struct AuditorListEntry *next;
+
+ /**
+ * Prev pointer of DLL.
+ */
+ struct AuditorListEntry *prev;
+
+ /**
+ * Base URL of the auditor.
+ */
+ const char *auditor_url;
+
+ /**
+ * Handle to the auditor.
+ */
+ struct TALER_AUDITOR_Handle *ah;
+
+ /**
+ * Head of DLL of interactions with this auditor.
+ */
+ struct AuditorInteractionEntry *ai_head;
+
+ /**
+ * Tail of DLL of interactions with this auditor.
+ */
+ struct AuditorInteractionEntry *ai_tail;
+
+ /**
+ * Public key of the auditor.
+ */
+ struct TALER_AuditorPublicKeyP auditor_pub;
+
+ /**
+ * Flag indicating that the auditor is available and that protocol
+ * version compatibility is given.
+ */
+ int is_up;
+
+};
+
+
+/**
+ * Handle to the exchange
+ */
+struct TALER_EXCHANGE_Handle
+{
+ /**
+ * The context of this handle
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * The URL of the exchange (i.e. "http://exchange.taler.net/")
+ */
+ char *url;
+
+ /**
+ * Function to call with the exchange's certification data,
+ * NULL if this has already been done.
+ */
+ TALER_EXCHANGE_CertificationCallback cert_cb;
+
+ /**
+ * Closure to pass to @e cert_cb.
+ */
+ void *cert_cb_cls;
+
+ /**
+ * Data for the request to get the /keys of a exchange,
+ * NULL once we are past stage #MHS_INIT.
+ */
+ struct KeysRequest *kr;
+
+ /**
+ * Task for retrying /keys request.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * Raw key data of the exchange, only valid if
+ * @e handshake_complete is past stage #MHS_CERT.
+ */
+ json_t *key_data_raw;
+
+ /**
+ * Head of DLL of auditors of this exchange.
+ */
+ struct AuditorListEntry *auditors_head;
+
+ /**
+ * Tail of DLL of auditors of this exchange.
+ */
+ struct AuditorListEntry *auditors_tail;
+
+ /**
+ * Key data of the exchange, only valid if
+ * @e handshake_complete is past stage #MHS_CERT.
+ */
+ struct TALER_EXCHANGE_Keys key_data;
+
+ /**
+ * Retry /keys frequency.
+ */
+ struct GNUNET_TIME_Relative retry_delay;
+
+ /**
+ * When does @e key_data expire?
+ */
+ struct GNUNET_TIME_Absolute key_data_expiration;
+
+ /**
+ * Stage of the exchange's initialization routines.
+ */
+ enum ExchangeHandleState state;
+
+};
+
+
+/* ***************** Internal /keys fetching ************* */
+
+/**
+ * Data for the request to get the /keys of a exchange.
+ */
+struct KeysRequest
+{
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this handle
+ */
+ char *url;
+
+ /**
+ * Entry for this request with the `struct GNUNET_CURL_Context`.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Expiration time according to "Expire:" header.
+ * 0 if not provided by the server.
+ */
+ struct GNUNET_TIME_Absolute expire;
+
+};
+
+
+/**
+ * Iterate over all available auditors for @a h, calling
+ * @param ah and giving it a chance to start a deposit
+ * confirmation interaction.
+ *
+ * @param h exchange to go over auditors for
+ * @param ac function to call per auditor
+ * @param ac_cls closure for @a ac
+ */
+void
+TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h,
+ TEAH_AuditorCallback ac,
+ void *ac_cls)
+{
+ // FIXME!
+}
+
+
+/**
+ * Release memory occupied by a keys request.
+ * Note that this does not cancel the request
+ * itself.
+ *
+ * @param kr request to free
+ */
+static void
+free_keys_request (struct KeysRequest *kr)
+{
+ GNUNET_free (kr->url);
+ GNUNET_free (kr);
+}
+
+
+#define EXITIF(cond) \
+ do { \
+ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
+ } while (0)
+
+
+/**
+ * Parse a exchange's signing key encoded in JSON.
+ *
+ * @param[out] sign_key where to return the result
+ * @param check_sigs should we check signatures?
+ * @param[in] sign_key_obj json to parse
+ * @param master_key master key to use to verify signature
+ * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
+ * invalid or the json malformed.
+ */
+static int
+parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
+ int check_sigs,
+ json_t *sign_key_obj,
+ const struct TALER_MasterPublicKeyP *master_key)
+{
+ struct TALER_ExchangeSigningKeyValidityPS sign_key_issue;
+ struct TALER_MasterSignatureP sign_key_issue_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &sign_key_issue_sig),
+ GNUNET_JSON_spec_fixed_auto ("key",
+ &sign_key->key),
+ GNUNET_JSON_spec_absolute_time ("stamp_start",
+ &sign_key->valid_from),
+ GNUNET_JSON_spec_absolute_time ("stamp_expire",
+ &sign_key->valid_until),
+ GNUNET_JSON_spec_absolute_time ("stamp_end",
+ &sign_key->valid_legal),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (sign_key_obj,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (! check_sigs)
+ return GNUNET_OK;
+ sign_key_issue.signkey_pub = sign_key->key;
+ sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY);
+ sign_key_issue.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS));
+ sign_key_issue.master_public_key = *master_key;
+ sign_key_issue.start = GNUNET_TIME_absolute_hton (sign_key->valid_from);
+ sign_key_issue.expire = GNUNET_TIME_absolute_hton (sign_key->valid_until);
+ sign_key_issue.end = GNUNET_TIME_absolute_hton (sign_key->valid_legal);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY,
+ &sign_key_issue.purpose,
+ &sign_key_issue_sig.eddsa_signature,
+ &master_key->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ sign_key->master_sig = sign_key_issue_sig;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse a exchange's denomination key encoded in JSON.
+ *
+ * @param[out] denom_key where to return the result
+ * @param check_sigs should we check signatures?
+ * @param[in] denom_key_obj json to parse
+ * @param master_key master key to use to verify signature
+ * @param hash_context where to accumulate data for signature verification
+ * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
+ * invalid or the json malformed.
+ */
+static int
+parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key,
+ int check_sigs,
+ json_t *denom_key_obj,
+ struct TALER_MasterPublicKeyP *master_key,
+ struct GNUNET_HashContext *hash_context)
+{
+ struct TALER_DenominationKeyValidityPS denom_key_issue;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &denom_key->master_sig),
+ GNUNET_JSON_spec_absolute_time ("stamp_expire_deposit",
+ &denom_key->expire_deposit),
+ GNUNET_JSON_spec_absolute_time ("stamp_expire_withdraw",
+ &denom_key->withdraw_valid_until),
+ GNUNET_JSON_spec_absolute_time ("stamp_start",
+ &denom_key->valid_from),
+ GNUNET_JSON_spec_absolute_time ("stamp_expire_legal",
+ &denom_key->expire_legal),
+ TALER_JSON_spec_amount ("value",
+ &denom_key->value),
+ TALER_JSON_spec_amount ("fee_withdraw",
+ &denom_key->fee_withdraw),
+ TALER_JSON_spec_amount ("fee_deposit",
+ &denom_key->fee_deposit),
+ TALER_JSON_spec_amount ("fee_refresh",
+ &denom_key->fee_refresh),
+ TALER_JSON_spec_amount ("fee_refund",
+ &denom_key->fee_refund),
+ GNUNET_JSON_spec_rsa_public_key ("denom_pub",
+ &denom_key->key.rsa_public_key),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (denom_key_obj,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_CRYPTO_rsa_public_key_hash (denom_key->key.rsa_public_key,
+ &denom_key->h_key);
+ if (! check_sigs)
+ return GNUNET_OK;
+ memset (&denom_key_issue,
+ 0,
+ sizeof (denom_key_issue));
+ denom_key_issue.purpose.purpose
+ = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY);
+ denom_key_issue.purpose.size
+ = htonl (sizeof (struct TALER_DenominationKeyValidityPS));
+ denom_key_issue.master = *master_key;
+ denom_key_issue.denom_hash = denom_key->h_key;
+ denom_key_issue.start = GNUNET_TIME_absolute_hton (denom_key->valid_from);
+ denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (denom_key->withdraw_valid_until);
+ denom_key_issue.expire_deposit = GNUNET_TIME_absolute_hton (denom_key->expire_deposit);
+ denom_key_issue.expire_legal = GNUNET_TIME_absolute_hton (denom_key->expire_legal);
+ TALER_amount_hton (&denom_key_issue.value,
+ &denom_key->value);
+ TALER_amount_hton (&denom_key_issue.fee_withdraw,
+ &denom_key->fee_withdraw);
+ TALER_amount_hton (&denom_key_issue.fee_deposit,
+ &denom_key->fee_deposit);
+ TALER_amount_hton (&denom_key_issue.fee_refresh,
+ &denom_key->fee_refresh);
+ TALER_amount_hton (&denom_key_issue.fee_refund,
+ &denom_key->fee_refund);
+ EXITIF (GNUNET_SYSERR ==
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY,
+ &denom_key_issue.purpose,
+ &denom_key->master_sig.eddsa_signature,
+ &master_key->eddsa_pub));
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &denom_key_issue.denom_hash,
+ sizeof (struct GNUNET_HashCode));
+ return GNUNET_OK;
+
+ EXITIF_exit:
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Parse a exchange's auditor information encoded in JSON.
+ *
+ * @param[out] auditor where to return the result
+ * @param check_sig should we check signatures
+ * @param[in] auditor_obj json to parse
+ * @param key_data information about denomination keys
+ * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
+ * invalid or the json malformed.
+ */
+static int
+parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
+ int check_sigs,
+ json_t *auditor_obj,
+ const struct TALER_EXCHANGE_Keys *key_data)
+{
+ json_t *keys;
+ json_t *key;
+ unsigned int len;
+ unsigned int off;
+ unsigned int i;
+ const char *auditor_url;
+ struct TALER_ExchangeKeyValidityPS kv;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("auditor_pub",
+ &auditor->auditor_pub),
+ GNUNET_JSON_spec_string ("auditor_url",
+ &auditor_url),
+ GNUNET_JSON_spec_json ("denomination_keys",
+ &keys),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (auditor_obj,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ auditor->auditor_url = GNUNET_strdup (auditor_url);
+ kv.purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS);
+ kv.purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS));
+ GNUNET_CRYPTO_hash (auditor_url,
+ strlen (auditor_url) + 1,
+ &kv.auditor_url_hash);
+ kv.master = key_data->master_pub;
+ len = json_array_size (keys);
+ auditor->denom_keys = GNUNET_new_array (len,
+ struct TALER_EXCHANGE_AuditorDenominationInfo);
+ i = 0;
+ off = 0;
+ json_array_foreach (keys, i, key) {
+ struct TALER_AuditorSignatureP auditor_sig;
+ struct GNUNET_HashCode denom_h;
+ const struct TALER_EXCHANGE_DenomPublicKey *dk;
+ unsigned int dk_off;
+ struct GNUNET_JSON_Specification kspec[] = {
+ GNUNET_JSON_spec_fixed_auto ("auditor_sig",
+ &auditor_sig),
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_h",
+ &denom_h),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (key,
+ kspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ continue;
+ }
+ dk = NULL;
+ dk_off = UINT_MAX;
+ for (unsigned int j=0;j<key_data->num_denom_keys;j++)
+ {
+ if (0 == memcmp (&denom_h,
+ &key_data->denom_keys[j].h_key,
+ sizeof (struct GNUNET_HashCode)))
+ {
+ dk = &key_data->denom_keys[j];
+ dk_off = j;
+ break;
+ }
+ }
+ if (NULL == dk)
+ {
+ GNUNET_break_op (0);
+ continue;
+ }
+ if (check_sigs)
+ {
+ kv.start = GNUNET_TIME_absolute_hton (dk->valid_from);
+ kv.expire_withdraw = GNUNET_TIME_absolute_hton (dk->withdraw_valid_until);
+ kv.expire_deposit = GNUNET_TIME_absolute_hton (dk->expire_deposit);
+ kv.expire_legal = GNUNET_TIME_absolute_hton (dk->expire_legal);
+ TALER_amount_hton (&kv.value,
+ &dk->value);
+ TALER_amount_hton (&kv.fee_withdraw,
+ &dk->fee_withdraw);
+ TALER_amount_hton (&kv.fee_deposit,
+ &dk->fee_deposit);
+ TALER_amount_hton (&kv.fee_refresh,
+ &dk->fee_refresh);
+ TALER_amount_hton (&kv.fee_refund,
+ &dk->fee_refund);
+ kv.denom_hash = dk->h_key;
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS,
+ &kv.purpose,
+ &auditor_sig.eddsa_sig,
+ &auditor->auditor_pub.eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ }
+ auditor->denom_keys[off].denom_key_offset = dk_off;
+ auditor->denom_keys[off].auditor_sig = auditor_sig;
+ off++;
+ }
+ auditor->num_denom_keys = off;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Decode the JSON in @a resp_obj from the /keys response
+ * and store the data in the @a key_data.
+ *
+ * @param[in] resp_obj JSON object to parse
+ * @param check_sig #GNUNET_YES if we should check the signature
+ * @param[out] key_data where to store the results we decoded
+ * @param[out] where to store version compatibility data
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ * (malformed JSON)
+ */
+static int
+decode_keys_json (const json_t *resp_obj,
+ int check_sig,
+ struct TALER_EXCHANGE_Keys *key_data,
+ enum TALER_EXCHANGE_VersionCompatibility *vc)
+{
+ struct TALER_ExchangeSignatureP sig;
+ struct GNUNET_HashContext *hash_context;
+ struct TALER_ExchangePublicKeyP pub;
+ unsigned int age;
+ unsigned int revision;
+ unsigned int current;
+ struct GNUNET_JSON_Specification mspec[] = {
+ GNUNET_JSON_spec_fixed_auto ("eddsa_sig",
+ &sig),
+ GNUNET_JSON_spec_fixed_auto ("eddsa_pub",
+ &pub),
+ /* sig and pub must be first, as we skip those if
+ check_sig is false! */
+ GNUNET_JSON_spec_fixed_auto ("master_public_key",
+ &key_data->master_pub),
+ GNUNET_JSON_spec_absolute_time ("list_issue_date",
+ &key_data->list_issue_date),
+ GNUNET_JSON_spec_relative_time
+ ("reserve_closing_delay",
+ &key_data->reserve_closing_delay),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (JSON_OBJECT != json_typeof (resp_obj))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* check the version */
+ {
+ const char *ver;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("version",
+ &ver),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (resp_obj,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (3 != sscanf (ver,
+ "%u:%u:%u",
+ &current,
+ &revision,
+ &age))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ *vc = TALER_EXCHANGE_VC_MATCH;
+ if (TALER_PROTOCOL_CURRENT < current)
+ {
+ *vc |= TALER_EXCHANGE_VC_NEWER;
+ if (TALER_PROTOCOL_CURRENT < current - age)
+ *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
+ }
+ if (TALER_PROTOCOL_CURRENT > current)
+ {
+ *vc |= TALER_EXCHANGE_VC_OLDER;
+ if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current)
+ *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
+ }
+ key_data->version = GNUNET_strdup (ver);
+ }
+
+ hash_context = NULL;
+ EXITIF (GNUNET_OK !=
+ GNUNET_JSON_parse (resp_obj,
+ (check_sig) ? mspec : &mspec[2],
+ NULL, NULL));
+
+ /* parse the master public key and issue date of the response */
+ if (check_sig)
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+ /* parse the signing keys */
+ {
+ json_t *sign_keys_array;
+ json_t *sign_key_obj;
+ unsigned int index;
+
+ EXITIF (NULL == (sign_keys_array =
+ json_object_get (resp_obj,
+ "signkeys")));
+ EXITIF (JSON_ARRAY != json_typeof (sign_keys_array));
+ EXITIF (0 == (key_data->num_sign_keys =
+ json_array_size (sign_keys_array)));
+ key_data->sign_keys
+ = GNUNET_new_array (key_data->num_sign_keys,
+ struct TALER_EXCHANGE_SigningPublicKey);
+ index = 0;
+ json_array_foreach (sign_keys_array, index, sign_key_obj) {
+ EXITIF (GNUNET_SYSERR ==
+ parse_json_signkey (&key_data->sign_keys[index],
+ check_sig,
+ sign_key_obj,
+ &key_data->master_pub));
+ }
+ }
+
+ /* parse the denomination keys, merging with the
+ possibly EXISTING array as required (/keys cherry picking) */
+ {
+ json_t *denom_keys_array;
+ json_t *denom_key_obj;
+ unsigned int index;
+
+ EXITIF (NULL == (denom_keys_array =
+ json_object_get (resp_obj,
+ "denoms")));
+ EXITIF (JSON_ARRAY != json_typeof (denom_keys_array));
+
+ index = 0;
+ json_array_foreach (denom_keys_array, index, denom_key_obj) {
+ struct TALER_EXCHANGE_DenomPublicKey dk;
+ bool found = false;
+
+ EXITIF (GNUNET_SYSERR ==
+ parse_json_denomkey (&dk,
+ check_sig,
+ denom_key_obj,
+ &key_data->master_pub,
+ hash_context));
+ for (unsigned int j=0;j<key_data->num_denom_keys;j++)
+ {
+ if (0 == memcmp (&dk,
+ &key_data->denom_keys[j],
+ sizeof (dk)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ {
+ /* 0:0:0 did not support /keys cherry picking */
+ GNUNET_break_op (0 == current);
+ continue;
+ }
+ if (key_data->denom_keys_size == key_data->num_denom_keys)
+ GNUNET_array_grow (key_data->denom_keys,
+ key_data->denom_keys_size,
+ key_data->denom_keys_size * 2 + 2);
+ key_data->denom_keys[key_data->num_denom_keys++] = dk;
+
+ /* Update "last_denom_issue_date" */
+ TALER_LOG_DEBUG ("Crawling DK 'valid_from': %s\n",
+ GNUNET_STRINGS_absolute_time_to_string (dk.valid_from));
+ key_data->last_denom_issue_date
+ = GNUNET_TIME_absolute_max (key_data->last_denom_issue_date,
+ dk.valid_from);
+ };
+ }
+ /* parse the auditor information */
+ {
+ json_t *auditors_array;
+ json_t *auditor_info;
+ unsigned int index;
+
+ EXITIF (NULL == (auditors_array =
+ json_object_get (resp_obj,
+ "auditors")));
+ EXITIF (JSON_ARRAY != json_typeof (auditors_array));
+
+ /* Merge with the existing auditor information we have (/keys cherry picking) */
+ index = 0;
+ json_array_foreach (auditors_array, index, auditor_info) {
+ struct TALER_EXCHANGE_AuditorInformation ai;
+ bool found = false;
+
+ memset (&ai,
+ 0,
+ sizeof (ai));
+ EXITIF (GNUNET_SYSERR ==
+ parse_json_auditor (&ai,
+ check_sig,
+ auditor_info,
+ key_data));
+ for (unsigned int j=0;j<key_data->num_auditors;j++)
+ {
+ struct TALER_EXCHANGE_AuditorInformation *aix = &key_data->auditors[j];
+
+ if (0 == memcmp (&ai.auditor_pub,
+ &aix->auditor_pub,
+ sizeof (struct TALER_AuditorPublicKeyP)))
+ {
+ found = true;
+ /* Merge denomination key signatures of downloaded /keys into existing
+ auditor information 'aix'. */
+ GNUNET_array_grow (aix->denom_keys,
+ aix->num_denom_keys,
+ aix->num_denom_keys + ai.num_denom_keys);
+ memcpy (&aix->denom_keys[aix->num_denom_keys - ai.num_denom_keys],
+ ai.denom_keys,
+ ai.num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
+ break;
+ }
+ }
+ if (found)
+ continue; /* we are done */
+ if (key_data->auditors_size == key_data->num_auditors)
+ GNUNET_array_grow (key_data->auditors,
+ key_data->auditors_size,
+ key_data->auditors_size * 2 + 2);
+ key_data->auditors[key_data->num_auditors++] = ai;
+ };
+ }
+
+ if (check_sig)
+ {
+ struct TALER_ExchangeKeySetPS ks;
+
+ /* Validate signature... */
+ ks.purpose.size = htonl (sizeof (ks));
+ ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET);
+ ks.list_issue_date = GNUNET_TIME_absolute_hton (key_data->list_issue_date);
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &ks.hc);
+ hash_context = NULL;
+ EXITIF (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_data,
+ &pub));
+ EXITIF (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET,
+ &ks.purpose,
+ &sig.eddsa_signature,
+ &pub.eddsa_pub));
+ }
+ return GNUNET_OK;
+ EXITIF_exit:
+
+ if (NULL != hash_context)
+ GNUNET_CRYPTO_hash_context_abort (hash_context);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Free key data object.
+ *
+ * @param key_data data to free (pointer itself excluded)
+ */
+static void
+free_key_data (struct TALER_EXCHANGE_Keys *key_data)
+{
+ GNUNET_array_grow (key_data->sign_keys,
+ key_data->num_sign_keys,
+ 0);
+ for (unsigned int i=0;i<key_data->num_denom_keys;i++)
+ GNUNET_CRYPTO_rsa_public_key_free (key_data->denom_keys[i].key.rsa_public_key);
+
+ GNUNET_array_grow (key_data->denom_keys,
+ key_data->denom_keys_size,
+ 0);
+ for (unsigned int i=0;i<key_data->num_auditors;i++)
+ {
+ GNUNET_array_grow (key_data->auditors[i].denom_keys,
+ key_data->auditors[i].num_denom_keys,
+ 0);
+ GNUNET_free (key_data->auditors[i].auditor_url);
+ }
+ GNUNET_array_grow (key_data->auditors,
+ key_data->auditors_size,
+ 0);
+ GNUNET_free_non_null (key_data->version);
+ key_data->version = NULL;
+}
+
+
+/**
+ * Initiate download of /keys from the exchange.
+ *
+ * @param cls exchange where to download /keys from
+ */
+static void
+request_keys (void *cls);
+
+
+/**
+ * Check if our current response for /keys is valid, and if
+ * not trigger download.
+ *
+ * @param exchange exchange to check keys for
+ * @param force_download #GNUNET_YES to force download even if /keys is still valid
+ * @return until when the response is current, 0 if we are re-downloading
+ */
+struct GNUNET_TIME_Absolute
+TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange,
+ int force_download)
+{
+ if (NULL != exchange->kr)
+ return GNUNET_TIME_UNIT_ZERO_ABS;
+ if ( (GNUNET_NO == force_download) &&
+ (0 < GNUNET_TIME_absolute_get_remaining (exchange->key_data_expiration).rel_value_us) )
+ return exchange->key_data_expiration;
+ if (NULL == exchange->retry_task)
+ exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys,
+ exchange);
+ return GNUNET_TIME_UNIT_ZERO_ABS;
+}
+
+
+/**
+ * Callback used when downloading the reply to a /keys request
+ * is complete.
+ *
+ * @param cls the `struct KeysRequest`
+ * @param response_code HTTP response code, 0 on error
+ * @param resp_obj parsed JSON result, NULL on error
+ */
+static void
+keys_completed_cb (void *cls,
+ long response_code,
+ const void *resp_obj)
+{
+ struct KeysRequest *kr = cls;
+ struct TALER_EXCHANGE_Handle *exchange = kr->exchange;
+ struct TALER_EXCHANGE_Keys kd;
+ struct TALER_EXCHANGE_Keys kd_old;
+ enum TALER_EXCHANGE_VersionCompatibility vc;
+ const json_t *j = resp_obj;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received keys from URL `%s' with status %ld.\n",
+ kr->url,
+ response_code);
+ kd_old = exchange->key_data;
+ memset (&kd,
+ 0,
+ sizeof (struct TALER_EXCHANGE_Keys));
+ vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR;
+ switch (response_code)
+ {
+ case 0:
+ free_keys_request (kr);
+ exchange->kr = NULL;
+ GNUNET_assert (NULL == exchange->retry_task);
+ exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay);
+ exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay,
+ &request_keys,
+ exchange);
+ return;
+ case MHD_HTTP_OK:
+ if (NULL == j)
+ {
+ response_code = 0;
+ break;
+ }
+ /* We keep the denomination keys and auditor signatures from the
+ previous iteration (/keys cherry picking) */
+ kd.num_denom_keys = kd_old.num_denom_keys;
+ kd.last_denom_issue_date = kd_old.last_denom_issue_date;
+ GNUNET_array_grow (kd.denom_keys,
+ kd.denom_keys_size,
+ kd.num_denom_keys);
+
+ /* First make a shallow copy, we then need another pass for the RSA key... */
+ memcpy (kd.denom_keys,
+ kd_old.denom_keys,
+ kd_old.num_denom_keys * sizeof (struct TALER_EXCHANGE_DenomPublicKey));
+ for (unsigned int i=0;i<kd_old.num_denom_keys;i++)
+ kd.denom_keys[i].key.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_dup (kd_old.denom_keys[i].key.rsa_public_key);
+
+ kd.num_auditors = kd_old.num_auditors;
+ kd.auditors = GNUNET_new_array (kd.num_auditors,
+ struct TALER_EXCHANGE_AuditorInformation);
+ /* Now the necessary deep copy... */
+ for (unsigned int i=0;i<kd_old.num_auditors;i++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *aold = &kd_old.auditors[i];
+ struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i];
+
+ anew->auditor_pub = aold->auditor_pub;
+ anew->auditor_url = GNUNET_strdup (aold->auditor_url);
+ GNUNET_array_grow (anew->denom_keys,
+ anew->num_denom_keys,
+ aold->num_denom_keys);
+ memcpy (anew->denom_keys,
+ aold->denom_keys,
+ aold->num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
+ }
+
+ if (GNUNET_OK !=
+ decode_keys_json (j,
+ GNUNET_YES,
+ &kd,
+ &vc))
+ {
+ TALER_LOG_ERROR ("Could not decode /keys response\n");
+ response_code = 0;
+ for (unsigned int i=0;i<kd.num_auditors;i++)
+ {
+ struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i];
+
+ GNUNET_array_grow (anew->denom_keys,
+ anew->num_denom_keys,
+ 0);
+ GNUNET_free (anew->auditor_url);
+ }
+ GNUNET_free (kd.auditors);
+ kd.auditors = NULL;
+ kd.num_auditors = 0;
+ for (unsigned int i=0;i<kd_old.num_denom_keys;i++)
+ GNUNET_CRYPTO_rsa_public_key_free (kd.denom_keys[i].key.rsa_public_key);
+ GNUNET_array_grow (kd.denom_keys,
+ kd.denom_keys_size,
+ 0);
+ kd.num_denom_keys = 0;
+ break;
+ }
+ json_decref (exchange->key_data_raw);
+ exchange->key_data_raw = json_deep_copy (j);
+ exchange->retry_delay = GNUNET_TIME_UNIT_ZERO;
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ break;
+ }
+ exchange->key_data = kd;
+ TALER_LOG_DEBUG ("Last DK issue date update to: %s\n",
+ GNUNET_STRINGS_absolute_time_to_string
+ (exchange->key_data.last_denom_issue_date));
+
+
+ if (MHD_HTTP_OK != response_code)
+ {
+ exchange->kr = NULL;
+ free_keys_request (kr);
+ exchange->state = MHS_FAILED;
+ if (NULL != exchange->key_data_raw)
+ {
+ json_decref (exchange->key_data_raw);
+ exchange->key_data_raw = NULL;
+ }
+ free_key_data (&kd_old);
+ /* notify application that we failed */
+ exchange->cert_cb (exchange->cert_cb_cls,
+ NULL,
+ vc);
+ return;
+ }
+
+ exchange->kr = NULL;
+ exchange->key_data_expiration = kr->expire;
+ free_keys_request (kr);
+ exchange->state = MHS_CERT;
+ /* notify application about the key information */
+ exchange->cert_cb (exchange->cert_cb_cls,
+ &exchange->key_data,
+ vc);
+ free_key_data (&kd_old);
+}
+
+
+/* ********************* library internal API ********* */
+
+
+/**
+ * Get the context of a exchange.
+ *
+ * @param h the exchange handle to query
+ * @return ctx context to execute jobs in
+ */
+struct GNUNET_CURL_Context *
+TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h)
+{
+ return h->ctx;
+}
+
+
+/**
+ * Check if the handle is ready to process requests.
+ *
+ * @param h the exchange handle to query
+ * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
+ */
+int
+TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h)
+{
+ return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO;
+}
+
+
+/**
+ * Obtain the URL to use for an API request.
+ *
+ * @param h handle for the exchange
+ * @param path Taler API path (i.e. "/reserve/withdraw")
+ * @return the full URL to use with cURL
+ */
+char *
+TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h,
+ const char *path)
+{
+ return TEAH_path_to_url2 (h->url,
+ path);
+}
+
+
+/**
+ * Obtain the URL to use for an API request.
+ *
+ * @param base_url base URL of the exchange (i.e. "http://exchange/")
+ * @param path Taler API path (i.e. "/reserve/withdraw")
+ * @return the full URL to use with cURL
+ */
+char *
+TEAH_path_to_url2 (const char *base_url,
+ const char *path)
+{
+ char *url;
+
+ if ( ('/' == path[0]) &&
+ (0 < strlen (base_url)) &&
+ ('/' == base_url[strlen (base_url) - 1]) )
+ path++; /* avoid generating URL with "//" from concat */
+ GNUNET_asprintf (&url,
+ "%s%s",
+ base_url,
+ path);
+ return url;
+}
+
+
+/**
+ * Parse HTTP timestamp.
+ *
+ * @param date header to parse header
+ * @param at where to write the result
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_date_string (const char *date,
+ struct GNUNET_TIME_Absolute *at)
+{
+ struct tm now;
+ time_t t;
+ const char *end;
+
+ memset (&now,
+ 0,
+ sizeof (now));
+ end = strptime (date,
+ "%a, %d %b %Y %H:%M:%S %Z", /* RFC-1123 standard spec */
+ &now);
+ if ( (NULL == end) ||
+ ( (*end != '\n') &&
+ (*end != '\r') ) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ t = mktime (&now);
+ if (((time_t) -1) == t)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "mktime");
+ return GNUNET_SYSERR;
+ }
+ if (t < 0)
+ t = 0; /* can happen due to timezone issues if date was 1.1.1970 */
+ at->abs_value_us = 1000LL * 1000LL * t;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called for each header in the HTTP /keys response.
+ * Finds the "Expire:" header and parses it, storing the result
+ * in the "expire" field fo the keys request.
+ *
+ * @param buffer header data received
+ * @param size size of an item in @a buffer
+ * @param nitems number of items in @a buffer
+ * @param userdata the `struct KeysRequest`
+ * @return `size * nitems` on success (everything else aborts)
+ */
+static size_t
+header_cb (char *buffer,
+ size_t size,
+ size_t nitems,
+ void *userdata)
+{
+ struct KeysRequest *kr = userdata;
+ size_t total = size * nitems;
+ char *val;
+
+ if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": "))
+ return total;
+ if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ",
+ buffer,
+ strlen (MHD_HTTP_HEADER_EXPIRES ": ")))
+ return total;
+ val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")],
+ total - strlen (MHD_HTTP_HEADER_EXPIRES ": "));
+ if (GNUNET_OK !=
+ parse_date_string (val,
+ &kr->expire))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s-header `%s'\n",
+ MHD_HTTP_HEADER_EXPIRES,
+ val);
+ kr->expire = GNUNET_TIME_UNIT_ZERO_ABS;
+ }
+ GNUNET_free (val);
+ return total;
+}
+
+
+/* ********************* public API ******************* */
+
+
+/**
+ * Deserialize the key data and use it to bootstrap @a exchange to
+ * more efficiently recover the state. Errors in @a data must be
+ * tolerated (i.e. by re-downloading instead).
+ *
+ * @param exchange which exchange's key and wire data should be deserialized
+ * @return data the data to deserialize
+ */
+static void
+deserialize_data (struct TALER_EXCHANGE_Handle *exchange,
+ const json_t *data)
+{
+ enum TALER_EXCHANGE_VersionCompatibility vc;
+ json_t *keys;
+ const char *url;
+ uint32_t version;
+ struct GNUNET_TIME_Absolute expire;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint32 ("version",
+ &version),
+ GNUNET_JSON_spec_json ("keys",
+ &keys),
+ GNUNET_JSON_spec_string ("url",
+ &url),
+ GNUNET_JSON_spec_absolute_time ("expire",
+ &expire),
+ GNUNET_JSON_spec_end()
+ };
+ struct TALER_EXCHANGE_Keys key_data;
+
+ if (NULL == data)
+ return;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (data,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return;
+ }
+ if (0 != version)
+ return; /* unsupported version */
+ if (0 != strcmp (url,
+ exchange->url))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ memset (&key_data,
+ 0,
+ sizeof (struct TALER_EXCHANGE_Keys));
+ if (GNUNET_OK !=
+ decode_keys_json (keys,
+ GNUNET_NO,
+ &key_data,
+ &vc))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ /* decode successful, initialize with the result */
+ GNUNET_assert (NULL == exchange->key_data_raw);
+ exchange->key_data_raw = json_deep_copy (keys);
+ exchange->key_data = key_data;
+ exchange->key_data_expiration = expire;
+ exchange->state = MHS_CERT;
+ /* notify application about the key information */
+ exchange->cert_cb (exchange->cert_cb_cls,
+ &exchange->key_data,
+ vc);
+}
+
+
+/**
+ * Serialize the latest key data from @a exchange to be persisted on
+ * disk (to be used with #TALER_EXCHANGE_OPTION_DATA to more
+ * efficiently recover the state).
+ *
+ * @param exchange which exchange's key and wire data should be
+ * serialized
+ * @return NULL on error (i.e. no current data available);
+ * otherwise JSON object owned by the caller
+ */
+json_t *
+TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange)
+{
+ const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data;
+ struct GNUNET_TIME_Absolute now;
+ json_t *keys;
+ json_t *signkeys;
+ json_t *denoms;
+ json_t *auditors;
+
+ now = GNUNET_TIME_absolute_get ();
+ signkeys = json_array ();
+ for (unsigned int i=0;i<kd->num_sign_keys;i++)
+ {
+ const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i];
+ json_t *signkey;
+
+ if (now.abs_value_us > sk->valid_until.abs_value_us)
+ continue; /* skip keys that have expired */
+ signkey = json_pack ("{s:o, s:o, s:o, s:o, s:o}",
+ "key",
+ GNUNET_JSON_from_data_auto (&sk->key),
+ "master_sig",
+ GNUNET_JSON_from_data_auto (&sk->master_sig),
+ "stamp_start",
+ GNUNET_JSON_from_time_abs (sk->valid_from),
+ "stamp_expire",
+ GNUNET_JSON_from_time_abs (sk->valid_until),
+ "stamp_end",
+ GNUNET_JSON_from_time_abs (sk->valid_legal));
+ if (NULL == signkey)
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ json_array_append_new (signkeys,
+ signkey);
+ }
+ denoms = json_array ();
+ for (unsigned int i=0;i<kd->num_denom_keys;i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i];
+ json_t *denom;
+
+ if (now.abs_value_us > dk->expire_deposit.abs_value_us)
+ continue; /* skip keys that have expired */
+ denom = json_pack ("{s:o, s:o, s:o, s:o, s:o "
+ ",s:o, s:o, s:o, s:o, s:o "
+ ",s:o}",
+ "stamp_expire_deposit",
+ GNUNET_JSON_from_time_abs (dk->expire_deposit),
+ "stamp_expire_withdraw",
+ GNUNET_JSON_from_time_abs (dk->withdraw_valid_until),
+ "stamp_start",
+ GNUNET_JSON_from_time_abs (dk->valid_from),
+ "stamp_expire_legal",
+ GNUNET_JSON_from_time_abs (dk->expire_legal),
+ "value",
+ TALER_JSON_from_amount (&dk->value),
+ "fee_withdraw",
+ /* #6 */
+ TALER_JSON_from_amount (&dk->fee_withdraw),
+ "fee_deposit",
+ TALER_JSON_from_amount (&dk->fee_deposit),
+ "fee_refresh",
+ TALER_JSON_from_amount (&dk->fee_refresh),
+ "fee_refund",
+ TALER_JSON_from_amount (&dk->fee_refund),
+ "master_sig",
+ GNUNET_JSON_from_data_auto (&dk->master_sig),
+ /* #10 */
+ "denom_pub",
+ GNUNET_JSON_from_rsa_public_key (dk->key.rsa_public_key));
+ if (NULL == denom)
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ json_array_append_new (denoms,
+ denom);
+ }
+ auditors = json_array ();
+ for (unsigned int i=0;i<kd->num_auditors;i++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i];
+ json_t *a;
+ json_t *adenoms;
+
+ adenoms = json_array ();
+ for (unsigned int j=0;j<ai->num_denom_keys;j++)
+ {
+ const struct TALER_EXCHANGE_AuditorDenominationInfo *adi = &ai->denom_keys[j];
+ const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[adi->denom_key_offset];
+ json_t *k;
+
+ if (now.abs_value_us > dk->expire_deposit.abs_value_us)
+ continue; /* skip auditor signatures for denomination keys that have expired */
+ GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
+ k = json_pack ("{s:o, s:o}",
+ "denom_pub_h",
+ GNUNET_JSON_from_data_auto (&dk->h_key),
+ "auditor_sig",
+ GNUNET_JSON_from_data_auto (&adi->auditor_sig));
+ if (NULL == k)
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ json_array_append_new (adenoms,
+ k);
+ }
+
+ a = json_pack ("{s:o, s:s, s:o}",
+ "auditor_pub",
+ GNUNET_JSON_from_data_auto (&ai->auditor_pub),
+ "auditor_url",
+ ai->auditor_url,
+ "denomination_keys",
+ adenoms);
+ if (NULL == a)
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ json_array_append_new (auditors,
+ a);
+ }
+ keys = json_pack ("{s:s, s:o, s:o, s:o, s:o"
+ ",s:o, s:o}",
+ /* 1 */
+ "version",
+ kd->version,
+ "master_public_key",
+ GNUNET_JSON_from_data_auto (&kd->master_pub),
+ "reserve_closing_delay",
+ GNUNET_JSON_from_time_rel (kd->reserve_closing_delay),
+ "list_issue_date",
+ GNUNET_JSON_from_time_abs (kd->list_issue_date),
+ "signkeys",
+ signkeys,
+ /* #6 */
+ "denoms",
+ denoms,
+ "auditors",
+ auditors);
+ if (NULL == keys)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ return json_pack ("{s:I, s:o, s:s, s:o}",
+ "version",
+ (json_int_t) TALER_SERIALIZATION_FORMAT_VERSION,
+ "expire",
+ GNUNET_JSON_from_time_abs (exchange->key_data_expiration),
+ "url",
+ exchange->url,
+ "keys",
+ keys);
+}
+
+
+/**
+ * Initialise a connection to the exchange. Will connect to the
+ * exchange and obtain information about the exchange's master
+ * public key and the exchange's auditor.
+ * The respective information will be passed to the @a cert_cb
+ * once available, and all future interactions with the exchange
+ * will be checked to be signed (where appropriate) by the
+ * respective master key.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param cert_cb function to call with the exchange's
+ * certification information
+ * @param cert_cb_cls closure for @a cert_cb
+ * @param ... list of additional arguments,
+ * terminated by #TALER_EXCHANGE_OPTION_END.
+ * @return the exchange handle; NULL upon error
+ */
+struct TALER_EXCHANGE_Handle *
+TALER_EXCHANGE_connect
+ (struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ TALER_EXCHANGE_CertificationCallback cert_cb,
+ void *cert_cb_cls,
+ ...)
+{
+ struct TALER_EXCHANGE_Handle *exchange;
+ va_list ap;
+ enum TALER_EXCHANGE_Option opt;
+
+ exchange = GNUNET_new (struct TALER_EXCHANGE_Handle);
+ exchange->ctx = ctx;
+ exchange->url = GNUNET_strdup (url);
+ exchange->cert_cb = cert_cb;
+ exchange->cert_cb_cls = cert_cb_cls;
+ exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys,
+ exchange);
+ va_start (ap, cert_cb_cls);
+ while (TALER_EXCHANGE_OPTION_END !=
+ (opt = va_arg (ap, int)))
+ {
+ switch (opt) {
+ case TALER_EXCHANGE_OPTION_END:
+ GNUNET_assert (0);
+ break;
+ case TALER_EXCHANGE_OPTION_DATA:
+ {
+ const json_t *data = va_arg (ap, const json_t *);
+
+ deserialize_data (exchange,
+ data);
+ break;
+ }
+ default:
+ GNUNET_assert (0);
+ break;
+ }
+ }
+ va_end (ap);
+ return exchange;
+}
+
+
+/**
+ * Initiate download of /keys from the exchange.
+ *
+ * @param cls exchange where to download /keys from
+ */
+static void
+request_keys (void *cls)
+{
+ struct TALER_EXCHANGE_Handle *exchange = cls;
+ struct KeysRequest *kr;
+ CURL *eh;
+
+ exchange->retry_task = NULL;
+ GNUNET_assert (NULL == exchange->kr);
+ kr = GNUNET_new (struct KeysRequest);
+ kr->exchange = exchange;
+ if (GNUNET_YES ==
+ TEAH_handle_is_ready (exchange))
+ {
+ char *arg;
+
+ TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
+ GNUNET_STRINGS_absolute_time_to_string (exchange->key_data.last_denom_issue_date));
+ GNUNET_asprintf (&arg,
+ "/keys?last_issue_date=%llu",
+ (unsigned long long) exchange->key_data.last_denom_issue_date.abs_value_us / 1000000LLU);
+ kr->url = TEAH_path_to_url (exchange,
+ arg);
+ GNUNET_free (arg);
+ }
+ else
+ {
+ kr->url = TEAH_path_to_url (exchange,
+ "/keys");
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting keys with URL `%s'.\n",
+ kr->url);
+ eh = TEL_curl_easy_get (kr->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT,
+ (long) 300));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERFUNCTION,
+ &header_cb));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ kr));
+ kr->job = GNUNET_CURL_job_add (exchange->ctx,
+ eh,
+ GNUNET_YES,
+ &keys_completed_cb,
+ kr);
+ exchange->kr = kr;
+}
+
+
+/**
+ * Disconnect from the exchange
+ *
+ * @param exchange the exchange handle
+ */
+void
+TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange)
+{
+ if (NULL != exchange->kr)
+ {
+ GNUNET_CURL_job_cancel (exchange->kr->job);
+ free_keys_request (exchange->kr);
+ exchange->kr = NULL;
+ }
+ free_key_data (&exchange->key_data);
+ if (NULL != exchange->key_data_raw)
+ {
+ json_decref (exchange->key_data_raw);
+ exchange->key_data_raw = NULL;
+ }
+ if (NULL != exchange->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (exchange->retry_task);
+ exchange->retry_task = NULL;
+ }
+ GNUNET_free (exchange->url);
+ GNUNET_free (exchange);
+}
+
+
+/**
+ * Lookup the given @a pub in @a keys.
+ *
+ * @param keys the exchange's key set
+ * @param pub claimed current online signing key for the exchange
+ * @return NULL if @a pub was not found
+ */
+const struct TALER_EXCHANGE_SigningPublicKey *
+TALER_EXCHANGE_get_signing_key_details (const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ExchangePublicKeyP *pub)
+{
+ for (unsigned int i=0;i<keys->num_sign_keys;i++)
+ {
+ struct TALER_EXCHANGE_SigningPublicKey *spk = &keys->sign_keys[i];
+
+ if (0 == memcmp (pub,
+ &spk->key,
+ sizeof (struct TALER_ExchangePublicKeyP)))
+ return spk;
+ }
+ return NULL;
+}
+
+
+/**
+ * Test if the given @a pub is a the current signing key from the exchange
+ * according to @a keys.
+ *
+ * @param keys the exchange's key set
+ * @param pub claimed current online signing key for the exchange
+ * @return #GNUNET_OK if @a pub is (according to /keys) a current signing key
+ */
+int
+TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ExchangePublicKeyP *pub)
+{
+ struct GNUNET_TIME_Absolute now;
+
+ /* we will check using a tolerance of 1h for the time */
+ now = GNUNET_TIME_absolute_get ();
+ for (unsigned int i=0;i<keys->num_sign_keys;i++)
+ if ( (keys->sign_keys[i].valid_from.abs_value_us <= now.abs_value_us + 60 * 60 * 1000LL * 1000LL) &&
+ (keys->sign_keys[i].valid_until.abs_value_us > now.abs_value_us - 60 * 60 * 1000LL * 1000LL) &&
+ (0 == memcmp (pub,
+ &keys->sign_keys[i].key,
+ sizeof (struct TALER_ExchangePublicKeyP))) )
+ return GNUNET_OK;
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Get exchange's base URL.
+ *
+ * @param exchange exchange handle.
+ * @return the base URL from the handle.
+ */
+const char *
+TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange)
+{
+ return exchange->url;
+}
+
+
+/**
+ * Obtain the denomination key details from the exchange.
+ *
+ * @param keys the exchange's key set
+ * @param pk public key of the denomination to lookup
+ * @return details about the given denomination key, NULL if the key is
+ * not found
+ */
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_EXCHANGE_get_denomination_key (const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_DenominationPublicKey *pk)
+{
+ for (unsigned int i=0;i<keys->num_denom_keys;i++)
+ if (0 == GNUNET_CRYPTO_rsa_public_key_cmp (pk->rsa_public_key,
+ keys->denom_keys[i].key.rsa_public_key))
+ return &keys->denom_keys[i];
+ return NULL;
+}
+
+
+/**
+ * Obtain the denomination key details from the exchange.
+ *
+ * @param keys the exchange's key set
+ * @param hc hash of the public key of the denomination to lookup
+ * @return details about the given denomination key
+ */
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_EXCHANGE_get_denomination_key_by_hash (const struct TALER_EXCHANGE_Keys *keys,
+ const struct GNUNET_HashCode *hc)
+{
+ for (unsigned int i=0;i<keys->num_denom_keys;i++)
+ if (0 == memcmp (hc,
+ &keys->denom_keys[i].h_key,
+ sizeof (struct GNUNET_HashCode)))
+ return &keys->denom_keys[i];
+ return NULL;
+}
+
+
+/**
+ * Obtain the keys from the exchange.
+ *
+ * @param exchange the exchange handle
+ * @return the exchange's key set
+ */
+const struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange)
+{
+ (void) TALER_EXCHANGE_check_keys_current (exchange,
+ GNUNET_NO);
+ return &exchange->key_data;
+}
+
+
+/**
+ * Obtain the keys from the exchange in the
+ * raw JSON format
+ *
+ * @param exchange the exchange handle
+ * @return the exchange's keys in raw JSON
+ */
+json_t *
+TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange)
+{
+ (void) TALER_EXCHANGE_check_keys_current (exchange,
+ GNUNET_NO);
+ return json_deep_copy (exchange->key_data_raw);
+}
+
+
+/* end of exchange_api_handle.c */
diff --git a/src/lib/exchange_api_handle.h b/src/lib/exchange_api_handle.h
new file mode 100644
index 00000000..f06fa4ee
--- /dev/null
+++ b/src/lib/exchange_api_handle.h
@@ -0,0 +1,103 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-lib/exchange_api_handle.h
+ * @brief Internal interface to the handle part of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_auditor_service.h"
+#include "taler_exchange_service.h"
+#include "taler_crypto_lib.h"
+
+
+/**
+ * Function called for each auditor to give us a chance to possibly
+ * launch a deposit confirmation interaction.
+ *
+ * @param cls closure
+ * @param ah handle to the auditor
+ * @param auditor_pub public key of the auditor
+ * @return NULL if no deposit confirmation interaction was launched
+ */
+typedef struct TALER_AUDITOR_DepositConfirmationHandle *
+(*TEAH_AuditorCallback)(void *cls,
+ struct TALER_AUDITOR_Handle *ah,
+ const struct TALER_AuditorPublicKeyP *auditor_pub);
+
+
+/**
+ * Iterate over all available auditors for @a h, calling
+ * @param ah and giving it a chance to start a deposit
+ * confirmation interaction.
+ *
+ * @param h exchange to go over auditors for
+ * @param ac function to call per auditor
+ * @param ac_cls closure for @a ac
+ */
+void
+TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h,
+ TEAH_AuditorCallback ac,
+ void *ac_cls);
+
+
+/**
+ * Get the context of a exchange.
+ *
+ * @param h the exchange handle to query
+ * @return ctx context to execute jobs in
+ */
+struct GNUNET_CURL_Context *
+TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h);
+
+
+/**
+ * Check if the handle is ready to process requests.
+ *
+ * @param h the exchange handle to query
+ * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
+ */
+int
+TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h);
+
+
+/**
+ * Obtain the URL to use for an API request.
+ *
+ * @param h the exchange handle to query
+ * @param path Taler API path (i.e. "/reserve/withdraw")
+ * @return the full URL to use with cURL
+ */
+char *
+TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h,
+ const char *path);
+
+
+/**
+ * Obtain the URL to use for an API request.
+ *
+ * @param base_url base URL of the exchange (i.e. "http://exchange/")
+ * @param path Taler API path (i.e. "/reserve/withdraw")
+ * @return the full URL to use with cURL
+ */
+char *
+TEAH_path_to_url2 (const char *base_url,
+ const char *path);
+
+
+/* end of exchange_api_handle.h */
diff --git a/src/lib/exchange_api_payback.c b/src/lib/exchange_api_payback.c
new file mode 100644
index 00000000..6c1772af
--- /dev/null
+++ b/src/lib/exchange_api_payback.c
@@ -0,0 +1,374 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017 GNUnet e.V. and Inria
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-lib/exchange_api_payback.c
+ * @brief Implementation of the /payback request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Payback Handle
+ */
+struct TALER_EXCHANGE_PaybackHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * JSON encoding of the request to POST.
+ */
+ char *json_enc;
+
+ /**
+ * Denomination key of the coin.
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_PaybackResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Public key of the coin we are trying to get paid back.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+};
+
+
+/**
+ * Verify that the signature on the "200 OK" response
+ * from the exchange is valid. If it is, call the
+ * callback.
+ *
+ * @param ph payback handle
+ * @param json json reply with the signature
+ * @return #GNUNET_OK if the signature is valid and we called the callback;
+ * #GNUNET_SYSERR if not (callback must still be called)
+ */
+static int
+verify_payback_signature_ok (const struct TALER_EXCHANGE_PaybackHandle *ph,
+ const json_t *json)
+{
+ struct TALER_PaybackConfirmationPS pc;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_Amount amount;
+ struct GNUNET_TIME_Absolute timestamp;
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub),
+ TALER_JSON_spec_amount ("amount", &amount),
+ GNUNET_JSON_spec_absolute_time ("timestamp", &timestamp),
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub", &pc.reserve_pub),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ key_state = TALER_EXCHANGE_get_keys (ph->exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK);
+ pc.purpose.size = htonl (sizeof (pc));
+ pc.timestamp = GNUNET_TIME_absolute_hton (timestamp);
+ TALER_amount_hton (&pc.payback_amount,
+ &amount);
+ pc.coin_pub = ph->coin_pub;
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK,
+ &pc.purpose,
+ &exchange_sig.eddsa_signature,
+ &exchange_pub.eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ ph->cb (ph->cb_cls,
+ MHD_HTTP_OK,
+ TALER_EC_NONE,
+ &amount,
+ timestamp,
+ &pc.reserve_pub,
+ json);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /payback request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_PaybackHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_payback_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PaybackHandle *ph = cls;
+ const json_t *j = response;
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ verify_payback_signature_ok (ph,
+ j))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ TALER_EXCHANGE_payback_cancel (ph);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ {
+ /* Insufficient funds, proof attached */
+ json_t *history;
+ struct TALER_Amount total;
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+
+ dki = ph->pk;
+ history = json_object_get (j,
+ "history");
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_verify_coin_history (dki->fee_deposit.currency,
+ &ph->coin_pub,
+ history,
+ &total))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ ph->cb (ph->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ &total,
+ GNUNET_TIME_UNIT_FOREVER_ABS,
+ NULL,
+ j);
+ TALER_EXCHANGE_payback_cancel (ph);
+ return;
+ }
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_GONE:
+ /* Kind of normal: the money was already sent to the merchant
+ (it was too late for the refund). */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ ph->cb (ph->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ NULL,
+ GNUNET_TIME_UNIT_FOREVER_ABS,
+ NULL,
+ j);
+ TALER_EXCHANGE_payback_cancel (ph);
+}
+
+
+/**
+ * Ask the exchange to pay back a coin due to the exchange triggering
+ * the emergency payback protocol for a given denomination. The value
+ * of the coin will be refunded to the original customer (without fees).
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param pk kind of coin to pay back
+ * @param denom_sig signature over the coin by the exchange using @a pk
+ * @param ps secret internals of the original planchet
+ * @param payback_cb the callback to call when the final result for this request is available
+ * @param payback_cb_cls closure for @a payback_cb
+ * @return NULL
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_PaybackHandle *
+TALER_EXCHANGE_payback (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_PlanchetSecretsP *ps,
+ TALER_EXCHANGE_PaybackResultCallback payback_cb,
+ void *payback_cb_cls)
+{
+ struct TALER_EXCHANGE_PaybackHandle *ph;
+ struct GNUNET_CURL_Context *ctx;
+ struct TALER_PaybackRequestPS pr;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ json_t *payback_obj;
+ CURL *eh;
+
+ GNUNET_assert (GNUNET_YES ==
+ TEAH_handle_is_ready (exchange));
+ pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_PAYBACK);
+ pr.purpose.size = htonl (sizeof (struct TALER_PaybackRequestPS));
+ GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv,
+ &pr.coin_pub.eddsa_pub);
+ pr.h_denom_pub = pk->h_key;
+ pr.coin_blind = ps->blinding_key;
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign (&ps->coin_priv.eddsa_priv,
+ &pr.purpose,
+ &coin_sig.eddsa_signature));
+
+ payback_obj = json_pack ("{s:o, s:o," /* denom pub/sig */
+ " s:o, s:o," /* coin pub/sig */
+ " s:o}", /* coin_bks */
+ "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->key.rsa_public_key),
+ "denom_sig", GNUNET_JSON_from_rsa_signature (denom_sig->rsa_signature),
+ "coin_pub", GNUNET_JSON_from_data_auto (&pr.coin_pub),
+ "coin_sig", GNUNET_JSON_from_data_auto (&coin_sig),
+ "coin_blind_key_secret", GNUNET_JSON_from_data_auto (&ps->blinding_key)
+ );
+ if (NULL == payback_obj)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ ph = GNUNET_new (struct TALER_EXCHANGE_PaybackHandle);
+ ph->coin_pub = pr.coin_pub;
+ ph->exchange = exchange;
+ ph->pk = pk;
+ ph->cb = payback_cb;
+ ph->cb_cls = payback_cb_cls;
+ ph->url = TEAH_path_to_url (exchange, "/payback");
+
+ ph->json_enc = json_dumps (payback_obj,
+ JSON_COMPACT);
+ json_decref (payback_obj);
+ if (NULL == ph->json_enc)
+ {
+ GNUNET_break (0);
+ GNUNET_free (ph->url);
+ GNUNET_free (ph);
+ return NULL;
+ }
+ eh = TEL_curl_easy_get (ph->url);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for payback: `%s'\n",
+ ph->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ ph->json_enc));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ strlen (ph->json_enc)));
+ ctx = TEAH_handle_to_context (exchange);
+ ph->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_payback_finished,
+ ph);
+ return ph;
+}
+
+
+/**
+ * Cancel a payback request. This function cannot be used on a
+ * request handle if the callback was already invoked.
+ *
+ * @param ph the payback handle
+ */
+void
+TALER_EXCHANGE_payback_cancel (struct TALER_EXCHANGE_PaybackHandle *ph)
+{
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ GNUNET_free (ph->url);
+ GNUNET_free (ph->json_enc);
+ GNUNET_free (ph);
+}
+
+
+/* end of exchange_api_payback.c */
diff --git a/src/lib/exchange_api_refresh.c b/src/lib/exchange_api_refresh.c
new file mode 100644
index 00000000..b766b795
--- /dev/null
+++ b/src/lib/exchange_api_refresh.c
@@ -0,0 +1,1678 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015, 2016, 2017 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-lib/exchange_api_refresh.c
+ * @brief Implementation of the /refresh/melt+reveal requests of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/* ********************* /refresh/ common ***************************** */
+
+/* structures for committing refresh data to disk before doing the
+ network interaction(s) */
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Header of serialized information about a coin we are melting.
+ */
+struct MeltedCoinP
+{
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Amount this coin contributes to the melt, including fee.
+ */
+ struct TALER_AmountNBO melt_amount_with_fee;
+
+ /**
+ * The applicable fee for withdrawing a coin of this denomination
+ */
+ struct TALER_AmountNBO fee_melt;
+
+ /**
+ * The original value of the coin.
+ */
+ struct TALER_AmountNBO original_value;
+
+ /**
+ * Transfer private keys for each cut-and-choose dimension.
+ */
+ struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA];
+
+ /**
+ * Timestamp indicating when coins of this denomination become invalid.
+ */
+ struct GNUNET_TIME_AbsoluteNBO expire_deposit;
+
+ /**
+ * Size of the encoded public key that follows.
+ */
+ uint16_t pbuf_size;
+
+ /**
+ * Size of the encoded signature that follows.
+ */
+ uint16_t sbuf_size;
+
+ /* Followed by serializations of:
+ 1) struct TALER_DenominationPublicKey pub_key;
+ 2) struct TALER_DenominationSignature sig;
+ */
+};
+
+
+/**
+ * Header of serialized data about a melt operation, suitable for
+ * persisting it on disk.
+ */
+struct MeltDataP
+{
+
+ /**
+ * Hash over the melting session.
+ */
+ struct TALER_RefreshCommitmentP rc;
+
+ /**
+ * Number of coins we are melting, in NBO
+ */
+ uint16_t num_melted_coins GNUNET_PACKED;
+
+ /**
+ * Number of coins we are creating, in NBO
+ */
+ uint16_t num_fresh_coins GNUNET_PACKED;
+
+ /* Followed by serializations of:
+ 1) struct MeltedCoinP melted_coins[num_melted_coins];
+ 2) struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins];
+ 3) TALER_CNC_KAPPA times:
+ 3a) struct TALER_PlanchetSecretsP fresh_coins[num_fresh_coins];
+ */
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+/**
+ * Information about a coin we are melting.
+ */
+struct MeltedCoin
+{
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Amount this coin contributes to the melt, including fee.
+ */
+ struct TALER_Amount melt_amount_with_fee;
+
+ /**
+ * The applicable fee for melting a coin of this denomination
+ */
+ struct TALER_Amount fee_melt;
+
+ /**
+ * The original value of the coin.
+ */
+ struct TALER_Amount original_value;
+
+ /**
+ * Transfer private keys for each cut-and-choose dimension.
+ */
+ struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA];
+
+ /**
+ * Timestamp indicating when coins of this denomination become invalid.
+ */
+ struct GNUNET_TIME_Absolute expire_deposit;
+
+ /**
+ * Denomination key of the original coin.
+ */
+ struct TALER_DenominationPublicKey pub_key;
+
+ /**
+ * Exchange's signature over the coin.
+ */
+ struct TALER_DenominationSignature sig;
+
+};
+
+
+/**
+ * Melt data in non-serialized format for convenient processing.
+ */
+struct MeltData
+{
+
+ /**
+ * Hash over the committed data during refresh operation.
+ */
+ struct TALER_RefreshCommitmentP rc;
+
+ /**
+ * Number of coins we are creating
+ */
+ uint16_t num_fresh_coins;
+
+ /**
+ * Information about the melted coin.
+ */
+ struct MeltedCoin melted_coin;
+
+ /**
+ * Array of @e num_fresh_coins denomination keys for the coins to be
+ * freshly exchangeed.
+ */
+ struct TALER_DenominationPublicKey *fresh_pks;
+
+ /**
+ * Arrays of @e num_fresh_coins with information about the fresh
+ * coins to be created, for each cut-and-choose dimension.
+ */
+ struct TALER_PlanchetSecretsP *fresh_coins[TALER_CNC_KAPPA];
+};
+
+
+/**
+ * Free all information associated with a melted coin session.
+ *
+ * @param mc melted coin to release, the pointer itself is NOT
+ * freed (as it is typically not allocated by itself)
+ */
+static void
+free_melted_coin (struct MeltedCoin *mc)
+{
+ if (NULL != mc->pub_key.rsa_public_key)
+ GNUNET_CRYPTO_rsa_public_key_free (mc->pub_key.rsa_public_key);
+ if (NULL != mc->sig.rsa_signature)
+ GNUNET_CRYPTO_rsa_signature_free (mc->sig.rsa_signature);
+}
+
+
+/**
+ * Free all information associated with a melting session. Note
+ * that we allow the melting session to be only partially initialized,
+ * as we use this function also when freeing melt data that was not
+ * fully initialized (i.e. due to failures in #deserialize_melt_data()).
+ *
+ * @param md melting data to release, the pointer itself is NOT
+ * freed (as it is typically not allocated by itself)
+ */
+static void
+free_melt_data (struct MeltData *md)
+{
+ free_melted_coin (&md->melted_coin);
+ if (NULL != md->fresh_pks)
+ {
+ for (unsigned int i=0;i<md->num_fresh_coins;i++)
+ if (NULL != md->fresh_pks[i].rsa_public_key)
+ GNUNET_CRYPTO_rsa_public_key_free (md->fresh_pks[i].rsa_public_key);
+ GNUNET_free (md->fresh_pks);
+ }
+
+ for (unsigned int i=0;i<TALER_CNC_KAPPA;i++)
+ GNUNET_free (md->fresh_coins[i]);
+ /* Finally, clean up a bit...
+ (NOTE: compilers might optimize this away, so this is
+ not providing any strong assurances that the key material
+ is purged.) */
+ memset (md,
+ 0,
+ sizeof (struct MeltData));
+}
+
+
+/**
+ * Serialize information about a coin we are melting.
+ *
+ * @param mc information to serialize
+ * @param buf buffer to write data in, NULL to just compute
+ * required size
+ * @param off offeset at @a buf to use
+ * @return number of bytes written to @a buf at @a off, or if
+ * @a buf is NULL, number of bytes required; 0 on error
+ */
+static size_t
+serialize_melted_coin (const struct MeltedCoin *mc,
+ char *buf,
+ size_t off)
+{
+ struct MeltedCoinP mcp;
+ unsigned int i;
+ char *pbuf;
+ size_t pbuf_size;
+ char *sbuf;
+ size_t sbuf_size;
+
+ sbuf_size = GNUNET_CRYPTO_rsa_signature_encode (mc->sig.rsa_signature,
+ &sbuf);
+ pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (mc->pub_key.rsa_public_key,
+ &pbuf);
+ if (NULL == buf)
+ {
+ GNUNET_free (sbuf);
+ GNUNET_free (pbuf);
+ return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size;
+ }
+ if ( (sbuf_size > UINT16_MAX) ||
+ (pbuf_size > UINT16_MAX) )
+ {
+ GNUNET_break (0);
+ return 0;
+ }
+ mcp.coin_priv = mc->coin_priv;
+ TALER_amount_hton (&mcp.melt_amount_with_fee,
+ &mc->melt_amount_with_fee);
+ TALER_amount_hton (&mcp.fee_melt,
+ &mc->fee_melt);
+ TALER_amount_hton (&mcp.original_value,
+ &mc->original_value);
+ for (i=0;i<TALER_CNC_KAPPA;i++)
+ mcp.transfer_priv[i] = mc->transfer_priv[i];
+ mcp.expire_deposit = GNUNET_TIME_absolute_hton (mc->expire_deposit);
+ mcp.pbuf_size = htons ((uint16_t) pbuf_size);
+ mcp.sbuf_size = htons ((uint16_t) sbuf_size);
+ memcpy (&buf[off],
+ &mcp,
+ sizeof (struct MeltedCoinP));
+ memcpy (&buf[off + sizeof (struct MeltedCoinP)],
+ pbuf,
+ pbuf_size);
+ memcpy (&buf[off + sizeof (struct MeltedCoinP) + pbuf_size],
+ sbuf,
+ sbuf_size);
+ GNUNET_free (sbuf);
+ GNUNET_free (pbuf);
+ return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size;
+}
+
+
+/**
+ * Deserialize information about a coin we are melting.
+ *
+ * @param[out] mc information to deserialize
+ * @param buf buffer to read data from
+ * @param size number of bytes available at @a buf to use
+ * @param[out] ok set to #GNUNET_NO to report errors
+ * @return number of bytes read from @a buf, 0 on error
+ */
+static size_t
+deserialize_melted_coin (struct MeltedCoin *mc,
+ const char *buf,
+ size_t size,
+ int *ok)
+{
+ struct MeltedCoinP mcp;
+ unsigned int i;
+ size_t pbuf_size;
+ size_t sbuf_size;
+ size_t off;
+
+ if (size < sizeof (struct MeltedCoinP))
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ memcpy (&mcp,
+ buf,
+ sizeof (struct MeltedCoinP));
+ pbuf_size = ntohs (mcp.pbuf_size);
+ sbuf_size = ntohs (mcp.sbuf_size);
+ if (size < sizeof (struct MeltedCoinP) + pbuf_size + sbuf_size)
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ off = sizeof (struct MeltedCoinP);
+ mc->pub_key.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_decode (&buf[off],
+ pbuf_size);
+ off += pbuf_size;
+ mc->sig.rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_decode (&buf[off],
+ sbuf_size);
+ off += sbuf_size;
+ if ( (NULL == mc->pub_key.rsa_public_key) ||
+ (NULL == mc->sig.rsa_signature) )
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+
+ mc->coin_priv = mcp.coin_priv;
+ TALER_amount_ntoh (&mc->melt_amount_with_fee,
+ &mcp.melt_amount_with_fee);
+ TALER_amount_ntoh (&mc->fee_melt,
+ &mcp.fee_melt);
+ TALER_amount_ntoh (&mc->original_value,
+ &mcp.original_value);
+ for (i=0;i<TALER_CNC_KAPPA;i++)
+ mc->transfer_priv[i] = mcp.transfer_priv[i];
+ mc->expire_deposit = GNUNET_TIME_absolute_ntoh (mcp.expire_deposit);
+ return off;
+}
+
+
+/**
+ * Serialize information about a denomination key.
+ *
+ * @param dk information to serialize
+ * @param buf buffer to write data in, NULL to just compute
+ * required size
+ * @param off offeset at @a buf to use
+ * @return number of bytes written to @a buf at @a off, or if
+ * @a buf is NULL, number of bytes required
+ */
+static size_t
+serialize_denomination_key (const struct TALER_DenominationPublicKey *dk,
+ char *buf,
+ size_t off)
+{
+ char *pbuf;
+ size_t pbuf_size;
+ uint32_t be;
+
+ pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key,
+ &pbuf);
+ if (NULL == buf)
+ {
+ GNUNET_free (pbuf);
+ return pbuf_size + sizeof (uint32_t);
+ }
+ be = htonl ((uint32_t) pbuf_size);
+ memcpy (&buf[off],
+ &be,
+ sizeof (uint32_t));
+ memcpy (&buf[off + sizeof (uint32_t)],
+ pbuf,
+ pbuf_size);
+ GNUNET_free (pbuf);
+ return pbuf_size + sizeof (uint32_t);
+}
+
+
+/**
+ * Deserialize information about a denomination key.
+ *
+ * @param[out] dk information to deserialize
+ * @param buf buffer to read data from
+ * @param size number of bytes available at @a buf to use
+ * @param[out] ok set to #GNUNET_NO to report errors
+ * @return number of bytes read from @a buf, 0 on error
+ */
+static size_t
+deserialize_denomination_key (struct TALER_DenominationPublicKey *dk,
+ const char *buf,
+ size_t size,
+ int *ok)
+{
+ size_t pbuf_size;
+ uint32_t be;
+
+ if (size < sizeof (uint32_t))
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ memcpy (&be,
+ buf,
+ sizeof (uint32_t));
+ pbuf_size = ntohl (be);
+ if (size < sizeof (uint32_t) + pbuf_size)
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ dk->rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_decode (&buf[sizeof (uint32_t)],
+ pbuf_size);
+ if (NULL == dk->rsa_public_key)
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ return sizeof (uint32_t) + pbuf_size;
+}
+
+
+/**
+ * Serialize information about a fresh coin we are generating.
+ *
+ * @param fc information to serialize
+ * @param buf buffer to write data in, NULL to just compute
+ * required size
+ * @param off offeset at @a buf to use
+ * @return number of bytes written to @a buf at @a off, or if
+ * @a buf is NULL, number of bytes required
+ */
+static size_t
+serialize_fresh_coin (const struct TALER_PlanchetSecretsP *fc,
+ char *buf,
+ size_t off)
+{
+ if (NULL != buf)
+ memcpy (&buf[off],
+ fc,
+ sizeof (struct TALER_PlanchetSecretsP));
+ return sizeof (struct TALER_PlanchetSecretsP);
+}
+
+
+/**
+ * Deserialize information about a fresh coin we are generating.
+ *
+ * @param[out] fc information to deserialize
+ * @param buf buffer to read data from
+ * @param size number of bytes available at @a buf to use
+ * @param[out] ok set to #GNUNET_NO to report errors
+ * @return number of bytes read from @a buf, 0 on error
+ */
+static size_t
+deserialize_fresh_coin (struct TALER_PlanchetSecretsP *fc,
+ const char *buf,
+ size_t size,
+ int *ok)
+{
+ if (size < sizeof (struct TALER_PlanchetSecretsP))
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ memcpy (fc,
+ buf,
+ sizeof (struct TALER_PlanchetSecretsP));
+ return sizeof (struct TALER_PlanchetSecretsP);
+}
+
+
+/**
+ * Serialize melt data.
+ *
+ * @param md data to serialize
+ * @param[out] res_size size of buffer returned
+ * @return serialized melt data
+ */
+static char *
+serialize_melt_data (const struct MeltData *md,
+ size_t *res_size)
+{
+ size_t size;
+ size_t asize;
+ char *buf;
+
+ size = 0;
+ asize = (size_t) -1; /* make the compiler happy */
+ buf = NULL;
+ /* we do 2 iterations, #1 to determine total size, #2 to
+ actually construct the buffer */
+ do {
+ if (0 == size)
+ {
+ size = sizeof (struct MeltDataP);
+ }
+ else
+ {
+ struct MeltDataP *mdp;
+
+ buf = GNUNET_malloc (size);
+ asize = size; /* just for invariant check later */
+ size = sizeof (struct MeltDataP);
+ mdp = (struct MeltDataP *) buf;
+ mdp->rc = md->rc;
+ mdp->num_fresh_coins = htons (md->num_fresh_coins);
+ }
+ size += serialize_melted_coin (&md->melted_coin,
+ buf,
+ size);
+ for (unsigned int i=0;i<md->num_fresh_coins;i++)
+ size += serialize_denomination_key (&md->fresh_pks[i],
+ buf,
+ size);
+ for (unsigned int i=0;i<TALER_CNC_KAPPA;i++)
+ for(unsigned int j=0;j<md->num_fresh_coins;j++)
+ size += serialize_fresh_coin (&md->fresh_coins[i][j],
+ buf,
+ size);
+ } while (NULL == buf);
+ GNUNET_assert (size == asize);
+ *res_size = size;
+ return buf;
+}
+
+
+/**
+ * Deserialize melt data.
+ *
+ * @param buf serialized data
+ * @param buf_size size of @a buf
+ * @return deserialized melt data, NULL on error
+ */
+static struct MeltData *
+deserialize_melt_data (const char *buf,
+ size_t buf_size)
+{
+ struct MeltData *md;
+ struct MeltDataP mdp;
+ size_t off;
+ int ok;
+
+ if (buf_size < sizeof (struct MeltDataP))
+ return NULL;
+ memcpy (&mdp,
+ buf,
+ sizeof (struct MeltDataP));
+ md = GNUNET_new (struct MeltData);
+ md->rc = mdp.rc;
+ md->num_fresh_coins = ntohs (mdp.num_fresh_coins);
+ md->fresh_pks = GNUNET_new_array (md->num_fresh_coins,
+ struct TALER_DenominationPublicKey);
+ for (unsigned int i=0;i<TALER_CNC_KAPPA;i++)
+ md->fresh_coins[i] = GNUNET_new_array (md->num_fresh_coins,
+ struct TALER_PlanchetSecretsP);
+ off = sizeof (struct MeltDataP);
+ ok = GNUNET_YES;
+ off += deserialize_melted_coin (&md->melted_coin,
+ &buf[off],
+ buf_size - off,
+ &ok);
+ for (unsigned int i=0;(i<md->num_fresh_coins)&&(GNUNET_YES == ok);i++)
+ off += deserialize_denomination_key (&md->fresh_pks[i],
+ &buf[off],
+ buf_size - off,
+ &ok);
+
+ for (unsigned int i=0;i<TALER_CNC_KAPPA;i++)
+ for (unsigned int j=0;(j<md->num_fresh_coins)&&(GNUNET_YES == ok);j++)
+ off += deserialize_fresh_coin (&md->fresh_coins[i][j],
+ &buf[off],
+ buf_size - off,
+ &ok);
+ if (off != buf_size)
+ {
+ GNUNET_break (0);
+ ok = GNUNET_NO;
+ }
+ if (GNUNET_YES != ok)
+ {
+ free_melt_data (md);
+ GNUNET_free (md);
+ return NULL;
+ }
+ return md;
+}
+
+
+/**
+ * Melt (partially spent) coins to obtain fresh coins that are
+ * unlinkable to the original coin(s). Note that melting more
+ * than one coin in a single request will make those coins linkable,
+ * so the safest operation only melts one coin at a time.
+ *
+ * This API is typically used by a wallet. Note that to ensure that
+ * no money is lost in case of hardware failures, is operation does
+ * not actually initiate the request. Instead, it generates a buffer
+ * which the caller must store before proceeding with the actual call
+ * to #TALER_EXCHANGE_refresh_melt() that will generate the request.
+ *
+ * This function does verify that the given request data is internally
+ * consistent. However, the @a melts_sigs are only verified if
+ * @a check_sigs is set to #GNUNET_YES, as this may be relatively
+ * expensive and should be redundant.
+ *
+ * Aside from some non-trivial cryptographic operations that might
+ * take a bit of CPU time to complete, this function returns
+ * its result immediately and does not start any asynchronous
+ * processing. This function is also thread-safe.
+ *
+ * @param melt_priv private key of the coin to melt
+ * @param melt_amount amount specifying how much
+ * the coin will contribute to the melt (including fee)
+ * @param melt_sig signature affirming the
+ * validity of the public keys corresponding to the
+ * @a melt_priv private key
+ * @param melt_pk denomination key information
+ * record corresponding to the @a melt_sig
+ * validity of the keys
+ * @param check_sig verify the validity of the @a melt_sig signature
+ * @param fresh_pks_len length of the @a pks array
+ * @param fresh_pks array of @a pks_len denominations of fresh coins to create
+ * @param[out] res_size set to the size of the return value, or 0 on error
+ * @return NULL
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * Otherwise, pointer to a buffer of @a res_size to store persistently
+ * before proceeding to #TALER_EXCHANGE_refresh_melt().
+ * Non-null results should be freed using GNUNET_free().
+ */
+char *
+TALER_EXCHANGE_refresh_prepare (const struct TALER_CoinSpendPrivateKeyP *melt_priv,
+ const struct TALER_Amount *melt_amount,
+ const struct TALER_DenominationSignature *melt_sig,
+ const struct TALER_EXCHANGE_DenomPublicKey *melt_pk,
+ int check_sig,
+ unsigned int fresh_pks_len,
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks,
+ size_t *res_size)
+{
+ struct MeltData md;
+ char *buf;
+ struct TALER_Amount total;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA];
+ struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA];
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&melt_priv->eddsa_priv,
+ &coin_pub.eddsa_pub);
+ /* build up melt data structure */
+ md.num_fresh_coins = fresh_pks_len;
+ md.melted_coin.coin_priv = *melt_priv;
+ md.melted_coin.melt_amount_with_fee = *melt_amount;
+ md.melted_coin.fee_melt = melt_pk->fee_refresh;
+ md.melted_coin.original_value = melt_pk->value;
+ md.melted_coin.expire_deposit
+ = melt_pk->expire_deposit;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (melt_amount->currency,
+ &total));
+ md.melted_coin.pub_key.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_dup (melt_pk->key.rsa_public_key);
+ md.melted_coin.sig.rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_dup (melt_sig->rsa_signature);
+ md.fresh_pks = GNUNET_new_array (fresh_pks_len,
+ struct TALER_DenominationPublicKey);
+ for (unsigned int i=0;i<fresh_pks_len;i++)
+ {
+ md.fresh_pks[i].rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pks[i].key.rsa_public_key);
+ if ( (GNUNET_OK !=
+ TALER_amount_add (&total,
+ &total,
+ &fresh_pks[i].value)) ||
+ (GNUNET_OK !=
+ TALER_amount_add (&total,
+ &total,
+ &fresh_pks[i].fee_withdraw)) )
+ {
+ GNUNET_break (0);
+ free_melt_data (&md);
+ return NULL;
+ }
+ }
+ /* verify that melt_amount is above total cost */
+ if (1 ==
+ TALER_amount_cmp (&total,
+ melt_amount) )
+ {
+ /* Eh, this operation is more expensive than the
+ @a melt_amount. This is not OK. */
+ GNUNET_break (0);
+ free_melt_data (&md);
+ return NULL;
+ }
+
+ /* build up coins */
+ for (unsigned int i=0;i<TALER_CNC_KAPPA;i++)
+ {
+ struct GNUNET_CRYPTO_EcdhePrivateKey *tpk;
+
+ tpk = GNUNET_CRYPTO_ecdhe_key_create ();
+ md.melted_coin.transfer_priv[i].ecdhe_priv = *tpk;
+ GNUNET_free (tpk);
+
+ GNUNET_CRYPTO_ecdhe_key_get_public (&md.melted_coin.transfer_priv[i].ecdhe_priv,
+ &rce[i].transfer_pub.ecdhe_pub);
+ TALER_link_derive_transfer_secret (melt_priv,
+ &md.melted_coin.transfer_priv[i],
+ &trans_sec[i]);
+ md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len,
+ struct TALER_PlanchetSecretsP);
+ rce[i].new_coins = GNUNET_new_array (fresh_pks_len,
+ struct TALER_RefreshCoinData);
+ for (unsigned int j=0;j<fresh_pks_len;j++)
+ {
+ struct TALER_PlanchetSecretsP *fc = &md.fresh_coins[i][j];
+ struct TALER_RefreshCoinData *rcd = &rce[i].new_coins[j];
+ struct TALER_PlanchetDetail pd;
+
+ TALER_planchet_setup_refresh (&trans_sec[i],
+ j,
+ fc);
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&md.fresh_pks[j],
+ fc,
+ &pd))
+ {
+ GNUNET_break_op (0);
+ free_melt_data (&md);
+ return NULL;
+ }
+ rcd->dk = &md.fresh_pks[j];
+ rcd->coin_ev = pd.coin_ev;
+ rcd->coin_ev_size = pd.coin_ev_size;
+ }
+ }
+
+ /* Compute refresh commitment */
+ TALER_refresh_get_commitment (&md.rc,
+ TALER_CNC_KAPPA,
+ fresh_pks_len,
+ rce,
+ &coin_pub,
+ melt_amount);
+ /* finally, serialize everything */
+ buf = serialize_melt_data (&md,
+ res_size);
+ for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++)
+ {
+ for (unsigned int j = 0; j < fresh_pks_len; j++)
+ GNUNET_free_non_null (rce[i].new_coins[j].coin_ev);
+ GNUNET_free_non_null (rce[i].new_coins);
+ }
+ free_melt_data (&md);
+ return buf;
+}
+
+
+/* ********************* /refresh/melt ***************************** */
+
+
+/**
+ * @brief A /refresh/melt Handle
+ */
+struct TALER_EXCHANGE_RefreshMeltHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * JSON encoding of the request to POST.
+ */
+ char *json_enc;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with refresh melt failure results.
+ */
+ TALER_EXCHANGE_RefreshMeltCallback melt_cb;
+
+ /**
+ * Closure for @e result_cb and @e melt_failure_cb.
+ */
+ void *melt_cb_cls;
+
+ /**
+ * Actual information about the melt operation.
+ */
+ struct MeltData *md;
+};
+
+
+/**
+ * Verify that the signature on the "200 OK" response
+ * from the exchange is valid.
+ *
+ * @param rmh melt handle
+ * @param json json reply with the signature
+ * @param[out] exchange_pub public key of the exchange used for the signature
+ * @param[out] noreveal_index set to the noreveal index selected by the exchange
+ * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
+ */
+static int
+verify_refresh_melt_signature_ok (struct TALER_EXCHANGE_RefreshMeltHandle *rmh,
+ const json_t *json,
+ struct TALER_ExchangePublicKeyP *exchange_pub,
+ uint32_t *noreveal_index)
+{
+ struct TALER_ExchangeSignatureP exchange_sig;
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub),
+ GNUNET_JSON_spec_uint32 ("noreveal_index", noreveal_index),
+ GNUNET_JSON_spec_end()
+ };
+ struct TALER_RefreshMeltConfirmationPS confirm;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* check that exchange signing key is permitted */
+ key_state = TALER_EXCHANGE_get_keys (rmh->exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* check that noreveal index is in permitted range */
+ if (TALER_CNC_KAPPA <= *noreveal_index)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* verify signature by exchange */
+ confirm.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT);
+ confirm.purpose.size = htonl (sizeof (struct TALER_RefreshMeltConfirmationPS));
+ confirm.rc = rmh->md->rc;
+ confirm.noreveal_index = htonl (*noreveal_index);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT,
+ &confirm.purpose,
+ &exchange_sig.eddsa_signature,
+ &exchange_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Verify that the signatures on the "403 FORBIDDEN" response from the
+ * exchange demonstrating customer double-spending are valid.
+ *
+ * @param rmh melt handle
+ * @param json json reply with the signature(s) and transaction history
+ * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
+ */
+static int
+verify_refresh_melt_signature_forbidden (struct TALER_EXCHANGE_RefreshMeltHandle *rmh,
+ const json_t *json)
+{
+ json_t *history;
+ struct TALER_Amount original_value;
+ struct TALER_Amount melt_value_with_fee;
+ struct TALER_Amount total;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("history", &history),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
+ TALER_JSON_spec_amount ("original_value", &original_value),
+ TALER_JSON_spec_amount ("requested_value", &melt_value_with_fee),
+ GNUNET_JSON_spec_end()
+ };
+ const struct MeltedCoin *mc;
+
+ /* parse JSON reply */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* Find out which coin was deemed problematic by the exchange */
+ mc = &rmh->md->melted_coin;
+
+ /* check basic coin properties */
+ if (0 != TALER_amount_cmp (&original_value,
+ &mc->original_value))
+ {
+ /* We disagree on the value of the coin */
+ GNUNET_break_op (0);
+ json_decref (history);
+ return GNUNET_SYSERR;
+ }
+ if (0 != TALER_amount_cmp (&melt_value_with_fee,
+ &mc->melt_amount_with_fee))
+ {
+ /* We disagree on the value of the coin */
+ GNUNET_break_op (0);
+ json_decref (history);
+ return GNUNET_SYSERR;
+ }
+
+ /* verify coin history */
+ history = json_object_get (json,
+ "history");
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_verify_coin_history (original_value.currency,
+ &coin_pub,
+ history,
+ &total))
+ {
+ GNUNET_break_op (0);
+ json_decref (history);
+ return GNUNET_SYSERR;
+ }
+ json_decref (history);
+
+ /* check if melt operation was really too expensive given history */
+ if (GNUNET_OK !=
+ TALER_amount_add (&total,
+ &total,
+ &melt_value_with_fee))
+ {
+ /* clearly not OK if our transaction would have caused
+ the overflow... */
+ return GNUNET_OK;
+ }
+
+ if (0 >= TALER_amount_cmp (&total,
+ &original_value))
+ {
+ /* transaction should have still fit */
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* everything OK, valid proof of double-spending was provided */
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /refresh/melt request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RefreshMeltHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_refresh_melt_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RefreshMeltHandle *rmh = cls;
+ uint32_t noreveal_index = TALER_CNC_KAPPA; /* invalid value */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ const json_t *j = response;
+
+ rmh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ verify_refresh_melt_signature_ok (rmh,
+ j,
+ &exchange_pub,
+ &noreveal_index))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ if (NULL != rmh->melt_cb)
+ {
+ rmh->melt_cb (rmh->melt_cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ noreveal_index,
+ (0 == response_code) ? NULL : &exchange_pub,
+ j);
+ rmh->melt_cb = NULL;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* Double spending; check signatures on transaction history */
+ if (GNUNET_OK !=
+ verify_refresh_melt_signature_forbidden (rmh,
+ j))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; assuming we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ if (NULL != rmh->melt_cb)
+ rmh->melt_cb (rmh->melt_cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ UINT32_MAX,
+ NULL,
+ j);
+ TALER_EXCHANGE_refresh_melt_cancel (rmh);
+}
+
+
+/**
+ * Submit a melt request to the exchange and get the exchange's
+ * response.
+ *
+ * This API is typically used by a wallet. Note that to ensure that
+ * no money is lost in case of hardware failures, the provided
+ * argument should have been constructed using
+ * #TALER_EXCHANGE_refresh_prepare and committed to persistent storage
+ * prior to calling this function.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param refresh_data_length size of the @a refresh_data (returned
+ * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare())
+ * @param refresh_data the refresh data as returned from
+ #TALER_EXCHANGE_refresh_prepare())
+ * @param melt_cb the callback to call with the result
+ * @param melt_cb_cls closure for @a melt_cb
+ * @return a handle for this request; NULL if the argument was invalid.
+ * In this case, neither callback will be called.
+ */
+struct TALER_EXCHANGE_RefreshMeltHandle *
+TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange,
+ size_t refresh_data_length,
+ const char *refresh_data,
+ TALER_EXCHANGE_RefreshMeltCallback melt_cb,
+ void *melt_cb_cls)
+{
+ json_t *melt_obj;
+ struct TALER_EXCHANGE_RefreshMeltHandle *rmh;
+ CURL *eh;
+ struct GNUNET_CURL_Context *ctx;
+ struct MeltData *md;
+ struct TALER_CoinSpendSignatureP confirm_sig;
+ struct TALER_RefreshMeltCoinAffirmationPS melt;
+
+ GNUNET_assert (GNUNET_YES ==
+ TEAH_handle_is_ready (exchange));
+ md = deserialize_melt_data (refresh_data,
+ refresh_data_length);
+ if (NULL == md)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
+ melt.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS));
+ melt.rc = md->rc;
+ TALER_amount_hton (&melt.amount_with_fee,
+ &md->melted_coin.melt_amount_with_fee);
+ TALER_amount_hton (&melt.melt_fee,
+ &md->melted_coin.fee_melt);
+ GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv,
+ &melt.coin_pub.eddsa_pub);
+ GNUNET_CRYPTO_eddsa_sign (&md->melted_coin.coin_priv.eddsa_priv,
+ &melt.purpose,
+ &confirm_sig.eddsa_signature);
+ melt_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}",
+ "coin_pub",
+ GNUNET_JSON_from_data_auto (&melt.coin_pub),
+ "denom_pub",
+ GNUNET_JSON_from_rsa_public_key (md->melted_coin.pub_key.rsa_public_key),
+ "denom_sig",
+ GNUNET_JSON_from_rsa_signature (md->melted_coin.sig.rsa_signature),
+ "confirm_sig",
+ GNUNET_JSON_from_data_auto (&confirm_sig),
+ "value_with_fee",
+ TALER_JSON_from_amount (&md->melted_coin.melt_amount_with_fee),
+ "rc",
+ GNUNET_JSON_from_data_auto (&melt.rc));
+ if (NULL == melt_obj)
+ {
+ GNUNET_break (0);
+ free_melt_data (md);
+ return NULL;
+ }
+
+ /* and now we can at last begin the actual request handling */
+ rmh = GNUNET_new (struct TALER_EXCHANGE_RefreshMeltHandle);
+ rmh->exchange = exchange;
+ rmh->melt_cb = melt_cb;
+ rmh->melt_cb_cls = melt_cb_cls;
+ rmh->md = md;
+ rmh->url = TEAH_path_to_url (exchange,
+ "/refresh/melt");
+ eh = TEL_curl_easy_get (rmh->url);
+ GNUNET_assert (NULL != (rmh->json_enc =
+ json_dumps (melt_obj,
+ JSON_COMPACT)));
+ json_decref (melt_obj);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ rmh->json_enc));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ strlen (rmh->json_enc)));
+ ctx = TEAH_handle_to_context (exchange);
+ rmh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_refresh_melt_finished,
+ rmh);
+ return rmh;
+}
+
+
+/**
+ * Cancel a refresh execute request. This function cannot be used
+ * on a request handle if either callback was already invoked.
+ *
+ * @param rmh the refresh melt handle
+ */
+void
+TALER_EXCHANGE_refresh_melt_cancel (struct TALER_EXCHANGE_RefreshMeltHandle *rmh)
+{
+ if (NULL != rmh->job)
+ {
+ GNUNET_CURL_job_cancel (rmh->job);
+ rmh->job = NULL;
+ }
+ free_melt_data (rmh->md); /* does not free 'md' itself */
+ GNUNET_free (rmh->md);
+ GNUNET_free (rmh->url);
+ GNUNET_free (rmh->json_enc);
+ GNUNET_free (rmh);
+}
+
+
+/* ********************* /refresh/reveal ***************************** */
+
+
+/**
+ * @brief A /refresh/reveal Handle
+ */
+struct TALER_EXCHANGE_RefreshRevealHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * JSON encoding of the request to POST.
+ */
+ char *json_enc;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_RefreshRevealCallback reveal_cb;
+
+ /**
+ * Closure for @e reveal_cb.
+ */
+ void *reveal_cb_cls;
+
+ /**
+ * Actual information about the melt operation.
+ */
+ struct MeltData *md;
+
+ /**
+ * The index selected by the exchange in cut-and-choose to not be revealed.
+ */
+ uint16_t noreveal_index;
+
+};
+
+
+/**
+ * We got a 200 OK response for the /refresh/reveal operation.
+ * Extract the coin signatures and return them to the caller.
+ * The signatures we get from the exchange is for the blinded value.
+ * Thus, we first must unblind them and then should verify their
+ * validity.
+ *
+ * If everything checks out, we return the unblinded signatures
+ * to the application via the callback.
+ *
+ * @param rrh operation handle
+ * @param json reply from the exchange
+ * @param[out] coin_privs array of length `num_fresh_coins`, initialized to contain private keys
+ * @param[out] sigs array of length `num_fresh_coins`, initialized to cointain RSA signatures
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static int
+refresh_reveal_ok (struct TALER_EXCHANGE_RefreshRevealHandle *rrh,
+ const json_t *json,
+ struct TALER_CoinSpendPrivateKeyP *coin_privs,
+ struct TALER_DenominationSignature *sigs)
+{
+ json_t *jsona;
+ struct GNUNET_JSON_Specification outer_spec[] = {
+ GNUNET_JSON_spec_json ("ev_sigs", &jsona),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ outer_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (! json_is_array (jsona))
+ {
+ /* We expected an array of coins */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (outer_spec);
+ return GNUNET_SYSERR;
+ }
+ if (rrh->md->num_fresh_coins != json_array_size (jsona))
+ {
+ /* Number of coins generated does not match our expectation */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (outer_spec);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i=0;i<rrh->md->num_fresh_coins;i++)
+ {
+ const struct TALER_PlanchetSecretsP *fc;
+ struct TALER_DenominationPublicKey *pk;
+ json_t *jsonai;
+ struct GNUNET_CRYPTO_RsaSignature *blind_sig;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct GNUNET_HashCode coin_hash;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_rsa_signature ("ev_sig", &blind_sig),
+ GNUNET_JSON_spec_end()
+ };
+ struct TALER_FreshCoin coin;
+
+ fc = &rrh->md->fresh_coins[rrh->noreveal_index][i];
+ pk = &rrh->md->fresh_pks[i];
+ jsonai = json_array_get (jsona, i);
+ GNUNET_assert (NULL != jsonai);
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jsonai,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (outer_spec);
+ return GNUNET_SYSERR;
+ }
+
+ /* needed to verify the signature, and we didn't store it earlier,
+ hence recomputing it here... */
+ GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv,
+ &coin_pub.eddsa_pub);
+ GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
+ &coin_hash);
+ if (GNUNET_OK !=
+ TALER_planchet_to_coin (pk,
+ blind_sig,
+ fc,
+ &coin_hash,
+ &coin))
+ {
+ GNUNET_break_op (0);
+ GNUNET_CRYPTO_rsa_signature_free (blind_sig);
+ GNUNET_JSON_parse_free (outer_spec);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_CRYPTO_rsa_signature_free (blind_sig);
+ coin_privs[i] = coin.coin_priv;
+ sigs[i] = coin.sig;
+ }
+ GNUNET_JSON_parse_free (outer_spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /refresh/reveal request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RefreshHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_refresh_reveal_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RefreshRevealHandle *rrh = cls;
+ const json_t *j = response;
+
+ rrh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct TALER_CoinSpendPrivateKeyP coin_privs[rrh->md->num_fresh_coins];
+ struct TALER_DenominationSignature sigs[rrh->md->num_fresh_coins];
+ int ret;
+
+ memset (sigs, 0, sizeof (sigs));
+ ret = refresh_reveal_ok (rrh,
+ j,
+ coin_privs,
+ sigs);
+ if (GNUNET_OK != ret)
+ {
+ response_code = 0;
+ }
+ else
+ {
+ rrh->reveal_cb (rrh->reveal_cb_cls,
+ MHD_HTTP_OK,
+ TALER_EC_NONE,
+ rrh->md->num_fresh_coins,
+ coin_privs,
+ sigs,
+ j);
+ rrh->reveal_cb = NULL;
+ }
+ for (unsigned int i=0;i<rrh->md->num_fresh_coins;i++)
+ if (NULL != sigs[i].rsa_signature)
+ GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature);
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Nothing really to verify, exchange says our reveal is inconsitent
+ with our commitment, so either side is buggy; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ if (NULL != rrh->reveal_cb)
+ rrh->reveal_cb (rrh->reveal_cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ 0,
+ NULL,
+ NULL,
+ j);
+ TALER_EXCHANGE_refresh_reveal_cancel (rrh);
+}
+
+
+/**
+ * Submit a /refresh/reval request to the exchange and get the exchange's
+ * response.
+ *
+ * This API is typically used by a wallet. Note that to ensure that
+ * no money is lost in case of hardware failures, the provided
+ * arguments should have been committed to persistent storage
+ * prior to calling this function.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param refresh_data_length size of the @a refresh_data (returned
+ * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare())
+ * @param refresh_data the refresh data as returned from
+ #TALER_EXCHANGE_refresh_prepare())
+ * @param noreveal_index response from the exchange to the
+ * #TALER_EXCHANGE_refresh_melt() invocation
+ * @param reveal_cb the callback to call with the final result of the
+ * refresh operation
+ * @param reveal_cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the argument was invalid.
+ * In this case, neither callback will be called.
+ */
+struct TALER_EXCHANGE_RefreshRevealHandle *
+TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
+ size_t refresh_data_length,
+ const char *refresh_data,
+ uint32_t noreveal_index,
+ TALER_EXCHANGE_RefreshRevealCallback reveal_cb,
+ void *reveal_cb_cls)
+{
+ struct TALER_EXCHANGE_RefreshRevealHandle *rrh;
+ json_t *transfer_privs;
+ json_t *new_denoms_h;
+ json_t *coin_evs;
+ json_t *reveal_obj;
+ CURL *eh;
+ struct GNUNET_CURL_Context *ctx;
+ struct MeltData *md;
+ struct TALER_TransferPublicKeyP transfer_pub;
+
+ GNUNET_assert (GNUNET_YES ==
+ TEAH_handle_is_ready (exchange));
+ md = deserialize_melt_data (refresh_data,
+ refresh_data_length);
+ if (NULL == md)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (noreveal_index >= TALER_CNC_KAPPA)
+ {
+ /* We check this here, as it would be really bad to below just
+ disclose all the transfer keys. Note that this error should
+ have been caught way earlier when the exchange replied, but maybe
+ we had some internal corruption that changed the value... */
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ /* now transfer_pub */
+ GNUNET_CRYPTO_ecdhe_key_get_public (&md->melted_coin.transfer_priv[noreveal_index].ecdhe_priv,
+ &transfer_pub.ecdhe_pub);
+
+ /* now new_denoms */
+ GNUNET_assert (NULL != (new_denoms_h = json_array ()));
+ GNUNET_assert (NULL != (coin_evs = json_array ()));
+ for (unsigned int i=0;i<md->num_fresh_coins;i++)
+ {
+ struct GNUNET_HashCode denom_hash;
+ struct TALER_PlanchetDetail pd;
+
+ GNUNET_CRYPTO_rsa_public_key_hash (md->fresh_pks[i].rsa_public_key,
+ &denom_hash);
+ GNUNET_assert (0 ==
+ json_array_append_new (new_denoms_h,
+ GNUNET_JSON_from_data_auto (&denom_hash)));
+
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&md->fresh_pks[i],
+ &md->fresh_coins[noreveal_index][i],
+ &pd))
+ {
+ /* This should have been noticed during the preparation stage. */
+ GNUNET_break (0);
+ json_decref (new_denoms_h);
+ json_decref (coin_evs);
+ return NULL;
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (coin_evs,
+ GNUNET_JSON_from_data (pd.coin_ev,
+ pd.coin_ev_size)));
+ GNUNET_free (pd.coin_ev);
+ }
+
+ /* build array of transfer private keys */
+ GNUNET_assert (NULL != (transfer_privs = json_array ()));
+ for (unsigned int j=0;j<TALER_CNC_KAPPA;j++)
+ {
+ if (j == noreveal_index)
+ {
+ /* This is crucial: exclude the transfer key for the
+ noreval index! */
+ continue;
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (transfer_privs,
+ GNUNET_JSON_from_data_auto (&md->melted_coin.transfer_priv[j])));
+ }
+
+ /* build main JSON request */
+ reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}",
+ "rc",
+ GNUNET_JSON_from_data_auto (&md->rc),
+ "transfer_pub",
+ GNUNET_JSON_from_data_auto (&transfer_pub),
+ "transfer_privs",
+ transfer_privs,
+ "new_denoms_h",
+ new_denoms_h,
+ "coin_evs",
+ coin_evs);
+ if (NULL == reveal_obj)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ /* finally, we can actually issue the request */
+ rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshRevealHandle);
+ rrh->exchange = exchange;
+ rrh->noreveal_index = noreveal_index;
+ rrh->reveal_cb = reveal_cb;
+ rrh->reveal_cb_cls = reveal_cb_cls;
+ rrh->md = md;
+ rrh->url = TEAH_path_to_url (rrh->exchange,
+ "/refresh/reveal");
+
+ eh = TEL_curl_easy_get (rrh->url);
+ GNUNET_assert (NULL != (rrh->json_enc =
+ json_dumps (reveal_obj,
+ JSON_COMPACT)));
+ json_decref (reveal_obj);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ rrh->json_enc));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ strlen (rrh->json_enc)));
+ ctx = TEAH_handle_to_context (rrh->exchange);
+ rrh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_refresh_reveal_finished,
+ rrh);
+ return rrh;
+}
+
+
+/**
+ * Cancel a refresh reveal request. This function cannot be used
+ * on a request handle if the callback was already invoked.
+ *
+ * @param rrh the refresh reval handle
+ */
+void
+TALER_EXCHANGE_refresh_reveal_cancel (struct TALER_EXCHANGE_RefreshRevealHandle *rrh)
+{
+ if (NULL != rrh->job)
+ {
+ GNUNET_CURL_job_cancel (rrh->job);
+ rrh->job = NULL;
+ }
+ GNUNET_free (rrh->url);
+ GNUNET_free (rrh->json_enc);
+ free_melt_data (rrh->md); /* does not free 'md' itself */
+ GNUNET_free (rrh->md);
+ GNUNET_free (rrh);
+}
+
+
+/* end of exchange_api_refresh.c */
diff --git a/src/lib/exchange_api_refresh_link.c b/src/lib/exchange_api_refresh_link.c
new file mode 100644
index 00000000..ea82c9ba
--- /dev/null
+++ b/src/lib/exchange_api_refresh_link.c
@@ -0,0 +1,444 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015, 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-lib/exchange_api_refresh_link.c
+ * @brief Implementation of the /refresh/link request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /refresh/link Handle
+ */
+struct TALER_EXCHANGE_RefreshLinkHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_RefreshLinkCallback link_cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *link_cb_cls;
+
+ /**
+ * Private key of the coin, required to decode link information.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+};
+
+
+/**
+ * Parse the provided linkage data from the "200 OK" response
+ * for one of the coins.
+ *
+ * @param rlh refresh link handle
+ * @param json json reply with the data for one coin
+ * @param coin_num number of the coin to decode
+ * @param trans_pub our transfer public key
+ * @param[out] coin_priv where to return private coin key
+ * @param[out] sig where to return private coin signature
+ * @param[out] pub where to return the public key for the coin
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static int
+parse_refresh_link_coin (const struct TALER_EXCHANGE_RefreshLinkHandle *rlh,
+ const json_t *json,
+ unsigned int coin_num,
+ const struct TALER_TransferPublicKeyP *trans_pub,
+ struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_DenominationSignature *sig,
+ struct TALER_DenominationPublicKey *pub)
+{
+ struct GNUNET_CRYPTO_RsaSignature *bsig;
+ struct GNUNET_CRYPTO_RsaPublicKey *rpub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_rsa_public_key ("denom_pub", &rpub),
+ GNUNET_JSON_spec_rsa_signature ("ev_sig", &bsig),
+ GNUNET_JSON_spec_end()
+ };
+ struct TALER_TransferSecretP secret;
+ struct TALER_PlanchetSecretsP fc;
+
+ /* parse reply */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ TALER_link_recover_transfer_secret (trans_pub,
+ &rlh->coin_priv,
+ &secret);
+ TALER_planchet_setup_refresh (&secret,
+ coin_num,
+ &fc);
+
+ /* extract coin and signature */
+ *coin_priv = fc.coin_priv;
+ sig->rsa_signature
+ = GNUNET_CRYPTO_rsa_unblind (bsig,
+ &fc.blinding_key.bks,
+ rpub);
+ /* clean up */
+ pub->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (rpub);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse the provided linkage data from the "200 OK" response
+ * for one of the coins.
+ *
+ * @param[in,out] rlh refresh link handle (callback may be zero'ed out)
+ * @param json json reply with the data for one coin
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static int
+parse_refresh_link_ok (struct TALER_EXCHANGE_RefreshLinkHandle *rlh,
+ const json_t *json)
+{
+ unsigned int session;
+ unsigned int num_coins;
+ int ret;
+
+ if (! json_is_array (json))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ num_coins = 0;
+ /* Theoretically, a coin may have been melted repeatedly
+ into different sessions; so the response is an array
+ which contains information by melting session. That
+ array contains another array. However, our API returns
+ a single 1d array, so we flatten the 2d array that is
+ returned into a single array. Note that usually a coin
+ is melted at most once, and so we'll only run this
+ loop once for 'session=0' in most cases.
+
+ num_coins tracks the size of the 1d array we return,
+ whilst 'i' and 'session' track the 2d array. */
+ for (session=0;session<json_array_size (json); session++)
+ {
+ json_t *jsona;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("new_coins", &jsona),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (json,
+ session),
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (! json_is_array (jsona))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+
+ /* count all coins over all sessions */
+ num_coins += json_array_size (jsona);
+ GNUNET_JSON_parse_free (spec);
+ }
+ /* Now that we know how big the 1d array is, allocate
+ and fill it. */
+ {
+ unsigned int off_coin; /* index into 1d array */
+ unsigned int i;
+ struct TALER_CoinSpendPrivateKeyP coin_privs[num_coins];
+ struct TALER_DenominationSignature sigs[num_coins];
+ struct TALER_DenominationPublicKey pubs[num_coins];
+
+ memset (sigs, 0, sizeof (sigs));
+ memset (pubs, 0, sizeof (pubs));
+ off_coin = 0;
+ for (session=0;session<json_array_size (json); session++)
+ {
+ json_t *jsona;
+ struct TALER_TransferPublicKeyP trans_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("new_coins",
+ &jsona),
+ GNUNET_JSON_spec_fixed_auto ("transfer_pub",
+ &trans_pub),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (json,
+ session),
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (! json_is_array (jsona))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+
+ /* decode all coins */
+ for (i=0;i<json_array_size (jsona);i++)
+ {
+ GNUNET_assert (i + off_coin < num_coins);
+ if (GNUNET_OK !=
+ parse_refresh_link_coin (rlh,
+ json_array_get (jsona,
+ i),
+ i,
+ &trans_pub,
+ &coin_privs[i+off_coin],
+ &sigs[i+off_coin],
+ &pubs[i+off_coin]))
+ {
+ GNUNET_break_op (0);
+ break;
+ }
+ }
+ /* check if we really got all, then invoke callback */
+ off_coin += i;
+ if (i != json_array_size (jsona))
+ {
+ GNUNET_break_op (0);
+ ret = GNUNET_SYSERR;
+ GNUNET_JSON_parse_free (spec);
+ break;
+ }
+ GNUNET_JSON_parse_free (spec);
+ } /* end of for (session) */
+
+ if (off_coin == num_coins)
+ {
+ rlh->link_cb (rlh->link_cb_cls,
+ MHD_HTTP_OK,
+ TALER_EC_NONE,
+ num_coins,
+ coin_privs,
+ sigs,
+ pubs,
+ json);
+ rlh->link_cb = NULL;
+ ret = GNUNET_OK;
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ ret = GNUNET_SYSERR;
+ }
+
+ /* clean up */
+ GNUNET_assert (off_coin <= num_coins);
+ for (i=0;i<off_coin;i++)
+ {
+ if (NULL != sigs[i].rsa_signature)
+ GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature);
+ if (NULL != pubs[i].rsa_public_key)
+ GNUNET_CRYPTO_rsa_public_key_free (pubs[i].rsa_public_key);
+ }
+ }
+ return ret;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /refresh/link request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RefreshLinkHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_refresh_link_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RefreshLinkHandle *rlh = cls;
+ const json_t *j = response;
+
+ rlh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ parse_refresh_link_ok (rlh,
+ j))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ if (NULL != rlh->link_cb)
+ rlh->link_cb (rlh->link_cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ j);
+ TALER_EXCHANGE_refresh_link_cancel (rlh);
+}
+
+
+/**
+ * Submit a link request to the exchange and get the exchange's response.
+ *
+ * This API is typically not used by anyone, it is more a threat
+ * against those trying to receive a funds transfer by abusing the
+ * /refresh protocol.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param coin_priv private key to request link data for
+ * @param link_cb the callback to call with the useful result of the
+ * refresh operation the @a coin_priv was involved in (if any)
+ * @param link_cb_cls closure for @a link_cb
+ * @return a handle for this request
+ */
+struct TALER_EXCHANGE_RefreshLinkHandle *
+TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ TALER_EXCHANGE_RefreshLinkCallback link_cb,
+ void *link_cb_cls)
+{
+ struct TALER_EXCHANGE_RefreshLinkHandle *rlh;
+ CURL *eh;
+ struct GNUNET_CURL_Context *ctx;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ char *pub_str;
+ char *arg_str;
+
+ if (GNUNET_YES !=
+ TEAH_handle_is_ready (exchange))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+ &coin_pub.eddsa_pub);
+ pub_str = GNUNET_STRINGS_data_to_string_alloc (&coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP));
+ GNUNET_asprintf (&arg_str,
+ "/refresh/link?coin_pub=%s",
+ pub_str);
+ GNUNET_free (pub_str);
+
+ rlh = GNUNET_new (struct TALER_EXCHANGE_RefreshLinkHandle);
+ rlh->exchange = exchange;
+ rlh->link_cb = link_cb;
+ rlh->link_cb_cls = link_cb_cls;
+ rlh->coin_priv = *coin_priv;
+ rlh->url = TEAH_path_to_url (exchange, arg_str);
+ GNUNET_free (arg_str);
+
+
+ eh = TEL_curl_easy_get (rlh->url);
+ ctx = TEAH_handle_to_context (exchange);
+ rlh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_refresh_link_finished,
+ rlh);
+ return rlh;
+}
+
+
+/**
+ * Cancel a refresh link request. This function cannot be used
+ * on a request handle if the callback was already invoked.
+ *
+ * @param rlh the refresh link handle
+ */
+void
+TALER_EXCHANGE_refresh_link_cancel (struct TALER_EXCHANGE_RefreshLinkHandle *rlh)
+{
+ if (NULL != rlh->job)
+ {
+ GNUNET_CURL_job_cancel (rlh->job);
+ rlh->job = NULL;
+ }
+ GNUNET_free (rlh->url);
+ GNUNET_free (rlh);
+}
+
+
+/* end of exchange_api_refresh_link.c */
diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c
new file mode 100644
index 00000000..75ebdc4e
--- /dev/null
+++ b/src/lib/exchange_api_refund.c
@@ -0,0 +1,416 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-lib/exchange_api_refund.c
+ * @brief Implementation of the /refund request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Refund Handle
+ */
+struct TALER_EXCHANGE_RefundHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * JSON encoding of the request to POST.
+ */
+ char *json_enc;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_RefundResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Information the exchange should sign in response.
+ */
+ struct TALER_RefundConfirmationPS depconf;
+
+};
+
+
+/**
+ * Verify that the signature on the "200 OK" response
+ * from the exchange is valid.
+ *
+ * @param rh refund handle
+ * @param json json reply with the signature
+ * @param[out] exchange_pub set to the exchange's public key
+ * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
+ */
+static int
+verify_refund_signature_ok (const struct TALER_EXCHANGE_RefundHandle *rh,
+ const json_t *json,
+ struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ struct TALER_ExchangeSignatureP exchange_sig;
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("sig", &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("pub", exchange_pub),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ key_state = TALER_EXCHANGE_get_keys (rh->exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND,
+ &rh->depconf.purpose,
+ &exchange_sig.eddsa_signature,
+ &exchange_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /refund request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RefundHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_refund_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RefundHandle *rh = cls;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangePublicKeyP *ep = NULL;
+ const json_t *j = response;
+
+ rh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ verify_refund_signature_ok (rh,
+ j,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ else
+ {
+ ep = &exchange_pub;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_GONE:
+ /* Kind of normal: the money was already sent to the merchant
+ (it was too late for the refund). */
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ /* Client request was inconsistent; might be a currency missmatch
+ problem. */
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Two refund requests were made about the same deposit, but
+ carrying different refund transaction ids. */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ rh->cb (rh->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ ep,
+ j);
+ TALER_EXCHANGE_refund_cancel (rh);
+}
+
+
+/**
+ * Submit a refund request to the exchange and get the exchange's
+ * response. This API is used by a merchant. Note that
+ * while we return the response verbatim to the caller for further
+ * processing, we do already verify that the response is well-formed
+ * (i.e. that signatures included in the response are all valid). If
+ * the exchange's reply is not well-formed, we return an HTTP status code
+ * of zero to @a cb.
+ *
+ * The @a exchange must be ready to operate (i.e. have
+ * finished processing the /keys reply). If this check fails, we do
+ * NOT initiate the transaction with the exchange and instead return NULL.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param amount the amount to be refunded; must be larger than the refund fee
+ * (as that fee is still being subtracted), and smaller than the amount
+ * (with deposit fee) of the original deposit contribution of this coin
+ * @param refund_fee fee applicable to this coin for the refund
+ * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded
+ * @param coin_pub coin’s public key of the coin from the original deposit operation
+ * @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation);
+ * this is needed as we may first do a partial refund and later a full refund. If both
+ * refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint
+ * refund requests different (as requests are idempotent and otherwise the 2nd refund might not work).
+ * @param merchant_priv the private key of the merchant, used to generate signature for refund request
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_RefundHandle *
+TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *refund_fee,
+ const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t rtransaction_id,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ TALER_EXCHANGE_RefundResultCallback cb,
+ void *cb_cls)
+{
+ struct TALER_RefundRequestPS rr;
+ struct TALER_MerchantSignatureP merchant_sig;
+
+ GNUNET_assert (GNUNET_YES ==
+ TEAH_handle_is_ready (exchange));
+ rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
+ rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS));
+ rr.h_contract_terms = *h_contract_terms;
+ rr.coin_pub = *coin_pub;
+ GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
+ &rr.merchant.eddsa_pub);
+ rr.rtransaction_id = GNUNET_htonll (rtransaction_id);
+ TALER_amount_hton (&rr.refund_amount,
+ amount);
+ TALER_amount_hton (&rr.refund_fee,
+ refund_fee);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
+ &rr.purpose,
+ &merchant_sig.eddsa_sig));
+ return TALER_EXCHANGE_refund2 (exchange,
+ amount,
+ refund_fee,
+ h_contract_terms,
+ coin_pub,
+ rtransaction_id,
+ &rr.merchant,
+ &merchant_sig,
+ cb,
+ cb_cls);
+}
+
+
+/**
+ * Submit a refund request to the exchange and get the exchange's
+ * response. This API is used by a merchant. Note that
+ * while we return the response verbatim to the caller for further
+ * processing, we do already verify that the response is well-formed
+ * (i.e. that signatures included in the response are all valid). If
+ * the exchange's reply is not well-formed, we return an HTTP status code
+ * of zero to @a cb.
+ *
+ * The @a exchange must be ready to operate (i.e. have
+ * finished processing the /keys reply). If this check fails, we do
+ * NOT initiate the transaction with the exchange and instead return NULL.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param amount the amount to be refunded; must be larger than the refund fee
+ * (as that fee is still being subtracted), and smaller than the amount
+ * (with deposit fee) of the original deposit contribution of this coin
+ * @param refund_fee fee applicable to this coin for the refund
+ * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded
+ * @param coin_pub coin’s public key of the coin from the original deposit operation
+ * @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation);
+ * this is needed as we may first do a partial refund and later a full refund. If both
+ * refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint
+ * refund requests different (as requests are idempotent and otherwise the 2nd refund might not work).
+ * @param merchant_pub public key of the merchant
+ * @param merchant_sig signature affirming the refund from the merchant
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_RefundHandle *
+TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *refund_fee,
+ const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t rtransaction_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig,
+ TALER_EXCHANGE_RefundResultCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_RefundHandle *rh;
+ struct GNUNET_CURL_Context *ctx;
+ json_t *refund_obj;
+ CURL *eh;
+
+refund_obj = json_pack ("{s:o, s:o," /* amount/fee */
+ " s:o, s:o," /* h_contract_terms, coin_pub */
+ " s:I," /* rtransaction id */
+ " s:o, s:o}", /* merchant_pub, merchant_sig */
+ "refund_amount", TALER_JSON_from_amount (amount),
+ "refund_fee", TALER_JSON_from_amount (refund_fee),
+ "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms),
+ "coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
+ "rtransaction_id", (json_int_t) rtransaction_id,
+ "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub),
+ "merchant_sig", GNUNET_JSON_from_data_auto (merchant_sig)
+ );
+ if (NULL == refund_obj)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ rh = GNUNET_new (struct TALER_EXCHANGE_RefundHandle);
+ rh->exchange = exchange;
+ rh->cb = cb;
+ rh->cb_cls = cb_cls;
+ rh->url = TEAH_path_to_url (exchange, "/refund");
+ rh->depconf.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS));
+ rh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND);
+ rh->depconf.h_contract_terms = *h_contract_terms;
+ rh->depconf.coin_pub = *coin_pub;
+ rh->depconf.merchant = *merchant_pub;
+ rh->depconf.rtransaction_id = GNUNET_htonll (rtransaction_id);
+ TALER_amount_hton (&rh->depconf.refund_amount,
+ amount);
+ TALER_amount_hton (&rh->depconf.refund_fee,
+ refund_fee);
+
+ eh = TEL_curl_easy_get (rh->url);
+ GNUNET_assert (NULL != (rh->json_enc =
+ json_dumps (refund_obj,
+ JSON_COMPACT)));
+ json_decref (refund_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for refund: `%s'\n",
+ rh->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ rh->json_enc));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ strlen (rh->json_enc)));
+ ctx = TEAH_handle_to_context (exchange);
+ rh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_refund_finished,
+ rh);
+ return rh;
+}
+
+
+/**
+ * Cancel a refund permission request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param refund the refund permission request handle
+ */
+void
+TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund)
+{
+ if (NULL != refund->job)
+ {
+ GNUNET_CURL_job_cancel (refund->job);
+ refund->job = NULL;
+ }
+ GNUNET_free (refund->url);
+ GNUNET_free (refund->json_enc);
+ GNUNET_free (refund);
+}
+
+
+/* end of exchange_api_refund.c */
diff --git a/src/lib/exchange_api_reserve.c b/src/lib/exchange_api_reserve.c
new file mode 100644
index 00000000..d4d0cb9a
--- /dev/null
+++ b/src/lib/exchange_api_reserve.c
@@ -0,0 +1,1203 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-lib/exchange_api_reserve.c
+ * @brief Implementation of the /reserve requests of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/* ********************** /reserve/status ********************** */
+
+/**
+ * @brief A Withdraw Status Handle
+ */
+struct TALER_EXCHANGE_ReserveStatusHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReserveStatusResultCallback cb;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Parse history given in JSON format and return it in binary
+ * format.
+ *
+ * @param exchange connection to the exchange we can use
+ * @param history JSON array with the history
+ * @param reserve_pub public key of the reserve to inspect
+ * @param currency currency we expect the balance to be in
+ * @param[out] balance final balance
+ * @param history_length number of entries in @a history
+ * @param[out] rhistory array of length @a history_length, set to the
+ * parsed history entries
+ * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
+ * were set,
+ * #GNUNET_SYSERR if there was a protocol violation in @a history
+ */
+static int
+parse_reserve_history (struct TALER_EXCHANGE_Handle *exchange,
+ const json_t *history,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *currency,
+ struct TALER_Amount *balance,
+ unsigned int history_length,
+ struct TALER_EXCHANGE_ReserveHistory *rhistory)
+{
+ struct GNUNET_HashCode uuid[history_length];
+ unsigned int uuid_off;
+ struct TALER_Amount total_in;
+ struct TALER_Amount total_out;
+ size_t off;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_out));
+ uuid_off = 0;
+ for (off=0;off<history_length;off++)
+ {
+ json_t *transaction;
+ struct TALER_Amount amount;
+ const char *type;
+ struct GNUNET_JSON_Specification hist_spec[] = {
+ GNUNET_JSON_spec_string ("type", &type),
+ TALER_JSON_spec_amount ("amount",
+ &amount),
+ /* 'wire' and 'signature' are optional depending on 'type'! */
+ GNUNET_JSON_spec_end()
+ };
+
+ transaction = json_array_get (history,
+ off);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ hist_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rhistory[off].amount = amount;
+
+ if (0 == strcasecmp (type,
+ "DEPOSIT"))
+ {
+ const char *wire_url;
+ void *wire_reference;
+ size_t wire_reference_size;
+ struct GNUNET_TIME_Absolute timestamp;
+
+ struct GNUNET_JSON_Specification withdraw_spec[] = {
+ GNUNET_JSON_spec_varsize ("wire_reference",
+ &wire_reference,
+ &wire_reference_size),
+ GNUNET_JSON_spec_absolute_time ("timestamp",
+ &timestamp),
+ GNUNET_JSON_spec_string ("sender_account_url",
+ &wire_url),
+ GNUNET_JSON_spec_end()
+ };
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_DEPOSIT;
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_in,
+ &total_in,
+ &amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ withdraw_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rhistory[off].details.in_details.sender_url = GNUNET_strdup (wire_url);
+ rhistory[off].details.in_details.wire_reference = wire_reference;
+ rhistory[off].details.in_details.wire_reference_size = wire_reference_size;
+ rhistory[off].details.in_details.timestamp = timestamp;
+ /* end type==DEPOSIT */
+ }
+ else if (0 == strcasecmp (type,
+ "WITHDRAW"))
+ {
+ struct TALER_ReserveSignatureP sig;
+ struct TALER_WithdrawRequestPS withdraw_purpose;
+ struct GNUNET_JSON_Specification withdraw_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &sig),
+ TALER_JSON_spec_amount_nbo ("withdraw_fee",
+ &withdraw_purpose.withdraw_fee),
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &withdraw_purpose.h_denomination_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
+ &withdraw_purpose.h_coin_envelope),
+ GNUNET_JSON_spec_end()
+ };
+ unsigned int i;
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_WITHDRAWAL;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ withdraw_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ withdraw_purpose.purpose.size
+ = htonl (sizeof (withdraw_purpose));
+ withdraw_purpose.purpose.purpose
+ = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
+ withdraw_purpose.reserve_pub = *reserve_pub;
+ TALER_amount_hton (&withdraw_purpose.amount_with_fee,
+ &amount);
+ /* Check that the signature is a valid withdraw request */
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
+ &withdraw_purpose.purpose,
+ &sig.eddsa_signature,
+ &reserve_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ /* TODO: check that withdraw fee matches expectations! */
+ rhistory[off].details.out_authorization_sig
+ = json_object_get (transaction,
+ "signature");
+ /* Check check that the same withdraw transaction
+ isn't listed twice by the exchange. We use the
+ "uuid" array to remember the hashes of all
+ purposes, and compare the hashes to find
+ duplicates. */
+ GNUNET_CRYPTO_hash (&withdraw_purpose,
+ ntohl (withdraw_purpose.purpose.size),
+ &uuid[uuid_off]);
+ for (i=0;i<uuid_off;i++)
+ {
+ if (0 == memcmp (&uuid[uuid_off],
+ &uuid[i],
+ sizeof (struct GNUNET_HashCode)))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ }
+ uuid_off++;
+
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_out,
+ &total_out,
+ &amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ /* end type==WITHDRAW */
+ }
+ else if (0 == strcasecmp (type,
+ "PAYBACK"))
+ {
+ struct TALER_PaybackConfirmationPS pc;
+ struct GNUNET_TIME_Absolute timestamp;
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification payback_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &pc.coin_pub),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rhistory[off].details.payback_details.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rhistory[off].details.payback_details.exchange_pub),
+ GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
+ &pc.timestamp),
+ GNUNET_JSON_spec_end()
+ };
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_PAYBACK;
+ rhistory[off].amount = amount;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ payback_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rhistory[off].details.payback_details.coin_pub = pc.coin_pub;
+ TALER_amount_hton (&pc.payback_amount,
+ &amount);
+ pc.purpose.size = htonl (sizeof (pc));
+ pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK);
+ pc.reserve_pub = *reserve_pub;
+ timestamp = GNUNET_TIME_absolute_ntoh (pc.timestamp);
+ rhistory[off].details.payback_details.timestamp = timestamp;
+
+ key_state = TALER_EXCHANGE_get_keys (exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &rhistory[off].details.payback_details.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK,
+ &pc.purpose,
+ &rhistory[off].details.payback_details.exchange_sig.eddsa_signature,
+ &rhistory[off].details.payback_details.exchange_pub.eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_in,
+ &total_in,
+ &rhistory[off].amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* end type==PAYBACK */
+ }
+ else if (0 == strcasecmp (type,
+ "CLOSING"))
+ {
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct TALER_ReserveCloseConfirmationPS rcc;
+ struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_JSON_Specification closing_spec[] = {
+ GNUNET_JSON_spec_string ("receiver_account_details",
+ &rhistory[off].details.close_details.receiver_account_details),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &rhistory[off].details.close_details.wtid),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rhistory[off].details.close_details.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rhistory[off].details.close_details.exchange_pub),
+ TALER_JSON_spec_amount_nbo ("closing_fee",
+ &rcc.closing_fee),
+ GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
+ &rcc.timestamp),
+ GNUNET_JSON_spec_end()
+ };
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_CLOSE;
+ rhistory[off].amount = amount;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ closing_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ TALER_amount_hton (&rcc.closing_amount,
+ &amount);
+ GNUNET_CRYPTO_hash (rhistory[off].details.close_details.receiver_account_details,
+ strlen (rhistory[off].details.close_details.receiver_account_details) + 1,
+ &rcc.h_wire);
+ rcc.wtid = rhistory[off].details.close_details.wtid;
+ rcc.purpose.size = htonl (sizeof (rcc));
+ rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED);
+ rcc.reserve_pub = *reserve_pub;
+ timestamp = GNUNET_TIME_absolute_ntoh (rcc.timestamp);
+ rhistory[off].details.close_details.timestamp = timestamp;
+
+ key_state = TALER_EXCHANGE_get_keys (exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &rhistory[off].details.close_details.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED,
+ &rcc.purpose,
+ &rhistory[off].details.close_details.exchange_sig.eddsa_signature,
+ &rhistory[off].details.close_details.exchange_pub.eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_out,
+ &total_out,
+ &rhistory[off].amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* end type==CLOSING */
+ }
+ else
+ {
+ /* unexpected 'type', protocol incompatibility, complain! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ /* check balance = total_in - total_out < withdraw-amount */
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (balance,
+ &total_in,
+ &total_out))
+ {
+ /* total_in < total_out, why did the exchange ever allow this!? */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free memory (potentially) allocated by #parse_reserve_history().
+ *
+ * @param rhistory result to free
+ * @param len number of entries in @a rhistory
+ */
+static void
+free_rhistory (struct TALER_EXCHANGE_ReserveHistory *rhistory,
+ unsigned int len)
+{
+ for (unsigned int i=0;i<len;i++)
+ {
+ switch (rhistory[i].type)
+ {
+ case TALER_EXCHANGE_RTT_DEPOSIT:
+ GNUNET_free_non_null (rhistory[i].details.in_details.wire_reference);
+ GNUNET_free_non_null (rhistory[i].details.in_details.sender_url);
+ break;
+ case TALER_EXCHANGE_RTT_WITHDRAWAL:
+ break;
+ case TALER_EXCHANGE_RTT_PAYBACK:
+ break;
+ case TALER_EXCHANGE_RTT_CLOSE:
+ // should we free "receiver_account_details" ?
+ break;
+ }
+ }
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserve/status request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReserveStatusHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserve_status_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReserveStatusHandle *rsh = cls;
+ const json_t *j = response;
+
+ rsh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ {
+ /* TODO: move into separate function... */
+ json_t *history;
+ unsigned int len;
+ struct TALER_Amount balance;
+ struct TALER_Amount balance_from_history;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("balance", &balance),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ break;
+ }
+ history = json_object_get (j,
+ "history");
+ if (NULL == history)
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ break;
+ }
+ len = json_array_size (history);
+ {
+ struct TALER_EXCHANGE_ReserveHistory rhistory[len];
+
+ memset (rhistory, 0, sizeof (rhistory));
+ if (GNUNET_OK !=
+ parse_reserve_history (rsh->exchange,
+ history,
+ &rsh->reserve_pub,
+ balance.currency,
+ &balance_from_history,
+ len,
+ rhistory))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ if ( (0 != response_code) &&
+ (0 !=
+ TALER_amount_cmp (&balance_from_history,
+ &balance)) )
+ {
+ /* exchange cannot add up balances!? */
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ if (0 != response_code)
+ {
+ rsh->cb (rsh->cb_cls,
+ response_code,
+ TALER_EC_NONE,
+ j,
+ &balance,
+ len,
+ rhistory);
+ rsh->cb = NULL;
+ }
+ free_rhistory (rhistory,
+ len);
+ }
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ if (NULL != rsh->cb)
+ {
+ rsh->cb (rsh->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ j,
+ NULL,
+ 0, NULL);
+ rsh->cb = NULL;
+ }
+ TALER_EXCHANGE_reserve_status_cancel (rsh);
+}
+
+
+/**
+ * Submit a request to obtain the transaction history of a reserve
+ * from the exchange. Note that while we return the full response to the
+ * caller for further processing, we do already verify that the
+ * response is well-formed (i.e. that signatures included in the
+ * response are all valid and add up to the balance). If the exchange's
+ * reply is not well-formed, we return an HTTP status code of zero to
+ * @a cb.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param reserve_pub public key of the reserve to inspect
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_ReserveStatusHandle *
+TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ TALER_EXCHANGE_ReserveStatusResultCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReserveStatusHandle *rsh;
+ struct GNUNET_CURL_Context *ctx;
+ CURL *eh;
+ char *pub_str;
+ char *arg_str;
+
+ if (GNUNET_YES !=
+ TEAH_handle_is_ready (exchange))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ pub_str = GNUNET_STRINGS_data_to_string_alloc (reserve_pub,
+ sizeof (struct TALER_ReservePublicKeyP));
+ GNUNET_asprintf (&arg_str,
+ "/reserve/status?reserve_pub=%s",
+ pub_str);
+ GNUNET_free (pub_str);
+ rsh = GNUNET_new (struct TALER_EXCHANGE_ReserveStatusHandle);
+ rsh->exchange = exchange;
+ rsh->cb = cb;
+ rsh->cb_cls = cb_cls;
+ rsh->reserve_pub = *reserve_pub;
+ rsh->url = TEAH_path_to_url (exchange,
+ arg_str);
+ GNUNET_free (arg_str);
+
+ eh = TEL_curl_easy_get (rsh->url);
+ ctx = TEAH_handle_to_context (exchange);
+ rsh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_NO,
+ &handle_reserve_status_finished,
+ rsh);
+ return rsh;
+}
+
+
+/**
+ * Cancel a reserve status request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rsh the reserve status request handle
+ */
+void
+TALER_EXCHANGE_reserve_status_cancel (struct TALER_EXCHANGE_ReserveStatusHandle *rsh)
+{
+ if (NULL != rsh->job)
+ {
+ GNUNET_CURL_job_cancel (rsh->job);
+ rsh->job = NULL;
+ }
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+}
+
+
+/* ********************** /reserve/withdraw ********************** */
+
+/**
+ * @brief A Withdraw Sign Handle
+ */
+struct TALER_EXCHANGE_ReserveWithdrawHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * JSON encoding of the request to POST.
+ */
+ char *json_enc;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReserveWithdrawResultCallback cb;
+
+ /**
+ * Secrets of the planchet.
+ */
+ struct TALER_PlanchetSecretsP ps;
+
+ /**
+ * Denomination key we are withdrawing.
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Hash of the public key of the coin we are signing.
+ */
+ struct GNUNET_HashCode c_hash;
+
+ /**
+ * Public key of the reserve we are withdrawing from.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+};
+
+
+/**
+ * We got a 200 OK response for the /reserve/withdraw operation.
+ * Extract the coin's signature and return it to the caller.
+ * The signature we get from the exchange is for the blinded value.
+ * Thus, we first must unblind it and then should verify its
+ * validity against our coin's hash.
+ *
+ * If everything checks out, we return the unblinded signature
+ * to the application via the callback.
+ *
+ * @param wsh operation handle
+ * @param json reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static int
+reserve_withdraw_ok (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh,
+ const json_t *json)
+{
+ struct GNUNET_CRYPTO_RsaSignature *blind_sig;
+ struct TALER_FreshCoin fc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_rsa_signature ("ev_sig",
+ &blind_sig),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_planchet_to_coin (&wsh->pk->key,
+ blind_sig,
+ &wsh->ps,
+ &wsh->c_hash,
+ &fc))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_JSON_parse_free (spec);
+
+ /* signature is valid, return it to the application */
+ wsh->cb (wsh->cb_cls,
+ MHD_HTTP_OK,
+ TALER_EC_NONE,
+ &fc.sig,
+ json);
+ /* make sure callback isn't called again after return */
+ wsh->cb = NULL;
+ GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature);
+ return GNUNET_OK;
+}
+
+
+/**
+ * We got a 403 FORBIDDEN response for the /reserve/withdraw operation.
+ * Check the signatures on the withdraw transactions in the provided
+ * history and that the balances add up. We don't do anything directly
+ * with the information, as the JSON will be returned to the application.
+ * However, our job is ensuring that the exchange followed the protocol, and
+ * this in particular means checking all of the signatures in the history.
+ *
+ * @param wsh operation handle
+ * @param json reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static int
+reserve_withdraw_payment_required (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh,
+ const json_t *json)
+{
+ struct TALER_Amount balance;
+ struct TALER_Amount balance_from_history;
+ struct TALER_Amount requested_amount;
+ json_t *history;
+ size_t len;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("balance", &balance),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ history = json_object_get (json,
+ "history");
+ if (NULL == history)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* go over transaction history and compute
+ total incoming and outgoing amounts */
+ len = json_array_size (history);
+ {
+ struct TALER_EXCHANGE_ReserveHistory *rhistory;
+
+ /* Use heap allocation as "len" may be very big and thus this may
+ not fit on the stack. Use "GNUNET_malloc_large" as a malicious
+ exchange may theoretically try to crash us by giving a history
+ that does not fit into our memory. */
+ rhistory = GNUNET_malloc_large (sizeof (struct TALER_EXCHANGE_ReserveHistory) * len);
+ if (NULL == rhistory)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ parse_reserve_history (wsh->exchange,
+ history,
+ &wsh->reserve_pub,
+ balance.currency,
+ &balance_from_history,
+ len,
+ rhistory))
+ {
+ GNUNET_break_op (0);
+ free_rhistory (rhistory,
+ len);
+ return GNUNET_SYSERR;
+ }
+ free_rhistory (rhistory,
+ len);
+ }
+
+ if (0 !=
+ TALER_amount_cmp (&balance_from_history,
+ &balance))
+ {
+ /* exchange cannot add up balances!? */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* Compute how much we expected to charge to the reserve */
+ if (GNUNET_OK !=
+ TALER_amount_add (&requested_amount,
+ &wsh->pk->value,
+ &wsh->pk->fee_withdraw))
+ {
+ /* Overflow here? Very strange, our CPU must be fried... */
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* Check that funds were really insufficient */
+ if (0 >= TALER_amount_cmp (&requested_amount,
+ &balance))
+ {
+ /* Requested amount is smaller or equal to reported balance,
+ so this should not have failed. */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserve/withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReserveWithdrawHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserve_withdraw_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh = cls;
+ const json_t *j = response;
+
+ wsh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ reserve_withdraw_ok (wsh,
+ j))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* The exchange says that the reserve has insufficient funds;
+ check the signatures in the history... */
+ if (GNUNET_OK !=
+ reserve_withdraw_payment_required (wsh,
+ j))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ GNUNET_break (0);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know this reserve. Can happen if we
+ query before the wire transfer went through.
+ We should simply pass the JSON reply to the application. */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ if (NULL != wsh->cb)
+ {
+ wsh->cb (wsh->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ NULL,
+ j);
+ wsh->cb = NULL;
+ }
+ TALER_EXCHANGE_reserve_withdraw_cancel (wsh);
+}
+
+
+/**
+ * Helper function for #TALER_EXCHANGE_reserve_withdraw2() and
+ * #TALER_EXCHANGE_reserve_withdraw().
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param pk kind of coin to create
+ * @param reserve_sig signature from the reserve authorizing the withdrawal
+ * @param reserve_pub public key of the reserve to withdraw from
+ * @param ps secrets of the planchet
+ * caller must have committed this value to disk before the call (with @a pk)
+ * @param pd planchet details matching @a ps
+ * @param res_cb the callback to call when the final result for this request is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_ReserveWithdrawHandle *
+reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_PlanchetSecretsP *ps,
+ const struct TALER_PlanchetDetail *pd,
+ TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh;
+ struct GNUNET_CURL_Context *ctx;
+ json_t *withdraw_obj;
+ CURL *eh;
+
+ wsh = GNUNET_new (struct TALER_EXCHANGE_ReserveWithdrawHandle);
+ wsh->exchange = exchange;
+ wsh->cb = res_cb;
+ wsh->cb_cls = res_cb_cls;
+ wsh->pk = pk;
+ wsh->reserve_pub = *reserve_pub;
+ wsh->c_hash = pd->c_hash;
+ withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub and coin_ev */
+ " s:o, s:o}",/* reserve_pub and reserve_sig */
+ "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->key.rsa_public_key),
+ "coin_ev", GNUNET_JSON_from_data (pd->coin_ev,
+ pd->coin_ev_size),
+ "reserve_pub", GNUNET_JSON_from_data_auto (reserve_pub),
+ "reserve_sig", GNUNET_JSON_from_data_auto (reserve_sig));
+ if (NULL == withdraw_obj)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ wsh->ps = *ps;
+ wsh->url = TEAH_path_to_url (exchange, "/reserve/withdraw");
+
+ eh = TEL_curl_easy_get (wsh->url);
+ GNUNET_assert (NULL != (wsh->json_enc =
+ json_dumps (withdraw_obj,
+ JSON_COMPACT)));
+ json_decref (withdraw_obj);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ wsh->json_enc));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ strlen (wsh->json_enc)));
+ ctx = TEAH_handle_to_context (exchange);
+ wsh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_reserve_withdraw_finished,
+ wsh);
+ return wsh;
+}
+
+
+/**
+ * Withdraw a coin from the exchange using a /reserve/withdraw request. Note
+ * that to ensure that no money is lost in case of hardware failures,
+ * the caller must have committed (most of) the arguments to disk
+ * before calling, and be ready to repeat the request with the same
+ * arguments in case of failures.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param pk kind of coin to create
+ * @param reserve_priv private key of the reserve to withdraw from
+ * @param ps secrets of the planchet
+ * caller must have committed this value to disk before the call (with @a pk)
+ * @param res_cb the callback to call when the final result for this request is available
+ * @param res_cb_cls closure for the above callback
+ * @return handle for the operation on success, NULL on error, i.e.
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_ReserveWithdrawHandle *
+TALER_EXCHANGE_reserve_withdraw (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_PlanchetSecretsP *ps,
+ TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_Amount amount_with_fee;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct TALER_WithdrawRequestPS req;
+ struct TALER_PlanchetDetail pd;
+ struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &req.reserve_pub.eddsa_pub);
+ req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS));
+ req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
+ if (GNUNET_OK !=
+ TALER_amount_add (&amount_with_fee,
+ &pk->fee_withdraw,
+ &pk->value))
+ {
+ /* exchange gave us denomination keys that overflow like this!? */
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ TALER_amount_hton (&req.amount_with_fee,
+ &amount_with_fee);
+ TALER_amount_hton (&req.withdraw_fee,
+ &pk->fee_withdraw);
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&pk->key,
+ ps,
+ &pd))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ req.h_denomination_pub = pd.denom_pub_hash;
+ GNUNET_CRYPTO_hash (pd.coin_ev,
+ pd.coin_ev_size,
+ &req.h_coin_envelope);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+ &req.purpose,
+ &reserve_sig.eddsa_signature));
+ wsh = reserve_withdraw_internal (exchange,
+ pk,
+ &reserve_sig,
+ &req.reserve_pub,
+ ps,
+ &pd,
+ res_cb,
+ res_cb_cls);
+ GNUNET_free (pd.coin_ev);
+ return wsh;
+}
+
+
+/**
+ * Withdraw a coin from the exchange using a /reserve/withdraw
+ * request. This API is typically used by a wallet to withdraw a tip
+ * where the reserve's signature was created by the merchant already.
+ *
+ * Note that to ensure that no money is lost in case of hardware
+ * failures, the caller must have committed (most of) the arguments to
+ * disk before calling, and be ready to repeat the request with the
+ * same arguments in case of failures.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param pk kind of coin to create
+ * @param reserve_sig signature from the reserve authorizing the withdrawal
+ * @param reserve_pub public key of the reserve to withdraw from
+ * @param ps secrets of the planchet
+ * caller must have committed this value to disk before the call (with @a pk)
+ * @param res_cb the callback to call when the final result for this request is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_ReserveWithdrawHandle *
+TALER_EXCHANGE_reserve_withdraw2 (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_PlanchetSecretsP *ps,
+ TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh;
+ struct TALER_PlanchetDetail pd;
+
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&pk->key,
+ ps,
+ &pd))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ wsh = reserve_withdraw_internal (exchange,
+ pk,
+ reserve_sig,
+ reserve_pub,
+ ps,
+ &pd,
+ res_cb,
+ res_cb_cls);
+ GNUNET_free (pd.coin_ev);
+ return wsh;
+}
+
+
+/**
+ * Cancel a withdraw status request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param sign the withdraw sign request handle
+ */
+void
+TALER_EXCHANGE_reserve_withdraw_cancel (struct TALER_EXCHANGE_ReserveWithdrawHandle *sign)
+{
+ if (NULL != sign->job)
+ {
+ GNUNET_CURL_job_cancel (sign->job);
+ sign->job = NULL;
+ }
+ GNUNET_free (sign->url);
+ GNUNET_free (sign->json_enc);
+ GNUNET_free (sign);
+}
+
+
+/* end of exchange_api_reserve.c */
diff --git a/src/lib/exchange_api_track_transaction.c b/src/lib/exchange_api_track_transaction.c
new file mode 100644
index 00000000..0942ce84
--- /dev/null
+++ b/src/lib/exchange_api_track_transaction.c
@@ -0,0 +1,367 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-lib/exchange_api_track_transaction.c
+ * @brief Implementation of the /track/transaction request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Deposit Wtid Handle
+ */
+struct TALER_EXCHANGE_TrackTransactionHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * JSON encoding of the request to POST.
+ */
+ char *json_enc;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_TrackTransactionCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Information the exchange should sign in response.
+ * (with pre-filled fields from the request).
+ */
+ struct TALER_ConfirmWirePS depconf;
+
+};
+
+
+/**
+ * Verify that the signature on the "200 OK" response
+ * from the exchange is valid.
+ *
+ * @param dwh deposit wtid handle
+ * @param json json reply with the signature
+ * @param[out] exchange_pub set to the exchange's public key
+ * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
+ */
+static int
+verify_deposit_wtid_signature_ok (const struct TALER_EXCHANGE_TrackTransactionHandle *dwh,
+ const json_t *json,
+ struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ struct TALER_ExchangeSignatureP exchange_sig;
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ key_state = TALER_EXCHANGE_get_keys (dwh->exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE,
+ &dwh->depconf.purpose,
+ &exchange_sig.eddsa_signature,
+ &exchange_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /track/transaction request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_TrackTransactionHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_deposit_wtid_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_TrackTransactionHandle *dwh = cls;
+ const struct TALER_WireTransferIdentifierRawP *wtid = NULL;
+ struct GNUNET_TIME_Absolute execution_time = GNUNET_TIME_UNIT_FOREVER_ABS;
+ const struct TALER_Amount *coin_contribution = NULL;
+ struct TALER_Amount coin_contribution_s;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangePublicKeyP *ep = NULL;
+ const json_t *j = response;
+
+ dwh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("wtid", &dwh->depconf.wtid),
+ GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time),
+ TALER_JSON_spec_amount ("coin_contribution", &coin_contribution_s),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ break;
+ }
+ wtid = &dwh->depconf.wtid;
+ dwh->depconf.execution_time = GNUNET_TIME_absolute_hton (execution_time);
+ TALER_amount_hton (&dwh->depconf.coin_contribution,
+ &coin_contribution_s);
+ coin_contribution = &coin_contribution_s;
+ if (GNUNET_OK !=
+ verify_deposit_wtid_signature_ok (dwh,
+ j,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ else
+ {
+ ep = &exchange_pub;
+ }
+ }
+ break;
+ case MHD_HTTP_ACCEPTED:
+ {
+ /* Transaction known, but not executed yet */
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Exchange does not know about transaction;
+ we should pass the reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ dwh->cb (dwh->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ ep,
+ j,
+ wtid,
+ execution_time,
+ coin_contribution);
+ TALER_EXCHANGE_track_transaction_cancel (dwh);
+}
+
+
+/**
+ * Obtain wire transfer details about an existing deposit operation.
+ *
+ * @param exchange the exchange to query
+ * @param merchant_priv the merchant's private key
+ * @param h_wire hash of merchant's wire transfer details
+ * @param h_contract_terms hash of the proposal data from the contract
+ * between merchant and customer
+ * @param coin_pub public key of the coin
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to abort request
+ */
+struct TALER_EXCHANGE_TrackTransactionHandle *
+TALER_EXCHANGE_track_transaction (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ TALER_EXCHANGE_TrackTransactionCallback cb,
+ void *cb_cls)
+{
+ struct TALER_DepositTrackPS dtp;
+ struct TALER_MerchantSignatureP merchant_sig;
+ struct TALER_EXCHANGE_TrackTransactionHandle *dwh;
+ struct GNUNET_CURL_Context *ctx;
+ json_t *deposit_wtid_obj;
+ CURL *eh;
+
+ if (GNUNET_YES !=
+ TEAH_handle_is_ready (exchange))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ dtp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION);
+ dtp.purpose.size = htonl (sizeof (dtp));
+ dtp.h_contract_terms = *h_contract_terms;
+ dtp.h_wire = *h_wire;
+ GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
+ &dtp.merchant.eddsa_pub);
+
+ dtp.coin_pub = *coin_pub;
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
+ &dtp.purpose,
+ &merchant_sig.eddsa_sig));
+ deposit_wtid_obj = json_pack ("{s:o, s:o," /* H_wire, h_contract_terms */
+ " s:o," /* coin_pub */
+ " s:o, s:o}", /* merchant_pub, merchant_sig */
+ "H_wire", GNUNET_JSON_from_data_auto (h_wire),
+ "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms),
+ "coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
+ "merchant_pub", GNUNET_JSON_from_data_auto (&dtp.merchant),
+ "merchant_sig", GNUNET_JSON_from_data_auto (&merchant_sig));
+ if (NULL == deposit_wtid_obj)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ dwh = GNUNET_new (struct TALER_EXCHANGE_TrackTransactionHandle);
+ dwh->exchange = exchange;
+ dwh->cb = cb;
+ dwh->cb_cls = cb_cls;
+ dwh->url = TEAH_path_to_url (exchange, "/track/transaction");
+ dwh->depconf.purpose.size = htonl (sizeof (struct TALER_ConfirmWirePS));
+ dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE);
+ dwh->depconf.h_wire = *h_wire;
+ dwh->depconf.h_contract_terms = *h_contract_terms;
+ dwh->depconf.coin_pub = *coin_pub;
+
+ eh = TEL_curl_easy_get (dwh->url);
+ GNUNET_assert (NULL != (dwh->json_enc =
+ json_dumps (deposit_wtid_obj,
+ JSON_COMPACT)));
+ json_decref (deposit_wtid_obj);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ dwh->json_enc));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ strlen (dwh->json_enc)));
+ ctx = TEAH_handle_to_context (exchange);
+ dwh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_deposit_wtid_finished,
+ dwh);
+ return dwh;
+}
+
+
+/**
+ * Cancel deposit wtid request. This function cannot be used on a request
+ * handle if a response is already served for it.
+ *
+ * @param dwh the wire deposits request handle
+ */
+void
+TALER_EXCHANGE_track_transaction_cancel (struct TALER_EXCHANGE_TrackTransactionHandle *dwh)
+{
+ if (NULL != dwh->job)
+ {
+ GNUNET_CURL_job_cancel (dwh->job);
+ dwh->job = NULL;
+ }
+ GNUNET_free (dwh->url);
+ GNUNET_free (dwh->json_enc);
+ GNUNET_free (dwh);
+}
+
+
+/* end of exchange_api_deposit_wtid.c */
diff --git a/src/lib/exchange_api_track_transfer.c b/src/lib/exchange_api_track_transfer.c
new file mode 100644
index 00000000..88043b7b
--- /dev/null
+++ b/src/lib/exchange_api_track_transfer.c
@@ -0,0 +1,388 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-lib/exchange_api_track_transfer.c
+ * @brief Implementation of the /track/transfer request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /track/transfer Handle
+ */
+struct TALER_EXCHANGE_TrackTransferHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_TrackTransferCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * We got a #MHD_HTTP_OK response for the /track/transfer request.
+ * Check that the response is well-formed and if it is, call the
+ * callback. If not, return an error code.
+ *
+ * This code is very similar to
+ * merchant_api_track_transfer.c::check_track_transfer_response_ok.
+ * Any changes should likely be reflected there as well.
+ *
+ * @param wdh handle to the operation
+ * @param json response we got
+ * @return #GNUNET_OK if we are done and all is well,
+ * #GNUNET_SYSERR if the response was bogus
+ */
+static int
+check_track_transfer_response_ok (struct TALER_EXCHANGE_TrackTransferHandle *wdh,
+ const json_t *json)
+{
+ json_t *details_j;
+ struct GNUNET_HashCode h_wire;
+ struct GNUNET_TIME_Absolute exec_time;
+ struct TALER_Amount total_amount;
+ struct TALER_Amount total_expected;
+ struct TALER_Amount wire_fee;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ unsigned int num_details;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("total", &total_amount),
+ TALER_JSON_spec_amount ("wire_fee", &wire_fee),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub),
+ GNUNET_JSON_spec_fixed_auto ("H_wire", &h_wire),
+ GNUNET_JSON_spec_absolute_time ("execution_time", &exec_time),
+ GNUNET_JSON_spec_json ("deposits", &details_j),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_get_zero (total_amount.currency,
+ &total_expected))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ num_details = json_array_size (details_j);
+ {
+ struct TALER_TrackTransferDetails details[num_details];
+ unsigned int i;
+ struct GNUNET_HashContext *hash_context;
+ struct TALER_WireDepositDetailP dd;
+ struct TALER_WireDepositDataPS wdp;
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+ for (i=0;i<num_details;i++)
+ {
+ struct TALER_TrackTransferDetails *detail = &details[i];
+ struct json_t *detail_j = json_array_get (details_j, i);
+ struct GNUNET_JSON_Specification spec_detail[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &detail->h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub),
+ TALER_JSON_spec_amount ("deposit_value", &detail->coin_value),
+ TALER_JSON_spec_amount ("deposit_fee", &detail->coin_fee),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (detail_j,
+ spec_detail,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ GNUNET_CRYPTO_hash_context_abort (hash_context);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ /* build up big hash for signature checking later */
+ dd.h_contract_terms = detail->h_contract_terms;
+ dd.execution_time = GNUNET_TIME_absolute_hton (exec_time);
+ dd.coin_pub = detail->coin_pub;
+ TALER_amount_hton (&dd.deposit_value,
+ &detail->coin_value);
+ TALER_amount_hton (&dd.deposit_fee,
+ &detail->coin_fee);
+ if ( (GNUNET_OK !=
+ TALER_amount_add (&total_expected,
+ &total_expected,
+ &detail->coin_value)) ||
+ (GNUNET_OK !=
+ TALER_amount_subtract (&total_expected,
+ &total_expected,
+ &detail->coin_fee)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_CRYPTO_hash_context_abort (hash_context);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &dd,
+ sizeof (struct TALER_WireDepositDetailP));
+ }
+ /* Check signature */
+ wdp.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT);
+ wdp.purpose.size = htonl (sizeof (struct TALER_WireDepositDataPS));
+ TALER_amount_hton (&wdp.total,
+ &total_amount);
+ TALER_amount_hton (&wdp.wire_fee,
+ &wire_fee);
+ wdp.merchant_pub = merchant_pub;
+ wdp.h_wire = h_wire;
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &wdp.h_details);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (TALER_EXCHANGE_get_keys (wdh->exchange),
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify
+ (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT,
+ &wdp.purpose,
+ &exchange_sig.eddsa_signature,
+ &exchange_pub.eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_amount_subtract (&total_expected,
+ &total_expected,
+ &wire_fee))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ TALER_amount_cmp (&total_expected,
+ &total_amount))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ wdh->cb (wdh->cb_cls,
+ MHD_HTTP_OK,
+ TALER_EC_NONE,
+ &exchange_pub,
+ json,
+ &h_wire,
+ exec_time,
+ &total_amount,
+ &wire_fee,
+ num_details,
+ details);
+ }
+ GNUNET_JSON_parse_free (spec);
+ TALER_EXCHANGE_track_transfer_cancel (wdh);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /track/transfer request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_TrackTransferHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_track_transfer_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_TrackTransferHandle *wdh = cls;
+ const json_t *j = response;
+
+ wdh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK ==
+ check_track_transfer_response_ok (wdh,
+ j))
+ return;
+ GNUNET_break_op (0);
+ response_code = 0;
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Exchange does not know about transaction;
+ we should pass the reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ wdh->cb (wdh->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ NULL,
+ j,
+ NULL,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ NULL,
+ NULL,
+ 0, NULL);
+ TALER_EXCHANGE_track_transfer_cancel (wdh);
+}
+
+
+/**
+ * Query the exchange about which transactions were combined
+ * to create a wire transfer.
+ *
+ * @param exchange exchange to query
+ * @param wtid raw wire transfer identifier to get information about
+ * @param cb callback to call
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation
+ */
+struct TALER_EXCHANGE_TrackTransferHandle *
+TALER_EXCHANGE_track_transfer (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_EXCHANGE_TrackTransferCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_TrackTransferHandle *wdh;
+ struct GNUNET_CURL_Context *ctx;
+ char *buf;
+ char *path;
+ CURL *eh;
+
+ if (GNUNET_YES !=
+ TEAH_handle_is_ready (exchange))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ wdh = GNUNET_new (struct TALER_EXCHANGE_TrackTransferHandle);
+ wdh->exchange = exchange;
+ wdh->cb = cb;
+ wdh->cb_cls = cb_cls;
+
+ buf = GNUNET_STRINGS_data_to_string_alloc (wtid,
+ sizeof (struct TALER_WireTransferIdentifierRawP));
+ GNUNET_asprintf (&path,
+ "/track/transfer?wtid=%s",
+ buf);
+ wdh->url = TEAH_path_to_url (wdh->exchange,
+ path);
+ GNUNET_free (buf);
+ GNUNET_free (path);
+
+ eh = TEL_curl_easy_get (wdh->url);
+ ctx = TEAH_handle_to_context (exchange);
+ wdh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_track_transfer_finished,
+ wdh);
+ return wdh;
+}
+
+
+/**
+ * Cancel wire deposits request. This function cannot be used on a request
+ * handle if a response is already served for it.
+ *
+ * @param wdh the wire deposits request handle
+ */
+void
+TALER_EXCHANGE_track_transfer_cancel (struct TALER_EXCHANGE_TrackTransferHandle *wdh)
+{
+ if (NULL != wdh->job)
+ {
+ GNUNET_CURL_job_cancel (wdh->job);
+ wdh->job = NULL;
+ }
+ GNUNET_free (wdh->url);
+ GNUNET_free (wdh);
+}
+
+
+/* end of exchange_api_wire_deposits.c */
diff --git a/src/lib/exchange_api_wire.c b/src/lib/exchange_api_wire.c
new file mode 100644
index 00000000..85d1835c
--- /dev/null
+++ b/src/lib/exchange_api_wire.c
@@ -0,0 +1,442 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-lib/exchange_api_wire.c
+ * @brief Implementation of the /wire request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "taler_wire_lib.h"
+#include "taler_signatures.h"
+#include "taler_wire_plugin.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Wire Handle
+ */
+struct TALER_EXCHANGE_WireHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_WireResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * List of wire fees by method.
+ */
+struct FeeMap
+{
+ /**
+ * Next entry in list.
+ */
+ struct FeeMap *next;
+
+ /**
+ * Wire method this fee structure is for.
+ */
+ char *method;
+
+ /**
+ * Array of wire fees, also linked list, but allocated
+ * only once.
+ */
+ struct TALER_EXCHANGE_WireAggregateFees *fee_list;
+};
+
+
+/**
+ * Frees @a fm.
+ *
+ * @param fm memory to release
+ */
+static void
+free_fees (struct FeeMap *fm)
+{
+ while (NULL != fm)
+ {
+ struct FeeMap *fe = fm->next;
+
+ GNUNET_free (fm->fee_list);
+ GNUNET_free (fm->method);
+ GNUNET_free (fm);
+ fm = fe;
+ }
+}
+
+
+/**
+ * Parse wire @a fees and return map.
+ *
+ * @param fees json AggregateTransferFee to parse
+ * @return NULL on error
+ */
+static struct FeeMap *
+parse_fees (json_t *fees)
+{
+ struct FeeMap *fm = NULL;
+ const char *key;
+ json_t *fee_array;
+
+ json_object_foreach (fees, key, fee_array) {
+ struct FeeMap *fe = GNUNET_new (struct FeeMap);
+ int len;
+ unsigned int idx;
+ json_t *fee;
+
+ if (0 == (len = json_array_size (fee_array)))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (fe);
+ continue; /* skip */
+ }
+ fe->method = GNUNET_strdup (key);
+ fe->next = fm;
+ fe->fee_list = GNUNET_new_array (len,
+ struct TALER_EXCHANGE_WireAggregateFees);
+ fm = fe;
+ json_array_foreach (fee_array, idx, fee)
+ {
+ struct TALER_EXCHANGE_WireAggregateFees *wa = &fe->fee_list[idx];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("sig",
+ &wa->master_sig),
+ TALER_JSON_spec_amount ("wire_fee",
+ &wa->wire_fee),
+ TALER_JSON_spec_amount ("closing_fee",
+ &wa->closing_fee),
+ GNUNET_JSON_spec_absolute_time ("start_date",
+ &wa->start_date),
+ GNUNET_JSON_spec_absolute_time ("end_date",
+ &wa->end_date),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (fee,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ free_fees (fm);
+ return NULL;
+ }
+ if (idx + 1 < len)
+ wa->next = &fe->fee_list[idx + 1];
+ else
+ wa->next = NULL;
+ }
+ }
+ return fm;
+}
+
+
+/**
+ * Find fee by @a method.
+ *
+ * @param fm map to look in
+ * @param method key to look for
+ * @return NULL if fee is not specified in @a fm
+ */
+static const struct TALER_EXCHANGE_WireAggregateFees *
+lookup_fee (const struct FeeMap *fm,
+ const char *method)
+{
+ for (;NULL != fm; fm = fm->next)
+ if (0 == strcasecmp (fm->method,
+ method))
+ return fm->fee_list;
+ return NULL;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /wire request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_WireHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_wire_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_WireHandle *wh = cls;
+ enum TALER_ErrorCode ec;
+ const json_t *j = response;
+
+ TALER_LOG_DEBUG ("Checking raw /wire response\n");
+ wh->job = NULL;
+ ec = TALER_EC_NONE;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ {
+ json_t *accounts;
+ json_t *fees;
+ int num_accounts;
+ struct FeeMap *fm;
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("accounts", &accounts),
+ GNUNET_JSON_spec_json ("fees", &fees),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ /* bogus reply */
+ GNUNET_break_op (0);
+ response_code = 0;
+ ec = TALER_EC_SERVER_JSON_INVALID;
+ break;
+ }
+ if (0 == (num_accounts = json_array_size (accounts)))
+ {
+ /* bogus reply */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ response_code = 0;
+ ec = TALER_EC_SERVER_JSON_INVALID;
+ break;
+ }
+ if (NULL == (fm = parse_fees (fees)))
+ {
+ /* bogus reply */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ response_code = 0;
+ ec = TALER_EC_SERVER_JSON_INVALID;
+ break;
+ }
+
+ key_state = TALER_EXCHANGE_get_keys (wh->exchange);
+ /* parse accounts */
+ {
+ struct TALER_EXCHANGE_WireAccount was[num_accounts];
+
+ for (unsigned int i=0;i<num_accounts;i++)
+ {
+ struct TALER_EXCHANGE_WireAccount *wa = &was[i];
+ json_t *account;
+ struct GNUNET_JSON_Specification spec_account[] = {
+ GNUNET_JSON_spec_string ("url", &wa->url),
+ GNUNET_JSON_spec_fixed_auto ("master_sig", &wa->master_sig),
+ GNUNET_JSON_spec_end()
+ };
+ char *method;
+
+ account = json_array_get (accounts,
+ i);
+ if (GNUNET_OK !=
+ TALER_JSON_exchange_wire_signature_check (account,
+ &key_state->master_pub))
+ {
+ /* bogus reply */
+ GNUNET_break_op (0);
+ response_code = 0;
+ ec = TALER_EC_SERVER_SIGNATURE_INVALID;
+ break;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (account,
+ spec_account,
+ NULL, NULL))
+ {
+ /* bogus reply */
+ GNUNET_break_op (0);
+ response_code = 0;
+ ec = TALER_EC_SERVER_JSON_INVALID;
+ break;
+ }
+ if (NULL == (method = TALER_WIRE_payto_get_method (wa->url)))
+ {
+ /* bogus reply */
+ GNUNET_break_op (0);
+ response_code = 0;
+ ec = TALER_EC_SERVER_JSON_INVALID;
+ break;
+ }
+ if (NULL == (wa->fees = lookup_fee (fm,
+ method)))
+ {
+ /* bogus reply */
+ GNUNET_break_op (0);
+ response_code = 0;
+ ec = TALER_EC_SERVER_JSON_INVALID;
+ GNUNET_free (method);
+ break;
+ }
+ GNUNET_free (method);
+ } /* end 'for all accounts */
+ if ( (0 != response_code) &&
+ (NULL != wh->cb) )
+ {
+ wh->cb (wh->cb_cls,
+ response_code,
+ TALER_EC_NONE,
+ num_accounts,
+ was);
+ wh->cb = NULL;
+ }
+ } /* end of 'parse accounts */
+ free_fees (fm);
+ GNUNET_JSON_parse_free (spec);
+ } /* end of MHD_HTTP_OK */
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ if (NULL != wh->cb)
+ wh->cb (wh->cb_cls,
+ response_code,
+ (0 == response_code) ? ec : TALER_JSON_get_error_code (j),
+ 0,
+ NULL);
+ TALER_EXCHANGE_wire_cancel (wh);
+}
+
+
+/**
+ * Obtain information about a exchange's wire instructions.
+ * A exchange may provide wire instructions for creating
+ * a reserve. The wire instructions also indicate
+ * which wire formats merchants may use with the exchange.
+ * This API is typically used by a wallet for wiring
+ * funds, and possibly by a merchant to determine
+ * supported wire formats.
+ *
+ * Note that while we return the (main) response verbatim to the
+ * caller for further processing, we do already verify that the
+ * response is well-formed (i.e. that signatures included in the
+ * response are all valid). If the exchange's reply is not well-formed,
+ * we return an HTTP status code of zero to @a cb.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param wire_cb the callback to call when a reply for this request is available
+ * @param wire_cb_cls closure for the above callback
+ * @return a handle for this request
+ */
+struct TALER_EXCHANGE_WireHandle *
+TALER_EXCHANGE_wire (struct TALER_EXCHANGE_Handle *exchange,
+ TALER_EXCHANGE_WireResultCallback wire_cb,
+ void *wire_cb_cls)
+{
+ struct TALER_EXCHANGE_WireHandle *wh;
+ struct GNUNET_CURL_Context *ctx;
+ CURL *eh;
+
+ if (GNUNET_YES !=
+ TEAH_handle_is_ready (exchange))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ wh = GNUNET_new (struct TALER_EXCHANGE_WireHandle);
+ wh->exchange = exchange;
+ wh->cb = wire_cb;
+ wh->cb_cls = wire_cb_cls;
+ wh->url = TEAH_path_to_url (exchange, "/wire");
+
+ eh = TEL_curl_easy_get (wh->url);
+ ctx = TEAH_handle_to_context (exchange);
+ wh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_wire_finished,
+ wh);
+ return wh;
+}
+
+
+/**
+ * Cancel a wire information request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param wh the wire information request handle
+ */
+void
+TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
+
+
+/* end of exchange_api_wire.c */
diff --git a/src/lib/test_auditor_api.c b/src/lib/test_auditor_api.c
new file mode 100644
index 00000000..cddd2119
--- /dev/null
+++ b/src/lib/test_auditor_api.c
@@ -0,0 +1,549 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file auditor-lib/test_auditor_api.c
+ * @brief testcase to test auditor's HTTP API interface
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_exchange_service.h"
+#include "taler_auditor_service.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler_bank_service.h"
+#include "taler_fakebank_lib.h"
+#include "taler_testing_lib.h"
+#include "taler_testing_auditor_lib.h"
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_auditor_api.conf"
+
+#define CONFIG_FILE_EXPIRE_RESERVE_NOW "test_auditor_api_expire_reserve_now.conf"
+
+/**
+ * URL of the fakebank. Obtained from CONFIG_FILE's
+ * "exchange-wire-test:BANK_URI" option.
+ */
+static char *fakebank_url;
+
+/**
+ * Auditor base URL as it appears in the configuration. Note
+ * that it might differ from the one where the exchange actually
+ * listens from.
+ */
+static char *auditor_url;
+
+/**
+ * Exchange base URL as it appears in the configuration. Note
+ * that it might differ from the one where the exchange actually
+ * listens from.
+ */
+static char *exchange_url;
+
+/**
+ * Account number of the exchange at the bank.
+ */
+#define EXCHANGE_ACCOUNT_NO 2
+
+/**
+ * Account number of some user.
+ */
+#define USER_ACCOUNT_NO 42
+
+/**
+ * User name. Never checked by fakebank.
+ */
+#define USER_LOGIN_NAME "user42"
+
+/**
+ * User password. Never checked by fakebank.
+ */
+#define USER_LOGIN_PASS "pass42"
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_WIREWATCH(label) \
+ TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE)
+
+/**
+ * Execute the taler-exchange-aggregator command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+ TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE)
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
+ TALER_TESTING_cmd_fakebank_transfer (label, amount, \
+ fakebank_url, USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO, \
+ USER_LOGIN_NAME, USER_LOGIN_PASS, exchange_url)
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE_SUBJECT(label,amount,subject) \
+ TALER_TESTING_cmd_fakebank_transfer_with_subject \
+ (label, amount, fakebank_url, USER_ACCOUNT_NO, \
+ EXCHANGE_ACCOUNT_NO, USER_LOGIN_NAME, USER_LOGIN_PASS, \
+ subject, exchange_url)
+
+/**
+ * Run the taler-auditor.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_RUN_AUDITOR(label) \
+ TALER_TESTING_cmd_exec_auditor (label, CONFIG_FILE)
+
+/**
+ * Run the taler-wire-auditor.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_RUN_WIRE_AUDITOR(label) \
+ TALER_TESTING_cmd_exec_wire_auditor (label, CONFIG_FILE)
+
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ /**
+ * Test withdraw.
+ */
+ struct TALER_TESTING_Command withdraw[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1",
+ "EUR:5.01"),
+ /**
+ * Make a reserve exist, according to the previous transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-1"),
+ /**
+ * Withdraw EUR:5.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command spend[] = {
+ /**
+ * Spend the coin.
+ */
+ TALER_TESTING_cmd_deposit ("deposit-simple",
+ "withdraw-coin-1",
+ 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command refresh[] = {
+ /**
+ * Fill reserve with EUR:5, 1ct is for fees. NOTE: the old
+ * test-suite gave a account number of _424_ to the user at
+ * this step; to type less, here the _42_ number is reused.
+ * Does this change the tests semantics?
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-1",
+ "EUR:5.01"),
+ /**
+ * Make previous command effective.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-2"),
+ /**
+ * Withdraw EUR:5.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1",
+ "refresh-create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ /**
+ * Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in
+ * full) Merchant receives EUR:0.99 due to 1 ct deposit fee.
+ */
+ TALER_TESTING_cmd_deposit ("refresh-deposit-partial",
+ "refresh-withdraw-coin-1",
+ 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice\",\"value\":\"EUR:1\"}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:1",
+ MHD_HTTP_OK),
+ /**
+ * Melt the rest of the coin's value (EUR:4.00 = 3x EUR:1.03 + 7x
+ * EUR:0.13) */
+ TALER_TESTING_cmd_refresh_melt_double ("refresh-melt-1",
+ "EUR:4",
+ "refresh-withdraw-coin-1",
+ MHD_HTTP_OK),
+ /**
+ * Complete (successful) melt operation, and withdraw the coins
+ */
+ TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-1",
+ "refresh-melt-1",
+ MHD_HTTP_OK),
+ /**
+ * Try to spend a refreshed EUR:0.1 coin
+ */
+ TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1b",
+ "refresh-reveal-1",
+ 3,
+ TALER_TESTING_make_wire_details (43,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":3}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:0.1",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command track[] = {
+ /**
+ * Run transfers. Note that _actual_ aggregation will NOT
+ * happen here, as each deposit operation is run with a
+ * fresh merchant public key! NOTE: this comment comes
+ * "verbatim" from the old test-suite, and IMO does not explain
+ * a lot!
+ */
+ CMD_EXEC_AGGREGATOR ("run-aggregator"),
+
+ /**
+ * Check all the transfers took place.
+ */
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-499c", exchange_url,
+ "EUR:4.98", 2, 42),
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-99c1", exchange_url,
+ "EUR:0.98", 2, 42),
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-99c", exchange_url,
+ "EUR:0.08", 2, 43),
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-aai-1", exchange_url,
+ "EUR:5.01", 42, 2),
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-aai-2", exchange_url,
+ "EUR:5.01", 42, 2),
+
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ /**
+ * This block checks whether a wire deadline
+ * very far in the future does NOT get aggregated now.
+ */
+ struct TALER_TESTING_Command unaggregation[] = {
+ TALER_TESTING_cmd_check_bank_empty ("far-future-aggregation-a"),
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-unaggregated",
+ "EUR:5.01"),
+ CMD_EXEC_WIREWATCH ("wirewatch-unaggregated"),
+ /* "consume" reserve creation transfer. */
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-unaggregated",
+ exchange_url,
+ "EUR:5.01",
+ 42,
+ 2),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",
+ "create-reserve-unaggregated",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_deposit ("deposit-unaggregated",
+ "withdraw-coin-unaggregated",
+ 0,
+ TALER_TESTING_make_wire_details
+ (43,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_relative_multiply
+ (GNUNET_TIME_UNIT_YEARS,
+ 3000),
+ "EUR:5",
+ MHD_HTTP_OK),
+ CMD_EXEC_AGGREGATOR ("aggregation-attempt"),
+ TALER_TESTING_cmd_check_bank_empty ("far-future-aggregation-b"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command refund[] = {
+ /**
+ * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-r1",
+ "EUR:5.01"),
+ /**
+ * Run wire-watch to trigger the reserve creation.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-3"),
+ /**
+ * Withdraw a 5 EUR coin, at fee of 1 ct
+ */
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",
+ "create-reserve-r1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ /**
+ * Spend 5 EUR of the 5 EUR coin (in full). Merchant would
+ * receive EUR:4.99 due to 1 ct deposit fee.
+ */
+ TALER_TESTING_cmd_deposit ("deposit-refund-1",
+ "withdraw-coin-r1",
+ 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice\",\"value\":\"EUR:5\"}]}",
+ GNUNET_TIME_UNIT_MINUTES,
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_refund ("refund-ok",
+ MHD_HTTP_OK,
+ "EUR:5",
+ "EUR:0.01",
+ "deposit-refund-1"),
+ /**
+ * Spend 4.99 EUR of the refunded 4.99 EUR coin (1ct gone
+ * due to refund) (merchant would receive EUR:4.98 due to
+ * 1 ct deposit fee) */
+ TALER_TESTING_cmd_deposit ("deposit-refund-2",
+ "withdraw-coin-r1",
+ 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"more\",\"value\":\"EUR:5\"}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:4.99",
+ MHD_HTTP_OK),
+ /**
+ * Run transfers. This will do the transfer as refund deadline was
+ * 0.
+ */
+ CMD_EXEC_AGGREGATOR ("run-aggregator-3"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command payback[] = {
+ /**
+ * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per
+ * config.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("payback-create-reserve-1",
+ "EUR:5.01"),
+ /**
+ * Run wire-watch to trigger the reserve creation.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-4"),
+ /**
+ * Withdraw a 5 EUR coin, at fee of 1 ct
+ */
+ TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-1",
+ "payback-create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_revoke ("revoke-1",
+ MHD_HTTP_OK,
+ "payback-withdraw-coin-1",
+ CONFIG_FILE),
+ TALER_TESTING_cmd_payback ("payback-1",
+ MHD_HTTP_OK,
+ "payback-withdraw-coin-1",
+ "EUR:5"),
+ /**
+ * Re-withdraw from this reserve
+ */
+ TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2",
+ "payback-create-reserve-1",
+ "EUR:1",
+ MHD_HTTP_OK),
+ /**
+ * These commands should close the reserve because the aggregator
+ * is given a config file that ovverrides the reserve expiration
+ * time (making it now-ish)
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("short-lived-reserve",
+ "EUR:5.01"),
+ TALER_TESTING_cmd_exec_wirewatch ("short-lived-aggregation",
+ CONFIG_FILE_EXPIRE_RESERVE_NOW),
+ TALER_TESTING_cmd_exec_aggregator ("close-reserves",
+ CONFIG_FILE_EXPIRE_RESERVE_NOW),
+ /**
+ * Fill reserve with EUR:2.02, as withdraw fee is 1 ct per
+ * config, then withdraw two coin, partially spend one, and
+ * then have the rest paid back. Check deposit of other coin
+ * fails. (Do not use EUR:5 here as the EUR:5 coin was
+ * revoked and we did not bother to create a new one...)
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("payback-create-reserve-2",
+ "EUR:2.02"),
+ /**
+ * Make previous command effective.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-5"),
+ /**
+ * Withdraw a 1 EUR coin, at fee of 1 ct
+ */
+ TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2a",
+ "payback-create-reserve-2",
+ "EUR:1",
+ MHD_HTTP_OK),
+ /**
+ * Withdraw a 1 EUR coin, at fee of 1 ct
+ */
+ TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2b",
+ "payback-create-reserve-2",
+ "EUR:1",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_deposit ("payback-deposit-partial",
+ "payback-withdraw-coin-2a",
+ 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:0.5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_revoke ("revoke-2",
+ MHD_HTTP_OK,
+ "payback-withdraw-coin-2a",
+ CONFIG_FILE),
+ TALER_TESTING_cmd_payback ("payback-2",
+ MHD_HTTP_OK,
+ "payback-withdraw-coin-2a",
+ "EUR:0.5"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command commands[] = {
+ CMD_RUN_AUDITOR("virgin-auditor"),
+ CMD_RUN_WIRE_AUDITOR("virgin-wire-auditor"),
+ TALER_TESTING_cmd_batch ("withdraw",
+ withdraw),
+ TALER_TESTING_cmd_batch ("spend",
+ spend),
+ TALER_TESTING_cmd_batch ("refresh",
+ refresh),
+ TALER_TESTING_cmd_batch ("track",
+ track),
+ TALER_TESTING_cmd_batch ("unaggregation",
+ unaggregation),
+ TALER_TESTING_cmd_batch ("refund",
+ refund),
+ TALER_TESTING_cmd_batch ("payback",
+ payback),
+ CMD_RUN_AUDITOR("normal-auditor"),
+ CMD_RUN_WIRE_AUDITOR("normal-wire-auditor"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run_with_fakebank (is,
+ commands,
+ fakebank_url);
+}
+
+
+int
+main (int argc,
+ char * const *argv)
+{
+
+ /* These environment variables get in the way... */
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup ("test-auditor-api",
+ "INFO",
+ NULL);
+ if (NULL == (fakebank_url
+ /* Check fakebank port is available and config cares
+ * about bank url. */
+ = TALER_TESTING_prepare_fakebank (CONFIG_FILE,
+ "account-2")))
+ return 77;
+ TALER_TESTING_cleanup_files (CONFIG_FILE);
+ /* @helpers. Run keyup, create tables, ... Note: it
+ * fetches the port number from config in order to see
+ * if it's available. */
+ switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
+ &auditor_url,
+ &exchange_url))
+ {
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ return 1;
+ case GNUNET_NO:
+ return 77;
+ case GNUNET_OK:
+ if (GNUNET_OK !=
+ /* Set up event loop and reschedule context, plus
+ * start/stop the exchange. It calls TALER_TESTING_setup
+ * which creates the 'is' object.
+ */
+ TALER_TESTING_AUDITOR_setup (&run,
+ NULL,
+ CONFIG_FILE))
+ return 1;
+ break;
+ default:
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+/* end of test_auditor_api.c */
diff --git a/src/lib/test_auditor_api.conf b/src/lib/test_auditor_api.conf
new file mode 100644
index 00000000..532f9c93
--- /dev/null
+++ b/src/lib/test_auditor_api.conf
@@ -0,0 +1,202 @@
+
+# This file is in the public domain.
+#
+[PATHS]
+# Persistant data storage for the testcase
+TALER_TEST_HOME = test_exchange_api_home/
+
+[taler]
+# Currency supported by the exchange (can only be one)
+CURRENCY = EUR
+
+[auditor]
+BASE_URL = "http://localhost:8083/"
+
+# HTTP port the auditor listens to
+PORT = 8083
+
+[exchange]
+
+# how long is one signkey valid?
+signkey_duration = 4 weeks
+
+# how long are the signatures with the signkey valid?
+legal_duration = 2 years
+
+# how long do we provide to clients denomination and signing keys
+# ahead of time?
+lookahead_provide = 4 weeks 1 day
+
+# HTTP port the exchange listens to
+PORT = 8081
+
+# Master public key used to sign the exchange's various keys
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+
+# How to access our database
+DB = postgres
+
+# Base URL of the exchange. Must be set to a URL where the
+# exchange (or the twister) is actually listening.
+BASE_URL = "http://localhost:8081/"
+
+# Keep it short so the test runs fast.
+LOOKAHEAD_SIGN = 12 h
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+
+# Sections starting with "account-" configure the bank accounts
+# of the exchange. The "URL" specifies the account in
+# payto://-format, while the WIRE_JSON specifies the
+# (possibly offline) signed version to be returned in /wire.
+# WIRE_JSON is optional, as not all accounts must be
+# advertised in /wire.
+[account-1]
+# What is the URL of our account?
+URL = "payto://sepa/CH9300762011623852957"
+# This is the response we give out for the /wire request. It provides
+# wallets with the bank information for transfers to the exchange.
+WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json
+# Which wire plugin should we used to access the account?
+PLUGIN = ebics
+
+# ENABLE_CREDIT = YES
+
+[account-2]
+# What is the bank account (with the "Taler Bank" demo system)?
+URL = "payto://x-taler-bank/localhost:8082/2"
+
+# This is the response we give out for the /wire request. It provides
+# wallets with the bank information for transfers to the exchange.
+WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json
+
+# Which wire plugin should we used to access the account?
+PLUGIN = taler_bank
+
+# Authentication information for basic authentication
+TALER_BANK_AUTH_METHOD = "basic"
+USERNAME = user
+PASSWORD = pass
+
+ENABLE_DEBIT = YES
+
+ENABLE_CREDIT = YES
+
+
+# Sections starting with "fee-" configure the wire fee for the
+# respective wire method.
+[fees-sepa]
+# Fees for the forseeable future...
+# If you see this after 2017, update to match the next 10 years...
+WIRE-FEE-2018 = EUR:0.01
+WIRE-FEE-2019 = EUR:0.01
+WIRE-FEE-2020 = EUR:0.01
+WIRE-FEE-2021 = EUR:0.01
+WIRE-FEE-2022 = EUR:0.01
+WIRE-FEE-2023 = EUR:0.01
+WIRE-FEE-2024 = EUR:0.01
+WIRE-FEE-2025 = EUR:0.01
+WIRE-FEE-2026 = EUR:0.01
+WIRE-FEE-2027 = EUR:0.01
+
+CLOSING-FEE-2018 = EUR:0.01
+CLOSING-FEE-2019 = EUR:0.01
+CLOSING-FEE-2020 = EUR:0.01
+CLOSING-FEE-2021 = EUR:0.01
+CLOSING-FEE-2022 = EUR:0.01
+CLOSING-FEE-2023 = EUR:0.01
+CLOSING-FEE-2024 = EUR:0.01
+CLOSING-FEE-2025 = EUR:0.01
+CLOSING-FEE-2026 = EUR:0.01
+CLOSING-FEE-2027 = EUR:0.01
+
+[fees-x-taler-bank]
+# Fees for the forseeable future...
+# If you see this after 2017, update to match the next 10 years...
+WIRE-FEE-2018 = EUR:0.01
+WIRE-FEE-2019 = EUR:0.01
+WIRE-FEE-2020 = EUR:0.01
+WIRE-FEE-2021 = EUR:0.01
+WIRE-FEE-2022 = EUR:0.01
+WIRE-FEE-2023 = EUR:0.01
+WIRE-FEE-2024 = EUR:0.01
+WIRE-FEE-2025 = EUR:0.01
+WIRE-FEE-2026 = EUR:0.01
+WIRE-FEE-2027 = EUR:0.01
+
+CLOSING-FEE-2018 = EUR:0.01
+CLOSING-FEE-2019 = EUR:0.01
+CLOSING-FEE-2020 = EUR:0.01
+CLOSING-FEE-2021 = EUR:0.01
+CLOSING-FEE-2022 = EUR:0.01
+CLOSING-FEE-2023 = EUR:0.01
+CLOSING-FEE-2024 = EUR:0.01
+CLOSING-FEE-2025 = EUR:0.01
+CLOSING-FEE-2026 = EUR:0.01
+CLOSING-FEE-2027 = EUR:0.01
+
+# Sections starting with "coin_" specify which denominations
+# the exchange should support (and their respective fee structure)
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_1]
+value = EUR:1
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_5]
+value = EUR:5
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_10]
+value = EUR:10
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
diff --git a/src/lib/test_auditor_api_expire_reserve_now.conf b/src/lib/test_auditor_api_expire_reserve_now.conf
new file mode 100644
index 00000000..05bca956
--- /dev/null
+++ b/src/lib/test_auditor_api_expire_reserve_now.conf
@@ -0,0 +1,4 @@
+@INLINE@ test_exchange_api.conf
+
+[exchangedb]
+IDLE_RESERVE_EXPIRATION_TIME = 0 s
diff --git a/src/lib/test_exchange_api.conf b/src/lib/test_exchange_api.conf
new file mode 100644
index 00000000..a44bab3a
--- /dev/null
+++ b/src/lib/test_exchange_api.conf
@@ -0,0 +1,203 @@
+
+# This file is in the public domain.
+#
+[PATHS]
+# Persistant data storage for the testcase
+TALER_TEST_HOME = test_exchange_api_home/
+
+[taler]
+# Currency supported by the exchange (can only be one)
+CURRENCY = EUR
+
+[auditor]
+BASE_URL = "http://localhost:8083/"
+
+# HTTP port the auditor listens to
+PORT = 8083
+
+
+[exchange]
+
+# how long is one signkey valid?
+signkey_duration = 4 weeks
+
+# how long are the signatures with the signkey valid?
+legal_duration = 2 years
+
+# how long do we provide to clients denomination and signing keys
+# ahead of time?
+lookahead_provide = 4 weeks 1 day
+
+# HTTP port the exchange listens to
+PORT = 8081
+
+# Master public key used to sign the exchange's various keys
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+
+# How to access our database
+DB = postgres
+
+# Base URL of the exchange. Must be set to a URL where the
+# exchange (or the twister) is actually listening.
+BASE_URL = "http://localhost:8081/"
+
+# Keep it short so the test runs fast.
+LOOKAHEAD_SIGN = 12 h
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+
+# Sections starting with "account-" configure the bank accounts
+# of the exchange. The "URL" specifies the account in
+# payto://-format, while the WIRE_JSON specifies the
+# (possibly offline) signed version to be returned in /wire.
+# WIRE_JSON is optional, as not all accounts must be
+# advertised in /wire.
+[account-1]
+# What is the URL of our account?
+URL = "payto://sepa/CH9300762011623852957"
+# This is the response we give out for the /wire request. It provides
+# wallets with the bank information for transfers to the exchange.
+WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json
+# Which wire plugin should we used to access the account?
+PLUGIN = ebics
+
+# ENABLE_CREDIT = YES
+
+[account-2]
+# What is the bank account (with the "Taler Bank" demo system)?
+URL = "payto://x-taler-bank/localhost:8082/2"
+
+# This is the response we give out for the /wire request. It provides
+# wallets with the bank information for transfers to the exchange.
+WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json
+
+# Which wire plugin should we used to access the account?
+PLUGIN = taler_bank
+
+# Authentication information for basic authentication
+TALER_BANK_AUTH_METHOD = "basic"
+USERNAME = user
+PASSWORD = pass
+
+ENABLE_DEBIT = YES
+
+ENABLE_CREDIT = YES
+
+
+# Sections starting with "fee-" configure the wire fee for the
+# respective wire method.
+[fees-sepa]
+# Fees for the forseeable future...
+# If you see this after 2017, update to match the next 10 years...
+WIRE-FEE-2018 = EUR:0.01
+WIRE-FEE-2019 = EUR:0.01
+WIRE-FEE-2020 = EUR:0.01
+WIRE-FEE-2021 = EUR:0.01
+WIRE-FEE-2022 = EUR:0.01
+WIRE-FEE-2023 = EUR:0.01
+WIRE-FEE-2024 = EUR:0.01
+WIRE-FEE-2025 = EUR:0.01
+WIRE-FEE-2026 = EUR:0.01
+WIRE-FEE-2027 = EUR:0.01
+
+CLOSING-FEE-2018 = EUR:0.01
+CLOSING-FEE-2019 = EUR:0.01
+CLOSING-FEE-2020 = EUR:0.01
+CLOSING-FEE-2021 = EUR:0.01
+CLOSING-FEE-2022 = EUR:0.01
+CLOSING-FEE-2023 = EUR:0.01
+CLOSING-FEE-2024 = EUR:0.01
+CLOSING-FEE-2025 = EUR:0.01
+CLOSING-FEE-2026 = EUR:0.01
+CLOSING-FEE-2027 = EUR:0.01
+
+[fees-x-taler-bank]
+# Fees for the forseeable future...
+# If you see this after 2017, update to match the next 10 years...
+WIRE-FEE-2018 = EUR:0.01
+WIRE-FEE-2019 = EUR:0.01
+WIRE-FEE-2020 = EUR:0.01
+WIRE-FEE-2021 = EUR:0.01
+WIRE-FEE-2022 = EUR:0.01
+WIRE-FEE-2023 = EUR:0.01
+WIRE-FEE-2024 = EUR:0.01
+WIRE-FEE-2025 = EUR:0.01
+WIRE-FEE-2026 = EUR:0.01
+WIRE-FEE-2027 = EUR:0.01
+
+CLOSING-FEE-2018 = EUR:0.01
+CLOSING-FEE-2019 = EUR:0.01
+CLOSING-FEE-2020 = EUR:0.01
+CLOSING-FEE-2021 = EUR:0.01
+CLOSING-FEE-2022 = EUR:0.01
+CLOSING-FEE-2023 = EUR:0.01
+CLOSING-FEE-2024 = EUR:0.01
+CLOSING-FEE-2025 = EUR:0.01
+CLOSING-FEE-2026 = EUR:0.01
+CLOSING-FEE-2027 = EUR:0.01
+
+# Sections starting with "coin_" specify which denominations
+# the exchange should support (and their respective fee structure)
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_1]
+value = EUR:1
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_5]
+value = EUR:5
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_10]
+value = EUR:10
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
diff --git a/src/lib/test_exchange_api_expire_reserve_now.conf b/src/lib/test_exchange_api_expire_reserve_now.conf
new file mode 100644
index 00000000..05bca956
--- /dev/null
+++ b/src/lib/test_exchange_api_expire_reserve_now.conf
@@ -0,0 +1,4 @@
+@INLINE@ test_exchange_api.conf
+
+[exchangedb]
+IDLE_RESERVE_EXPIRATION_TIME = 0 s
diff --git a/src/lib/test_exchange_api_home/.config/taler/account-1.json b/src/lib/test_exchange_api_home/.config/taler/account-1.json
new file mode 100644
index 00000000..48093f2a
--- /dev/null
+++ b/src/lib/test_exchange_api_home/.config/taler/account-1.json
@@ -0,0 +1,5 @@
+{
+ "url": "payto://sepa/CH9300762011623852957",
+ "salt": "N83T9J9202WCC8TQFDMJDWEGZNBEKA33C1ZM241VNYH88RZNTHPW509Y1M2YF7Y098R8VRESWQ05H03BK1SPAZCWE54KARDCKT5N8AG",
+ "master_sig": "D4V5GJ998YK7D6N0N56AD0J6MZNFEW6MRZT2CFPVQ5ME3NMQ59AA2007CXYESSFGRN70CNCFM06858QSSENCWTZM8VHEJ93YQ20ZJ1R"
+} \ No newline at end of file
diff --git a/src/lib/test_exchange_api_home/.config/taler/account-2.json b/src/lib/test_exchange_api_home/.config/taler/account-2.json
new file mode 100644
index 00000000..159e0317
--- /dev/null
+++ b/src/lib/test_exchange_api_home/.config/taler/account-2.json
@@ -0,0 +1,4 @@
+{
+ "url": "payto://x-taler-bank/localhost:8082/2",
+ "master_sig": "HC47BZN3C0KJ2VPMJ5EJWD2FXJ72AET0NWFE6JGSGK5CXS4GSKJJ6Z7BTS56JWM7B40SD61Z5GYYMRRE3X9JTJBVMWE0X7XHNXQ9P38"
+} \ No newline at end of file
diff --git a/src/lib/test_exchange_api_home/.config/taler/sepa.json b/src/lib/test_exchange_api_home/.config/taler/sepa.json
new file mode 100644
index 00000000..b435ce86
--- /dev/null
+++ b/src/lib/test_exchange_api_home/.config/taler/sepa.json
@@ -0,0 +1,9 @@
+{
+ "name": "Max Musterman",
+ "bic": "COBADEFF370",
+ "type": "sepa",
+ "sig": "4EVRC2MCJPXQC8MC00831DNWEXMZAP4JQDDE1A7R6KR3MANG24RC1VQ55AX5A2E35S58VW1VSTENFTPHG5MWG9BSN8B8WXSV21KKW20",
+ "address": "Musterstadt",
+ "salt": "3KTM1ZRMWGEQPQ254S4R5R4Q8XM0ZYWTCTE01TZ76MVBSQ6RX7A5DR08WXVH1DCHR1R7ACRB7X0EVC2XDW1CBZM9WFSD9TRMZ90BR98",
+ "iban": "DE89370400440532013000"
+} \ No newline at end of file
diff --git a/src/lib/test_exchange_api_home/.config/taler/test.json b/src/lib/test_exchange_api_home/.config/taler/test.json
new file mode 100644
index 00000000..eca39424
--- /dev/null
+++ b/src/lib/test_exchange_api_home/.config/taler/test.json
@@ -0,0 +1,8 @@
+{
+ "salt": "AZPRFVJ58NM6M7J5CZQPJAH3EW5DYM52AEZ9Y1C1ER3W94QV8D8TQKF6CK8MYQRA9QMSKDQTGZ306ZS9GQ0M6R01CJ20KPP49WFDZK8",
+ "name": "The exchange",
+ "account_number": 3,
+ "bank_url": "http://localhost:8082/",
+ "type": "test",
+ "sig": "RPQXP9S4P8PQP7HEZQNRSZCT0ATNEP8GW0P5TPM34V5RX86FCD670V44R9NETSYDDKB8SZV7TKY9PAJYTY51D3VDWY9XXQ5BPFRXR28"
+}
diff --git a/src/lib/test_exchange_api_home/.config/taler/x-taler-bank.json b/src/lib/test_exchange_api_home/.config/taler/x-taler-bank.json
new file mode 100644
index 00000000..a15df27c
--- /dev/null
+++ b/src/lib/test_exchange_api_home/.config/taler/x-taler-bank.json
@@ -0,0 +1,4 @@
+{
+ "url": "payto://x-taler-bank/http://localhost:8082/2",
+ "master_sig": "KQ0BWSCNVR7HGGSAMCYK8ZM30RBS1MHMXT3QBN01PZWC9TV72FEE5RJ7T84C8134EPV6WEBXXY2MTFNE8ZXST6JEJQKR8HX6FQPVY10"
+} \ No newline at end of file
diff --git a/src/lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv b/src/lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv
new file mode 100644
index 00000000..39492693
--- /dev/null
+++ b/src/lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv
@@ -0,0 +1 @@
+p^-33XX!\0qmU_ \ No newline at end of file
diff --git a/src/lib/test_exchange_api_keys_cherry_picking.conf b/src/lib/test_exchange_api_keys_cherry_picking.conf
new file mode 100644
index 00000000..8d5585e3
--- /dev/null
+++ b/src/lib/test_exchange_api_keys_cherry_picking.conf
@@ -0,0 +1,161 @@
+# This file is in the public domain.
+#
+[PATHS]
+# Persistent data storage for the testcase
+TALER_TEST_HOME = test_exchange_api_home/
+
+[taler]
+# Currency supported by the exchange (can only be one)
+CURRENCY = EUR
+
+[auditor]
+BASE_URL = "http://localhost:8083/"
+
+# HTTP port the auditor listens to
+PORT = 8083
+
+[exchange]
+
+# how long is one signkey valid?
+signkey_duration = 5 seconds
+
+# how long are the signatures with the signkey valid?
+legal_duration = 2 years
+
+# how long do we provide to clients denomination and signing keys
+# ahead of time?
+lookahead_provide = 30 seconds
+
+# Keep it short so we can prolong later!
+LOOKAHEAD_SIGN = 60 s
+
+
+# HTTP port the exchange listens to
+PORT = 8081
+
+# Master public key used to sign the exchange's various keys
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+
+# How to access our database
+DB = postgres
+
+# Base URL of the exchange. Must be set to a URL where the
+# exchange (or the twister) is actually listening.
+BASE_URL = "http://localhost:8081/"
+
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+
+
+[account-1]
+# This is the response we give out for the /wire request. It provides
+# wallets with the bank information for transfers to the exchange.
+WIRE_RESPONSE = ${TALER_CONFIG_HOME}/iban.json
+
+# What is the URL of our bank account? Must match WIRE_RESPONSE above!
+URL = payto://sepa/FIXME
+
+# Which plugin implements access for this account?
+PLUGIN = "ebics"
+
+
+[account-2]
+# This is the response we give out for the /wire request. It provides
+# wallets with the bank information for transfers to the exchange.
+WIRE_RESPONSE = ${TALER_CONFIG_HOME}/x-taler-bank.json
+
+# What is the URL of our bank account? Must match WIRE_RESPONSE above!
+URL = payto://x-taler-bank/http://localhost:8082/2
+
+# Which plugin implements access for this account?
+PLUGIN = "taler_bank"
+
+# Authentication information for basic authentication
+TALER_BANK_AUTH_METHOD = "basic"
+USERNAME = user
+PASSWORD = pass
+
+ENABLE_DEBIT = YES
+
+ENABLE_CREDIT = YES
+
+
+
+
+[fees-x-taler-bank]
+# Fees for the forseeable future...
+# If you see this after 2017, update to match the next 10 years...
+WIRE-FEE-2017 = EUR:0.01
+WIRE-FEE-2018 = EUR:0.01
+WIRE-FEE-2019 = EUR:0.01
+WIRE-FEE-2020 = EUR:0.01
+WIRE-FEE-2021 = EUR:0.01
+WIRE-FEE-2022 = EUR:0.01
+WIRE-FEE-2023 = EUR:0.01
+WIRE-FEE-2024 = EUR:0.01
+WIRE-FEE-2025 = EUR:0.01
+WIRE-FEE-2026 = EUR:0.01
+
+CLOSING-FEE-2017 = EUR:0.01
+CLOSING-FEE-2018 = EUR:0.01
+CLOSING-FEE-2019 = EUR:0.01
+CLOSING-FEE-2020 = EUR:0.01
+CLOSING-FEE-2021 = EUR:0.01
+CLOSING-FEE-2022 = EUR:0.01
+CLOSING-FEE-2023 = EUR:0.01
+CLOSING-FEE-2024 = EUR:0.01
+CLOSING-FEE-2025 = EUR:0.01
+CLOSING-FEE-2026 = EUR:0.01
+
+[fees-sepa]
+# Fees for the forseeable future...
+# If you see this after 2017, update to match the next 10 years...
+WIRE-FEE-2017 = EUR:0.01
+WIRE-FEE-2018 = EUR:0.01
+WIRE-FEE-2019 = EUR:0.01
+WIRE-FEE-2020 = EUR:0.01
+WIRE-FEE-2021 = EUR:0.01
+WIRE-FEE-2022 = EUR:0.01
+WIRE-FEE-2023 = EUR:0.01
+WIRE-FEE-2024 = EUR:0.01
+WIRE-FEE-2025 = EUR:0.01
+WIRE-FEE-2026 = EUR:0.01
+
+CLOSING-FEE-2017 = EUR:0.01
+CLOSING-FEE-2018 = EUR:0.01
+CLOSING-FEE-2019 = EUR:0.01
+CLOSING-FEE-2020 = EUR:0.01
+CLOSING-FEE-2021 = EUR:0.01
+CLOSING-FEE-2022 = EUR:0.01
+CLOSING-FEE-2023 = EUR:0.01
+CLOSING-FEE-2024 = EUR:0.01
+CLOSING-FEE-2025 = EUR:0.01
+CLOSING-FEE-2026 = EUR:0.01
+
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_overlap = 1 s
+duration_withdraw = 25 s
+duration_spend = 40 s
+duration_legal = 60 s
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_ct_2]
+value = EUR:0.02
+duration_overlap = 1 s
+duration_withdraw = 25 s
+duration_spend = 40 s
+duration_legal = 60 s
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
diff --git a/src/lib/test_exchange_api_keys_cherry_picking_extended.conf b/src/lib/test_exchange_api_keys_cherry_picking_extended.conf
new file mode 100644
index 00000000..29290c99
--- /dev/null
+++ b/src/lib/test_exchange_api_keys_cherry_picking_extended.conf
@@ -0,0 +1,5 @@
+@INLINE@ test_exchange_api_keys_cherry_picking.conf
+
+[exchange]
+# Lengthen over original value (60 s)
+LOOKAHEAD_SIGN = 100 s
diff --git a/src/lib/test_exchange_api_keys_cherry_picking_extended_2.conf b/src/lib/test_exchange_api_keys_cherry_picking_extended_2.conf
new file mode 100644
index 00000000..cfa8b134
--- /dev/null
+++ b/src/lib/test_exchange_api_keys_cherry_picking_extended_2.conf
@@ -0,0 +1,5 @@
+@INLINE@ test_exchange_api_keys_cherry_picking_extended.conf
+
+[exchange]
+# Lengthen over firstly extended value (100 s)
+LOOKAHEAD_SIGN = 1500 s
diff --git a/src/lib/test_exchange_api_keys_cherry_picking_new.c b/src/lib/test_exchange_api_keys_cherry_picking_new.c
new file mode 100644
index 00000000..45222a66
--- /dev/null
+++ b/src/lib/test_exchange_api_keys_cherry_picking_new.c
@@ -0,0 +1,233 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/test_exchange_api_keys_cherry_picking_new.c
+ * @brief testcase to test exchange's /keys cherry picking ability
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler_bank_service.h"
+#include "taler_fakebank_lib.h"
+#include "taler_testing_lib.h"
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_exchange_api_keys_cherry_picking.conf"
+
+/**
+ * Used to increase the number of denomination keys.
+ */
+#define CONFIG_FILE_EXTENDED \
+ "test_exchange_api_keys_cherry_picking_extended.conf"
+
+/**
+ * Used to increase the number of denomination keys.
+ */
+#define CONFIG_FILE_EXTENDED_2 \
+ "test_exchange_api_keys_cherry_picking_extended_2.conf"
+
+/**
+ * Exchange base URL; mainly purpose is to make the compiler happy.
+ */
+static char *exchange_url;
+
+/**
+ * Auditor base URL; mainly purpose is to make the compiler happy.
+ */
+static char *auditor_url;
+
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+
+ struct TALER_TESTING_Command keys_serialization[] = {
+
+ /**
+ * Serialize keys, and disconnect from the exchange.
+ */
+ TALER_TESTING_cmd_serialize_keys ("serialize-keys"),
+
+ /**
+ * Reconnect to the exchange using the serialized keys.
+ */
+ TALER_TESTING_cmd_connect_with_state ("reconnect-with-state",
+ "serialize-keys"),
+
+ TALER_TESTING_cmd_wire ("verify-/wire-with-serialized-keys",
+ "x-taler-bank",
+ NULL,
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_exec_keyup ("keyup-serialization",
+ CONFIG_FILE_EXTENDED_2),
+
+ TALER_TESTING_cmd_exec_auditor_sign
+ ("auditor-sign-serialization",
+ CONFIG_FILE_EXTENDED_2),
+
+ TALER_TESTING_cmd_sleep ("sleep-serialization",
+ 3),
+
+ TALER_TESTING_cmd_signal ("reload-keys-serialization",
+ is->exchanged,
+ SIGUSR1),
+
+ TALER_TESTING_cmd_sleep ("sleep-serialization",
+ 3),
+
+ /**
+ * XXX.
+ *
+ * Current bug: this CMD here uses the "reconnect cert_cb",
+ * that has its 'consumed' field already set to GNUNET_YES.
+ * This way, there is no way to pass control to the next
+ * CMD making therefore the interpreter stuck.
+ *
+ * Doable solution: adapt the global "cert_cb" to handle
+ * "reconnect situations", or even provide some method to
+ * switch the 'consumed' field back to GNUNET_NO.
+ */
+ TALER_TESTING_cmd_check_keys ("check-freshest-keys",
+ 4,
+ 10),
+
+ TALER_TESTING_cmd_wire ("verify-/wire-with-fresh-keys",
+ "x-taler-bank",
+ NULL,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command commands[] = {
+ /* Trigger keys reloading from disk. */
+ TALER_TESTING_cmd_signal ("signal-reaction-1",
+ is->exchanged,
+ SIGUSR1),
+
+ TALER_TESTING_cmd_check_keys ("check-keys-1",
+ 1,
+ 4),
+ /* sleep a bit */
+ TALER_TESTING_cmd_sleep ("sleep",
+ 10),
+
+ /* 1st keyup happens at start-up */
+ TALER_TESTING_cmd_exec_keyup ("keyup-2",
+ CONFIG_FILE_EXTENDED),
+
+ TALER_TESTING_cmd_exec_auditor_sign ("sign-keys-1",
+ CONFIG_FILE_EXTENDED),
+
+ /* Cause exchange to reload (new) keys */
+ TALER_TESTING_cmd_signal ("trigger-keys-reload-1",
+ is->exchanged,
+ SIGUSR1),
+
+ TALER_TESTING_cmd_check_keys ("check-keys-2",
+ 2,
+ 6),
+ /* sleep a bit */
+ TALER_TESTING_cmd_sleep ("sleep",
+ 20),
+
+ /* Do 2nd keyup */
+ TALER_TESTING_cmd_exec_keyup ("keyup-3",
+ CONFIG_FILE_EXTENDED),
+
+ TALER_TESTING_cmd_exec_auditor_sign ("sign-keys-2",
+ CONFIG_FILE),
+
+ TALER_TESTING_cmd_signal ("trigger-keys-reload-2",
+ is->exchanged,
+ SIGUSR1),
+
+ TALER_TESTING_cmd_check_keys ("check-keys-3",
+ 3,
+ 8),
+
+ TALER_TESTING_cmd_batch ("keys-serialization",
+ keys_serialization),
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run (is,
+ commands);
+}
+
+
+int
+main (int argc,
+ char * const *argv)
+{
+ /* These environment variables get in the way... */
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup ("test-exchange-api-cherry-picking-new",
+ "DEBUG", NULL);
+ TALER_TESTING_cleanup_files (CONFIG_FILE);
+ /* @helpers. Run keyup, create tables, ... Note: it
+ * fetches the port number from config in order to see
+ * if it's available. */
+ switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
+ &auditor_url,
+ &exchange_url))
+ {
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ return 1;
+ case GNUNET_NO:
+ return 77;
+ case GNUNET_OK:
+ if (GNUNET_OK !=
+ /* Set up event loop and reschedule context, plus
+ * start/stop the exchange. It calls TALER_TESTING_setup
+ * which creates the 'is' object.
+ */
+ TALER_TESTING_setup_with_exchange (&run,
+ NULL,
+ CONFIG_FILE))
+ return 1;
+ break;
+ default:
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+/* end of test_exchange_api_keys_cherry_picking_new.c */
diff --git a/src/lib/test_exchange_api_new.c b/src/lib/test_exchange_api_new.c
new file mode 100644
index 00000000..bcbdb5ea
--- /dev/null
+++ b/src/lib/test_exchange_api_new.c
@@ -0,0 +1,1001 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange/test_exchange_api_new.c
+ * @brief testcase to test exchange's HTTP API interface
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler_bank_service.h"
+#include "taler_fakebank_lib.h"
+#include "taler_testing_lib.h"
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_exchange_api.conf"
+
+#define CONFIG_FILE_EXPIRE_RESERVE_NOW "test_exchange_api_expire_reserve_now.conf"
+
+/**
+ * Is the configuration file is set to include wire format 'ebics'?
+ * Requires that EBICS /history function is implemented, which it
+ * is currently not. Once it is, set ENABLE_CREDIT to YES in the
+ * configuration and then set this option to 1.
+ */
+#define WIRE_EBICS 0
+
+/**
+ * URL of the fakebank. Obtained from CONFIG_FILE's
+ * "exchange-wire-test:BANK_URI" option.
+ */
+static char *fakebank_url;
+
+/**
+ * Exchange base URL as it appears in the configuration. Note
+ * that it might differ from the one where the exchange actually
+ * listens from.
+ */
+static char *exchange_url;
+
+/**
+ * Auditor base URL as it appears in the configuration. Note
+ * that it might differ from the one where the auditor actually
+ * listens from.
+ */
+static char *auditor_url;
+
+/**
+ * Account number of the exchange at the bank.
+ */
+#define EXCHANGE_ACCOUNT_NO 2
+
+/**
+ * Account number of some user.
+ */
+#define USER_ACCOUNT_NO 42
+
+/**
+ * User name. Never checked by fakebank.
+ */
+#define USER_LOGIN_NAME "user42"
+
+/**
+ * User password. Never checked by fakebank.
+ */
+#define USER_LOGIN_PASS "pass42"
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_WIREWATCH(label) \
+ TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE)
+
+/**
+ * Execute the taler-exchange-aggregator command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+ TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE)
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
+ TALER_TESTING_cmd_fakebank_transfer (label, amount, \
+ fakebank_url, USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO, \
+ USER_LOGIN_NAME, USER_LOGIN_PASS, exchange_url)
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE_SUBJECT(label,amount,subject) \
+ TALER_TESTING_cmd_fakebank_transfer_with_subject \
+ (label, amount, fakebank_url, USER_ACCOUNT_NO, \
+ EXCHANGE_ACCOUNT_NO, USER_LOGIN_NAME, USER_LOGIN_PASS, \
+ subject, exchange_url)
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+
+ /**
+ * Checks made against /wire response.
+ */
+ struct TALER_TESTING_Command wire[] = {
+ /**
+ * Check if 'x-taler-bank' wire method is offered
+ * by the exchange.
+ */
+ TALER_TESTING_cmd_wire ("wire-taler-bank-1",
+ "x-taler-bank",
+ NULL,
+ MHD_HTTP_OK),
+ #if WIRE_EBICS
+ /**
+ * Check if 'ebics' wire method is offered by the exchange.
+ */
+ TALER_TESTING_cmd_wire ("wire-sepa-1",
+ "ebics",
+ NULL,
+ MHD_HTTP_OK),
+ #endif
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ /**
+ * Test withdrawal plus spending.
+ */
+ struct TALER_TESTING_Command withdraw[] = {
+
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1",
+ "EUR:5.01"),
+
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-1"),
+
+
+ /**
+ * Withdraw EUR:5.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+
+ /**
+ * Check the reserve is depleted.
+ */
+ TALER_TESTING_cmd_status ("status-1",
+ "create-reserve-1",
+ "EUR:0",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command spend[] = {
+ /**
+ * Spend the coin.
+ */
+ TALER_TESTING_cmd_deposit
+ ("deposit-simple", "withdraw-coin-1", 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_OK),
+
+ /**
+ * Try to overdraw.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_FORBIDDEN),
+
+ /**
+ * Try to double spend using different wire details.
+ */
+ TALER_TESTING_cmd_deposit
+ ("deposit-double-1", "withdraw-coin-1", 0,
+ TALER_TESTING_make_wire_details (43,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_FORBIDDEN),
+
+ /**
+ * Try to double spend using a different transaction id.
+ * (copied verbatim from old exchange-lib tests.)
+ * FIXME: how can it get a different transaction id? There
+ * isn't such a thing actually, the exchange only knows about
+ * contract terms' hashes. So since the contract terms are
+ * exactly the same as the previous command,
+ * how can a different id be generated?
+ */
+ TALER_TESTING_cmd_deposit
+ ("deposit-double-1", "withdraw-coin-1", 0,
+ TALER_TESTING_make_wire_details (43,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_FORBIDDEN),
+
+ /**
+ * Try to double spend with different proposal.
+ */
+ TALER_TESTING_cmd_deposit
+ ("deposit-double-2", "withdraw-coin-1", 0,
+ TALER_TESTING_make_wire_details (43,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":2}]}",
+ GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_FORBIDDEN),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+
+ struct TALER_TESTING_Command refresh[] = {
+
+ /**
+ * Fill reserve with EUR:5, 1ct is for fees. NOTE: the old
+ * test-suite gave a account number of _424_ to the user at
+ * this step; to type less, here the _42_ number is reused.
+ * Does this change the tests semantics?
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-1",
+ "EUR:5.01"),
+
+ /**
+ * Make previous command effective.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-2"),
+
+ /**
+ * Withdraw EUR:5.
+ */
+ TALER_TESTING_cmd_withdraw_amount
+ ("refresh-withdraw-coin-1",
+ "refresh-create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ /**
+ * Try to partially spend (deposit) 1 EUR of the 5 EUR coin
+ * (in full) (merchant would receive EUR:0.99 due to 1 ct
+ * deposit fee)
+ */
+ TALER_TESTING_cmd_deposit
+ ("refresh-deposit-partial",
+ "refresh-withdraw-coin-1", 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\
+ \"value\":\"EUR:1\"}]}",
+ GNUNET_TIME_UNIT_ZERO, "EUR:1", MHD_HTTP_OK),
+
+ /**
+ * Melt the rest of the coin's value
+ * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */
+ TALER_TESTING_cmd_refresh_melt_double
+ ("refresh-melt-1", "EUR:4",
+ "refresh-withdraw-coin-1", MHD_HTTP_OK),
+ /**
+ * Complete (successful) melt operation, and
+ * withdraw the coins
+ */
+ TALER_TESTING_cmd_refresh_reveal
+ ("refresh-reveal-1",
+ "refresh-melt-1", MHD_HTTP_OK),
+
+ /**
+ * Do it again to check idempotency
+ */
+ TALER_TESTING_cmd_refresh_reveal
+ ("refresh-reveal-1-idempotency",
+ "refresh-melt-1", MHD_HTTP_OK),
+
+ /**
+ * Test that /refresh/link works
+ */
+ TALER_TESTING_cmd_refresh_link
+ ("refresh-link-1",
+ "refresh-reveal-1", MHD_HTTP_OK),
+
+ /**
+ * Try to spend a refreshed EUR:1 coin
+ */
+ TALER_TESTING_cmd_deposit
+ ("refresh-deposit-refreshed-1a",
+ "refresh-reveal-1-idempotency", 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\
+ \"value\":3}]}",
+ GNUNET_TIME_UNIT_ZERO, "EUR:1", MHD_HTTP_OK),
+
+ /**
+ * Try to spend a refreshed EUR:0.1 coin
+ */
+ TALER_TESTING_cmd_deposit
+ ("refresh-deposit-refreshed-1b",
+ "refresh-reveal-1", 3,
+ TALER_TESTING_make_wire_details (43,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\
+ \"value\":3}]}",
+ GNUNET_TIME_UNIT_ZERO, "EUR:0.1", MHD_HTTP_OK),
+
+ /* Test running a failing melt operation (same operation
+ * again must fail) */
+ TALER_TESTING_cmd_refresh_melt
+ ("refresh-melt-failing", "EUR:4",
+ "refresh-withdraw-coin-1", MHD_HTTP_FORBIDDEN),
+
+ /* FIXME: also test with coin that was already melted
+ * (signature differs from coin that was deposited...) */
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command track[] = {
+ /**
+ * Try resolving a deposit's WTID, as we never triggered
+ * execution of transactions, the answer should be that
+ * the exchange knows about the deposit, but has no WTID yet.
+ */
+ TALER_TESTING_cmd_track_transaction
+ ("deposit-wtid-found",
+ "deposit-simple", 0, MHD_HTTP_ACCEPTED, NULL),
+
+ /**
+ * Try resolving a deposit's WTID for a failed deposit.
+ * As the deposit failed, the answer should be that the
+ * exchange does NOT know about the deposit.
+ */
+ TALER_TESTING_cmd_track_transaction
+ ("deposit-wtid-failing",
+ "deposit-double-2", 0, MHD_HTTP_NOT_FOUND, NULL),
+
+ /**
+ * Try resolving an undefined (all zeros) WTID; this
+ * should fail as obviously the exchange didn't use that
+ * WTID value for any transaction.
+ */
+ TALER_TESTING_cmd_track_transfer_empty
+ ("wire-deposit-failing",
+ NULL, 0, MHD_HTTP_NOT_FOUND),
+
+ /**
+ * Run transfers. Note that _actual_ aggregation will NOT
+ * happen here, as each deposit operation is run with a
+ * fresh merchant public key! NOTE: this comment comes
+ * "verbatim" from the old test-suite, and IMO does not explain
+ * a lot!
+ */
+ CMD_EXEC_AGGREGATOR ("run-aggregator"),
+
+ /**
+ * Check all the transfers took place.
+ */
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-499c", exchange_url,
+ "EUR:4.98", 2, 42),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-99c1", exchange_url,
+ "EUR:0.98", 2, 42),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-99c2", exchange_url,
+ "EUR:0.98", 2, 42),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-99c", exchange_url,
+ "EUR:0.08", 2, 43),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-aai-1", exchange_url,
+ "EUR:5.01", 42, 2),
+
+ /**
+ * NOTE: the old test-suite had this "check bank transfer"
+ * command with debit account == 424.
+ */
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-aai-2", exchange_url,
+ "EUR:5.01", 42, 2),
+
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
+
+ TALER_TESTING_cmd_track_transaction
+ ("deposit-wtid-ok",
+ "deposit-simple", 0, MHD_HTTP_OK, "check_bank_transfer-499c"),
+
+ TALER_TESTING_cmd_track_transfer
+ ("wire-deposit-success-bank",
+ "check_bank_transfer-99c1", 0, MHD_HTTP_OK, "EUR:0.98",
+ "EUR:0.01"),
+
+ TALER_TESTING_cmd_track_transfer
+ ("wire-deposits-success-wtid",
+ "deposit-wtid-ok", 0, MHD_HTTP_OK, "EUR:4.98",
+ "EUR:0.01"),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+
+ /**
+ * This block checks whether a wire deadline
+ * very far in the future does NOT get aggregated now.
+ */
+ struct TALER_TESTING_Command unaggregation[] = {
+
+ TALER_TESTING_cmd_check_bank_empty
+ ("far-future-aggregation-a"),
+
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-unaggregated",
+ "EUR:5.01"),
+
+ CMD_EXEC_WIREWATCH ("wirewatch-unaggregated"),
+
+ /* "consume" reserve creation transfer. */
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-unaggregated",
+ exchange_url,
+ "EUR:5.01",
+ 42,
+ 2),
+
+ TALER_TESTING_cmd_withdraw_amount
+ ("withdraw-coin-unaggregated",
+ "create-reserve-unaggregated",
+ "EUR:5",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_deposit
+ ("deposit-unaggregated",
+ "withdraw-coin-unaggregated",
+ 0,
+ TALER_TESTING_make_wire_details
+ (43,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_relative_multiply
+ (GNUNET_TIME_UNIT_YEARS,
+ 3000),
+ "EUR:5",
+ MHD_HTTP_OK),
+
+ CMD_EXEC_AGGREGATOR ("aggregation-attempt"),
+
+ TALER_TESTING_cmd_check_bank_empty
+ ("far-future-aggregation-b"),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+
+ struct TALER_TESTING_Command refund[] = {
+
+ /**
+ * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per
+ * config.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-r1",
+ "EUR:5.01"),
+
+
+ /**
+ * Run wire-watch to trigger the reserve creation.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-3"),
+
+ /* Withdraw a 5 EUR coin, at fee of 1 ct */
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",
+ "create-reserve-r1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ /**
+ * Spend 5 EUR of the 5 EUR coin (in full) (merchant would
+ * receive EUR:4.99 due to 1 ct deposit fee)
+ */
+ TALER_TESTING_cmd_deposit
+ ("deposit-refund-1", "withdraw-coin-r1", 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\","
+ "\"value\":\"EUR:5\"}]}",
+ GNUNET_TIME_UNIT_MINUTES, "EUR:5", MHD_HTTP_OK),
+
+
+ /**
+ * Run transfers. Should do nothing as refund deadline blocks
+ * it
+ */
+ CMD_EXEC_AGGREGATOR ("run-aggregator-refund"),
+
+ /**
+ * Check that aggregator didn't do anything, as expected.
+ * Note, this operation takes two commands: one to "flush"
+ * the preliminary transfer (used to withdraw) from the
+ * fakebank and the second to actually check there are not
+ * other transfers around.
+ */
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-pre-refund", exchange_url,
+ "EUR:5.01", 42, 2),
+
+ TALER_TESTING_cmd_check_bank_empty
+ ("check_bank_transfer-pre-refund"),
+
+ TALER_TESTING_cmd_refund
+ ("refund-ok", MHD_HTTP_OK,
+ "EUR:5", "EUR:0.01", "deposit-refund-1"),
+
+ TALER_TESTING_cmd_refund
+ ("refund-ok-double", MHD_HTTP_OK,
+ "EUR:5", "EUR:0.01", "deposit-refund-1"),
+
+ /* Previous /refund(s) had id == 0. */
+ TALER_TESTING_cmd_refund_with_id
+ ("refund-conflicting", MHD_HTTP_CONFLICT,
+ "EUR:5", "EUR:0.01", "deposit-refund-1", 1),
+
+ /**
+ * Spend 4.99 EUR of the refunded 4.99 EUR coin (1ct gone
+ * due to refund) (merchant would receive EUR:4.98 due to
+ * 1 ct deposit fee) */
+ TALER_TESTING_cmd_deposit
+ ("deposit-refund-2", "withdraw-coin-r1", 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"more ice cream\","
+ "\"value\":\"EUR:5\"}]}",
+ GNUNET_TIME_UNIT_ZERO, "EUR:4.99", MHD_HTTP_OK),
+
+
+ /**
+ * Run transfers. This will do the transfer as refund deadline
+ * was 0
+ */
+ CMD_EXEC_AGGREGATOR ("run-aggregator-3"),
+
+ /**
+ * Check that deposit did run.
+ */
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-pre-refund", exchange_url,
+ "EUR:4.97", 2, 42),
+
+ /**
+ * Run failing refund, as past deadline & aggregation.
+ */
+ TALER_TESTING_cmd_refund
+ ("refund-fail", MHD_HTTP_GONE,
+ "EUR:4.99", "EUR:0.01", "deposit-refund-2"),
+
+ TALER_TESTING_cmd_check_bank_empty
+ ("check-empty-after-refund"),
+
+ /**
+ * Test refunded coins are never executed, even past
+ * refund deadline
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-rb",
+ "EUR:5.01"),
+
+ CMD_EXEC_WIREWATCH ("wirewatch-rb"),
+
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-rb",
+ "create-reserve-rb",
+ "EUR:5",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-aai-3b", exchange_url,
+ "EUR:5.01", 42, 2),
+
+
+ TALER_TESTING_cmd_deposit
+ ("deposit-refund-1b", "withdraw-coin-rb", 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\","
+ "\"value\":\"EUR:5\"}]}",
+ GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_OK),
+
+ /**
+ * Trigger refund (before aggregator had a chance to execute
+ * deposit, even though refund deadline was zero).
+ */
+ TALER_TESTING_cmd_refund
+ ("refund-ok-fast", MHD_HTTP_OK,
+ "EUR:5", "EUR:0.01", "deposit-refund-1b"),
+
+ /**
+ * Run transfers. This will do the transfer as refund deadline
+ * was 0, except of course because the refund succeeded, the
+ * transfer should no longer be done.
+ */
+ CMD_EXEC_AGGREGATOR ("run-aggregator-3b"),
+
+ /* check that aggregator didn't do anything, as expected */
+ TALER_TESTING_cmd_check_bank_empty
+ ("check-refund-fast-not-run"),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command payback[] = {
+ /**
+ * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per
+ * config.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("payback-create-reserve-1",
+ "EUR:5.01"),
+
+ /**
+ * Run wire-watch to trigger the reserve creation.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-4"),
+
+ /* Withdraw a 5 EUR coin, at fee of 1 ct */
+ TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-1",
+ "payback-create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ /* Make coin invalid */
+ TALER_TESTING_cmd_revoke ("revoke-1",
+ MHD_HTTP_OK,
+ "payback-withdraw-coin-1",
+ CONFIG_FILE),
+
+ /* Refund coin to bank account */
+ TALER_TESTING_cmd_payback ("payback-1",
+ MHD_HTTP_OK,
+ "payback-withdraw-coin-1",
+ "EUR:5"),
+
+ /* Check the money is back with the reserve */
+ TALER_TESTING_cmd_status ("payback-reserve-status-1",
+ "payback-create-reserve-1",
+ "EUR:5.0",
+ MHD_HTTP_OK),
+
+ /* Re-withdraw from this reserve */
+ TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2",
+ "payback-create-reserve-1",
+ "EUR:1",
+ MHD_HTTP_OK),
+
+ /**
+ * This withdrawal will test the logic to create a "payback"
+ * element to insert into the reserve's history.
+ */
+ TALER_TESTING_cmd_withdraw_amount
+ ("payback-withdraw-coin-2-over",
+ "payback-create-reserve-1",
+ "EUR:10",
+ MHD_HTTP_FORBIDDEN),
+
+ TALER_TESTING_cmd_status ("payback-reserve-status-2",
+ "payback-create-reserve-1",
+ "EUR:3.99",
+ MHD_HTTP_OK),
+
+ /**
+ * These commands should close the reserve because
+ * the aggregator is given a config file that ovverrides
+ * the reserve expiration time (making it now-ish)
+ */
+ CMD_TRANSFER_TO_EXCHANGE
+ ("short-lived-reserve",
+ "EUR:5.01"),
+
+ TALER_TESTING_cmd_exec_wirewatch
+ ("short-lived-aggregation",
+ CONFIG_FILE_EXPIRE_RESERVE_NOW),
+
+ TALER_TESTING_cmd_exec_aggregator
+ ("close-reserves",
+ CONFIG_FILE_EXPIRE_RESERVE_NOW),
+
+ TALER_TESTING_cmd_status ("short-lived-status",
+ "short-lived-reserve",
+ "EUR:0",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_withdraw_amount
+ ("expired-withdraw",
+ "short-lived-reserve",
+ "EUR:1",
+ MHD_HTTP_FORBIDDEN),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_short-lived_transfer",
+ exchange_url,
+ "EUR:5.01",
+ 42,
+ 2),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_short-lived_reimburse",
+ exchange_url,
+ "EUR:5",
+ 2,
+ 42),
+
+ /**
+ * Fill reserve with EUR:2.02, as withdraw fee is 1 ct per
+ * config, then withdraw two coin, partially spend one, and
+ * then have the rest paid back. Check deposit of other coin
+ * fails. (Do not use EUR:5 here as the EUR:5 coin was
+ * revoked and we did not bother to create a new one...)
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("payback-create-reserve-2",
+ "EUR:2.02"),
+
+ /* Make previous command effective. */
+ CMD_EXEC_WIREWATCH ("wirewatch-5"),
+
+ /* Withdraw a 1 EUR coin, at fee of 1 ct */
+ TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2a",
+ "payback-create-reserve-2",
+ "EUR:1",
+ MHD_HTTP_OK),
+
+ /* Withdraw a 1 EUR coin, at fee of 1 ct */
+ TALER_TESTING_cmd_withdraw_amount ("payback-withdraw-coin-2b",
+ "payback-create-reserve-2",
+ "EUR:1",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_deposit
+ ("payback-deposit-partial",
+ "payback-withdraw-coin-2a", 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO, "EUR:0.5", MHD_HTTP_OK),
+
+
+ TALER_TESTING_cmd_revoke ("revoke-2", MHD_HTTP_OK,
+ "payback-withdraw-coin-2a",
+ CONFIG_FILE),
+
+ TALER_TESTING_cmd_payback ("payback-2", MHD_HTTP_OK,
+ "payback-withdraw-coin-2a",
+ "EUR:0.5"),
+
+ TALER_TESTING_cmd_payback ("payback-2b", MHD_HTTP_FORBIDDEN,
+ "payback-withdraw-coin-2a",
+ "EUR:0.5"),
+
+ TALER_TESTING_cmd_deposit
+ ("payback-deposit-revoked",
+ "payback-withdraw-coin-2b", 0,
+ TALER_TESTING_make_wire_details (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO, "EUR:1", MHD_HTTP_NOT_FOUND),
+
+
+ /* Test deposit fails after payback, with proof in payback */
+
+ /* FIXME: #3887: right now, the exchange will never return the
+ * coin's transaction history with payback data, as we get a
+ * 404 on the DK! */
+ TALER_TESTING_cmd_deposit
+ ("payback-deposit-partial-after-payback",
+ "payback-withdraw-coin-2a",
+ 0,
+ TALER_TESTING_make_wire_details
+ (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"extra ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:0.5",
+ MHD_HTTP_NOT_FOUND),
+
+ /* Test that revoked coins cannot be withdrawn */
+ CMD_TRANSFER_TO_EXCHANGE ("payback-create-reserve-3",
+ "EUR:1.01"),
+
+ CMD_EXEC_WIREWATCH ("wirewatch-6"),
+
+ TALER_TESTING_cmd_withdraw_amount
+ ("payback-withdraw-coin-3-revoked",
+ "payback-create-reserve-3",
+ "EUR:1",
+ MHD_HTTP_NOT_FOUND),
+
+ /* check that we are empty before the rejection test */
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-pr1", exchange_url,
+ "EUR:5.01", 42, 2),
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-pr2", exchange_url,
+ "EUR:2.02", 42, 2),
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-pr3", exchange_url,
+ "EUR:1.01", 42, 2),
+
+ TALER_TESTING_cmd_check_bank_empty
+ ("check-empty-again"),
+
+ /* Test rejection of bogus wire transfers */
+ CMD_TRANSFER_TO_EXCHANGE_SUBJECT
+ ("bogus-subject",
+ "EUR:1.01",
+ "not a reserve public key"),
+
+ CMD_EXEC_WIREWATCH ("wirewatch-7"),
+
+ TALER_TESTING_cmd_check_bank_empty
+ ("check-empty-from-reject"),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ #define RESERVE_OPEN_CLOSE_CHUNK 4
+ #define RESERVE_OPEN_CLOSE_ITERATIONS 3
+ #define CONSTANT_KEY \
+ "09QGYPEKNHBACK135BNXZFHA0YTQXT1KJDRVXF4J822G99AYNQ8G"
+
+ struct TALER_TESTING_Command reserve_open_close
+ [(RESERVE_OPEN_CLOSE_ITERATIONS
+ * RESERVE_OPEN_CLOSE_CHUNK) + 1];
+
+ for (unsigned int i = 0;
+ i < RESERVE_OPEN_CLOSE_ITERATIONS;
+ i++)
+ {
+ reserve_open_close[i * RESERVE_OPEN_CLOSE_CHUNK]
+ = CMD_TRANSFER_TO_EXCHANGE_SUBJECT
+ ("reserve-open-close-key",
+ "EUR:20",
+ CONSTANT_KEY);
+
+ reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 1]
+ = TALER_TESTING_cmd_exec_wirewatch
+ ("reserve-open-close-wirewatch",
+ CONFIG_FILE_EXPIRE_RESERVE_NOW);
+
+ reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 2]
+ = TALER_TESTING_cmd_exec_aggregator
+ ("reserve-open-close-aggregation",
+ CONFIG_FILE_EXPIRE_RESERVE_NOW);
+
+ reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 3]
+ = TALER_TESTING_cmd_status ("reserve-open-close-status",
+ "reserve-open-close-key",
+ "EUR:0",
+ MHD_HTTP_OK);
+ }
+ reserve_open_close
+ [RESERVE_OPEN_CLOSE_ITERATIONS * RESERVE_OPEN_CLOSE_CHUNK]
+ = TALER_TESTING_cmd_end ();
+
+ struct TALER_TESTING_Command commands[] = {
+
+ TALER_TESTING_cmd_batch ("wire",
+ wire),
+
+ TALER_TESTING_cmd_batch ("withdraw",
+ withdraw),
+
+ TALER_TESTING_cmd_batch ("spend",
+ spend),
+
+ TALER_TESTING_cmd_batch ("refresh",
+ refresh),
+
+ TALER_TESTING_cmd_batch ("track",
+ track),
+
+ TALER_TESTING_cmd_batch ("unaggregation",
+ unaggregation),
+
+ TALER_TESTING_cmd_batch ("refund",
+ refund),
+
+ TALER_TESTING_cmd_batch ("payback",
+ payback),
+ /* Fix #5462. */
+ TALER_TESTING_cmd_batch ("reserve-open-close",
+ reserve_open_close),
+ /**
+ * End the suite. Fixme: better to have a label for this
+ * too, as it shows as "(null)" on logs.
+ */
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run_with_fakebank (is,
+ commands,
+ fakebank_url);
+}
+
+
+int
+main (int argc,
+ char * const *argv)
+{
+ /* These environment variables get in the way... */
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup ("test-exchange-api-new",
+ "INFO",
+ NULL);
+ if (NULL == (fakebank_url
+ /* Check fakebank port is available and config cares
+ * about bank url. */
+ = TALER_TESTING_prepare_fakebank (CONFIG_FILE,
+ "account-2")))
+ return 77;
+ TALER_TESTING_cleanup_files (CONFIG_FILE);
+ /* @helpers. Run keyup, create tables, ... Note: it
+ * fetches the port number from config in order to see
+ * if it's available. */
+ switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
+ &auditor_url,
+ &exchange_url))
+ {
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ return 1;
+ case GNUNET_NO:
+ return 77;
+ case GNUNET_OK:
+ if (GNUNET_OK !=
+ /* Set up event loop and reschedule context, plus
+ * start/stop the exchange. It calls TALER_TESTING_setup
+ * which creates the 'is' object.
+ */
+ TALER_TESTING_setup_with_exchange (&run,
+ NULL,
+ CONFIG_FILE))
+ return 1;
+ break;
+ default:
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+/* end of test_exchange_api_new.c */
diff --git a/src/lib/test_exchange_api_overlapping_keys_bug.c b/src/lib/test_exchange_api_overlapping_keys_bug.c
new file mode 100755
index 00000000..d1fd7123
--- /dev/null
+++ b/src/lib/test_exchange_api_overlapping_keys_bug.c
@@ -0,0 +1,135 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/test_exchange_api_keys_cherry_picking_new.c
+ * @brief testcase to test exchange's /keys cherry picking ability
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler_bank_service.h"
+#include "taler_fakebank_lib.h"
+#include "taler_testing_lib.h"
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_exchange_api_keys_cherry_picking.conf"
+
+/**
+ * Used to increase the number of denomination keys.
+ */
+#define CONFIG_FILE_EXTENDED \
+ "test_exchange_api_keys_cherry_picking_extended.conf"
+
+/**
+ * Used to increase the number of denomination keys.
+ */
+#define CONFIG_FILE_EXTENDED_2 \
+ "test_exchange_api_keys_cherry_picking_extended_2.conf"
+
+/**
+ * Exchange base URL; mainly purpose is to make the compiler happy.
+ */
+static char *exchange_url;
+
+/**
+ * Auditor base URL; mainly purpose is to make the compiler happy.
+ */
+static char *auditor_url;
+
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+
+ struct TALER_TESTING_Command commands[] = {
+
+ TALER_TESTING_cmd_check_keys ("first-download",
+ 1,
+ 4),
+
+ TALER_TESTING_cmd_check_keys ("second-download",
+ 2,
+ 6),
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run (is,
+ commands);
+}
+
+
+int
+main (int argc,
+ char * const *argv)
+{
+ /* These environment variables get in the way... */
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup ("test-exchange-api-cherry-picking-new",
+ "DEBUG", NULL);
+ TALER_TESTING_cleanup_files (CONFIG_FILE);
+ /* @helpers. Run keyup, create tables, ... Note: it
+ * fetches the port number from config in order to see
+ * if it's available. */
+ switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
+ &auditor_url,
+ &exchange_url))
+ {
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ return 1;
+ case GNUNET_NO:
+ return 77;
+ case GNUNET_OK:
+ if (GNUNET_OK !=
+ /* Set up event loop and reschedule context, plus
+ * start/stop the exchange. It calls TALER_TESTING_setup
+ * which creates the 'is' object.
+ */
+ TALER_TESTING_setup_with_exchange (&run,
+ NULL,
+ CONFIG_FILE))
+ return 1;
+ break;
+ default:
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+/* end of test_exchange_api_keys_cherry_picking_new.c */
diff --git a/src/lib/test_exchange_api_twisted.c b/src/lib/test_exchange_api_twisted.c
new file mode 100644
index 00000000..1c530058
--- /dev/null
+++ b/src/lib/test_exchange_api_twisted.c
@@ -0,0 +1,399 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange/test_exchange_api_twister.c
+ * @brief testcase to test exchange's HTTP API interface
+ * @author Marcello Stanisci
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <taler/taler_util.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include <taler/taler_bank_service.h>
+#include <taler/taler_fakebank_lib.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_twister_testing_lib.h>
+#include <taler/taler_twister_service.h>
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_exchange_api_twisted.conf"
+
+/**
+ * (real) Twister URL. Used at startup time to check if it runs.
+ */
+static char *twister_url;
+
+/**
+ * URL of the fakebank. Obtained from CONFIG_FILE's
+ * "exchange-wire-test:BANK_URI" option.
+ */
+static char *fakebank_url;
+
+/**
+ * Exchange base URL.
+ */
+static char *exchange_url;
+
+/**
+ * Auditor URL, unused but needed to achieve compilation.
+ */
+static char *auditor_url;
+
+/**
+ * Twister process.
+ */
+static struct GNUNET_OS_Process *twisterd;
+
+/**
+ * Account number of the exchange at the bank.
+ */
+#define EXCHANGE_ACCOUNT_NO 2
+
+/**
+ * Account number of some user.
+ */
+#define USER_ACCOUNT_NO 62
+
+/**
+ * User name. Never checked by fakebank.
+ */
+#define USER_LOGIN_NAME "user42"
+
+/**
+ * User password. Never checked by fakebank.
+ */
+#define USER_LOGIN_PASS "pass42"
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_WIREWATCH(label) \
+ TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE)
+
+/**
+ * Execute the taler-exchange-aggregator command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+ TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE)
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ * @param url exchange_url
+ */
+#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
+ TALER_TESTING_cmd_fakebank_transfer (label, amount, \
+ fakebank_url, USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO, \
+ USER_LOGIN_NAME, USER_LOGIN_PASS, exchange_url)
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE_SUBJECT(label,amount,subject) \
+ TALER_TESTING_cmd_fakebank_transfer_with_subject \
+ (label, amount, fakebank_url, USER_ACCOUNT_NO, \
+ EXCHANGE_ACCOUNT_NO, USER_LOGIN_NAME, USER_LOGIN_PASS, \
+ subject)
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+
+
+ /**
+ * This batch aims to trigger the 409 Conflict
+ * response from a refresh-reveal operation.
+ */
+ struct TALER_TESTING_Command refresh_409_conflict[] = {
+
+ CMD_TRANSFER_TO_EXCHANGE
+ ("refresh-create-reserve",
+ "EUR:5.01"),
+
+ /**
+ * Make previous command effective.
+ */
+ CMD_EXEC_WIREWATCH
+ ("wirewatch"),
+
+ /**
+ * Withdraw EUR:5.
+ */
+ TALER_TESTING_cmd_withdraw_amount
+ ("refresh-withdraw-coin",
+ "refresh-create-reserve",
+ "EUR:5",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_deposit
+ ("refresh-deposit-partial",
+ "refresh-withdraw-coin",
+ 0,
+ TALER_TESTING_make_wire_details
+ (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\",\
+ \"value\":\"EUR:1\"}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:1",
+ MHD_HTTP_OK),
+
+ /**
+ * Melt the rest of the coin's value
+ * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */
+ TALER_TESTING_cmd_refresh_melt
+ ("refresh-melt",
+ "EUR:4",
+ "refresh-withdraw-coin",
+ MHD_HTTP_OK),
+
+ /* Trigger 409 Conflict. */
+ TALER_TESTING_cmd_flip_upload
+ ("flip-upload",
+ CONFIG_FILE,
+ "transfer_privs.0"),
+
+ TALER_TESTING_cmd_refresh_reveal
+ ("refresh-(flipped-)reveal",
+ "refresh-melt",
+ MHD_HTTP_CONFLICT),
+
+ TALER_TESTING_cmd_end ()
+
+ };
+
+
+ /**
+ * NOTE: not all CMDs actually need the twister,
+ * so it may be better to move those into the "main"
+ * lib test suite.
+ */
+ struct TALER_TESTING_Command refund[] = {
+
+ CMD_TRANSFER_TO_EXCHANGE
+ ("create-reserve-r1",
+ "EUR:5.01"),
+
+ CMD_EXEC_WIREWATCH
+ ("wirewatch-r1"),
+
+ TALER_TESTING_cmd_withdraw_amount
+ ("withdraw-coin-r1",
+ "create-reserve-r1",
+ "EUR:5",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_deposit
+ ("deposit-refund-1",
+ "withdraw-coin-r1",
+ 0,
+ TALER_TESTING_make_wire_details
+ (42,
+ fakebank_url),
+ "{\"items\":[{\"name\":\"ice cream\","
+ "\"value\":\"EUR:5\"}]}",
+ GNUNET_TIME_UNIT_MINUTES,
+ "EUR:5",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_refund
+ ("refund-currency-missmatch",
+ MHD_HTTP_PRECONDITION_FAILED,
+ "USD:5",
+ "USD:0.01",
+ "deposit-refund-1"),
+
+ TALER_TESTING_cmd_refund
+ ("refund-fee-above-amount",
+ MHD_HTTP_BAD_REQUEST,
+ "EUR:5",
+ "EUR:10",
+ "deposit-refund-1"),
+
+ TALER_TESTING_cmd_flip_upload
+ ("flip-upload",
+ CONFIG_FILE,
+ "merchant_sig"),
+
+ TALER_TESTING_cmd_refund
+ ("refund-bad-sig",
+ MHD_HTTP_UNAUTHORIZED,
+ "EUR:5",
+ "EUR:0.01",
+ "deposit-refund-1"),
+
+ /* This next deposit CMD is only used to provide a
+ * good merchant signature to the next (failing) refund
+ * operations. */
+
+ TALER_TESTING_cmd_deposit
+ ("deposit-refund-to-fail",
+ "withdraw-coin-r1",
+ 0, /* coin index. */
+ TALER_TESTING_make_wire_details
+ (42,
+ fakebank_url),
+ /* This parameter will make any comparison about
+ h_contract_terms fail, when /refund will be handled.
+ So in other words, this is h_contract missmatch. */
+ "{\"items\":[{\"name\":\"ice skate\","
+ "\"value\":\"EUR:5\"}]}",
+ GNUNET_TIME_UNIT_MINUTES,
+ "EUR:5",
+ MHD_HTTP_FORBIDDEN),
+
+ TALER_TESTING_cmd_refund
+ ("refund-deposit-not-found",
+ MHD_HTTP_NOT_FOUND,
+ "EUR:5",
+ "EUR:0.01",
+ "deposit-refund-to-fail"),
+
+ TALER_TESTING_cmd_refund
+ ("refund-insufficient-funds",
+ MHD_HTTP_PRECONDITION_FAILED,
+ "EUR:50",
+ "EUR:0.01",
+ "deposit-refund-1"),
+
+ TALER_TESTING_cmd_refund
+ ("refund-fee-too-low",
+ MHD_HTTP_BAD_REQUEST,
+ "EUR:5",
+ "EUR:0.000001",
+ "deposit-refund-1"),
+
+ TALER_TESTING_cmd_end ()
+
+ };
+
+ struct TALER_TESTING_Command commands[] = {
+
+ TALER_TESTING_cmd_batch ("refresh-reveal-409-conflict",
+ refresh_409_conflict),
+
+ TALER_TESTING_cmd_batch ("refund",
+ refund),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run_with_fakebank (is,
+ commands,
+ fakebank_url);
+}
+
+/**
+ * Kill, wait, and destroy convenience function.
+ *
+ * @param process process to purge.
+ */
+static void
+purge_process (struct GNUNET_OS_Process *process)
+{
+ GNUNET_OS_process_kill (process, SIGINT);
+ GNUNET_OS_process_wait (process);
+ GNUNET_OS_process_destroy (process);
+}
+
+int
+main (int argc,
+ char * const *argv)
+{
+ unsigned int ret;
+ /* These environment variables get in the way... */
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup ("test-exchange-api-new-twisted",
+ "DEBUG", NULL);
+
+ if (NULL == (fakebank_url = TALER_TESTING_prepare_fakebank
+ (CONFIG_FILE,
+ "account-2")))
+ return 77;
+
+ if (NULL == (twister_url = TALER_TESTING_prepare_twister
+ (CONFIG_FILE)))
+ return 77;
+
+ TALER_TESTING_cleanup_files (CONFIG_FILE);
+
+ switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
+ &auditor_url,
+ &exchange_url))
+ {
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ return 1;
+ case GNUNET_NO:
+ return 77;
+
+ case GNUNET_OK:
+
+ if (NULL == (twisterd = TALER_TESTING_run_twister
+ (CONFIG_FILE)))
+ return 77;
+
+ ret = TALER_TESTING_setup_with_exchange (&run,
+ NULL,
+ CONFIG_FILE);
+ purge_process (twisterd);
+ GNUNET_free (twister_url);
+
+ if (GNUNET_OK != ret)
+ return 1;
+ break;
+ default:
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+/* end of test_exchange_api_twisted.c */
diff --git a/src/lib/test_exchange_api_twisted.conf b/src/lib/test_exchange_api_twisted.conf
new file mode 100644
index 00000000..ba59b5a6
--- /dev/null
+++ b/src/lib/test_exchange_api_twisted.conf
@@ -0,0 +1,169 @@
+# This file is in the public domain.
+#
+
+[twister]
+# HTTP listen port for twister
+HTTP_PORT = 8888
+
+# HTTP Destination for twister. The test-Webserver needs
+# to listen on the port used here. Note: no trailing '/'!
+DESTINATION_BASE_URL = "http://localhost:8081"
+
+# Control port for TCP
+# PORT = 8889
+HOSTNAME = localhost
+ACCEPT_FROM = 127.0.0.1;
+ACCEPT_FROM6 = ::1;
+
+# Control port for UNIX
+UNIXPATH = /tmp/taler-service-twister.sock
+UNIX_MATCH_UID = NO
+UNIX_MATCH_GID = YES
+
+# Launching of twister by ARM
+# BINARY = taler-service-twister
+# AUTOSTART = NO
+# FORCESTART = NO
+
+[PATHS]
+# Persistant data storage for the testcase
+TALER_TEST_HOME = test_exchange_api_home/
+
+[taler]
+# Currency supported by the exchange (can only be one)
+CURRENCY = EUR
+
+[exchange]
+
+# how long is one signkey valid?
+SIGNKEY_DURATION = 4 weeks
+
+# how long are the signatures with the signkey valid?
+LEGAL_DURATION = 2 years
+
+# how long do we provide to clients denomination and signing keys
+# ahead of time?
+LOOKAHEAD_PROVIDE = 4 weeks 1 day
+
+# Keep it short so the test runs fast.
+LOOKAHEAD_SIGN = 12 h
+
+# HTTP port the exchange listens to
+PORT = 8081
+
+# Master public key used to sign the exchange's various keys
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+
+# How to access our database
+DB = postgres
+
+# Base URL of the exchange ('S PROXY). This URL is where the
+# twister listens at, so that it will be able to get all the
+# connection addressed to the exchange. In fact, the presence
+# of the twister is 100% transparent to the test case, as it
+# only seeks the exchange/BASE_URL URL to connect to the exchange.
+BASE_URL = "http://localhost:8888/"
+
+[exchangedb-postgres]
+DB_CONN_STR = "postgres:///talercheck"
+
+[auditor]
+BASE_URL = "http://the.auditor/"
+
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[account-2]
+URL = payto://x-taler-bank/localhost:8082/2
+PLUGIN = taler_bank
+WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json
+TALER_BANK_AUTH_METHOD = "basic"
+USERNAME = user
+PASSWORD = pass
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[fees-x-taler-bank]
+# Fees for the forseeable future...
+# If you see this after 2017, update to match the next 10 years...
+WIRE-FEE-2018 = EUR:0.01
+WIRE-FEE-2019 = EUR:0.01
+WIRE-FEE-2020 = EUR:0.01
+WIRE-FEE-2021 = EUR:0.01
+WIRE-FEE-2022 = EUR:0.01
+WIRE-FEE-2023 = EUR:0.01
+WIRE-FEE-2024 = EUR:0.01
+WIRE-FEE-2025 = EUR:0.01
+WIRE-FEE-2026 = EUR:0.01
+WIRE-FEE-2027 = EUR:0.01
+
+CLOSING-FEE-2018 = EUR:0.01
+CLOSING-FEE-2019 = EUR:0.01
+CLOSING-FEE-2020 = EUR:0.01
+CLOSING-FEE-2021 = EUR:0.01
+CLOSING-FEE-2022 = EUR:0.01
+CLOSING-FEE-2023 = EUR:0.01
+CLOSING-FEE-2024 = EUR:0.01
+CLOSING-FEE-2025 = EUR:0.01
+CLOSING-FEE-2026 = EUR:0.01
+CLOSING-FEE-2027 = EUR:0.01
+
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_1]
+value = EUR:1
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_5]
+value = EUR:5
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_10]
+value = EUR:10
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
diff --git a/src/lib/testing_api_cmd_bank_check.c b/src/lib/testing_api_cmd_bank_check.c
new file mode 100644
index 00000000..265cba17
--- /dev/null
+++ b/src/lib/testing_api_cmd_bank_check.c
@@ -0,0 +1,379 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/testing_api_cmd_bank_check.c
+ * @brief command to check if a particular wire transfer took
+ * place.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_testing_lib.h"
+#include "taler_fakebank_lib.h"
+
+
+/**
+ * State for a "bank check" CMD.
+ */
+struct BankCheckState
+{
+
+ /**
+ * Base URL of the exchange supposed to be
+ * involved in the bank transaction.
+ */
+ const char *exchange_base_url;
+
+ /**
+ * Expected transferred amount.
+ */
+ const char *amount;
+
+ /**
+ * Expected debit bank account.
+ */
+ uint64_t debit_account;
+
+ /**
+ * Expected credit bank account.
+ */
+ uint64_t credit_account;
+
+ /**
+ * Wire transfer subject (set by fakebank-lib).
+ */
+ char *subject;
+
+ /**
+ * Binary form of the wire transfer subject.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Reference to a CMD that provides all the data
+ * needed to issue the bank check. If NULL, that data
+ * must exist here in the state.
+ */
+ const char *deposit_reference;
+};
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+check_bank_transfer_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct BankCheckState *bcs = cls;
+
+ struct TALER_Amount amount;
+ const uint64_t *debit_account;
+ const uint64_t *credit_account;
+ const char *exchange_base_url;
+
+
+ if (NULL == bcs->deposit_reference)
+ {
+ TALER_LOG_INFO ("Deposit reference NOT given\n");
+ debit_account = &bcs->debit_account;
+ credit_account = &bcs->credit_account;
+ exchange_base_url = bcs->exchange_base_url;
+
+ if (GNUNET_OK !=
+ TALER_string_to_amount (bcs->amount,
+ &amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %u\n",
+ bcs->amount,
+ is->ip);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ }
+
+ if (NULL != bcs->deposit_reference)
+ {
+ const struct TALER_TESTING_Command *deposit_cmd;
+ const struct TALER_Amount *amount_ptr;
+
+ TALER_LOG_INFO ("`%s' uses reference (%s/%p)\n",
+ TALER_TESTING_interpreter_get_current_label
+ (is),
+ bcs->deposit_reference,
+ bcs->deposit_reference);
+ deposit_cmd = TALER_TESTING_interpreter_lookup_command
+ (is, bcs->deposit_reference);
+
+ if (NULL == deposit_cmd)
+ TALER_TESTING_FAIL (is);
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_get_trait_amount_obj
+ (deposit_cmd, 0, &amount_ptr));
+ amount = *amount_ptr;
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT
+ (deposit_cmd, &debit_account));
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT
+ (deposit_cmd, &credit_account));
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_get_trait_url
+ (deposit_cmd, 0, &exchange_base_url)); // check 0 works!
+
+ }
+
+ if (GNUNET_OK !=
+ TALER_FAKEBANK_check (is->fakebank,
+ &amount,
+ *debit_account,
+ *credit_account,
+ exchange_base_url,
+ &bcs->subject))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+/**
+ * Free the state of a "bank check" CMD.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+check_bank_transfer_cleanup
+ (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct BankCheckState *bcs = cls;
+
+ GNUNET_free_non_null (bcs->subject);
+ GNUNET_free (bcs);
+}
+
+/**
+ * Offer internal data from a "bank check" CMD state.
+ *
+ * @param cls closure.
+ * @param ret[out] result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static int
+check_bank_transfer_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct BankCheckState *bcs = cls;
+ struct TALER_WireTransferIdentifierRawP *wtid_ptr;
+
+ if (GNUNET_OK != GNUNET_STRINGS_string_to_data
+ (bcs->subject,
+ strlen (bcs->subject),
+ &bcs->wtid,
+ sizeof (struct TALER_WireTransferIdentifierRawP)))
+ wtid_ptr = NULL;
+ else
+ wtid_ptr = &bcs->wtid;
+
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_transfer_subject (0, bcs->subject),
+ TALER_TESTING_make_trait_wtid (0, wtid_ptr),
+ TALER_TESTING_make_trait_url (0, bcs->exchange_base_url),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+
+/**
+ * Make a "bank check" CMD. It checks whether a
+ * particular wire transfer has been made or not.
+ *
+ * @param label the command label.
+ * @param exchange_base_url base url of the exchange involved in
+ * the wire transfer.
+ * @param amount the amount expected to be transferred.
+ * @param debit_account the account that gave money.
+ * @param credit_account the account that received money.
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_bank_transfer
+ (const char *label,
+ const char *exchange_base_url,
+ const char *amount,
+ uint64_t debit_account,
+ uint64_t credit_account)
+{
+ struct BankCheckState *bcs;
+
+ bcs = GNUNET_new (struct BankCheckState);
+ bcs->exchange_base_url = exchange_base_url;
+ bcs->amount = amount;
+ bcs->debit_account = debit_account;
+ bcs->credit_account = credit_account;
+
+ bcs->deposit_reference = NULL;
+
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .cls = bcs,
+ .run = &check_bank_transfer_run,
+ .cleanup = &check_bank_transfer_cleanup,
+ .traits = &check_bank_transfer_traits
+ };
+
+ return cmd;
+}
+
+/**
+ * Cleanup the state, only defined to respect the API.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+check_bank_empty_cleanup
+ (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ return;
+}
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+check_bank_empty_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+
+ if (GNUNET_OK != TALER_FAKEBANK_check_empty (is->fakebank))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Some commands (notably "bank history") could randomly
+ * look for traits; this way makes sure we don't segfault.
+ */
+static int
+check_bank_empty_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Checks wheter all the wire transfers got "checked"
+ * by the "bank check" CMD.
+ *
+ * @param label command label.
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_bank_empty (const char *label)
+{
+ struct TALER_TESTING_Command cmd;
+
+ cmd.label = label;
+ cmd.run = &check_bank_empty_run;
+ cmd.cleanup = &check_bank_empty_cleanup;
+ cmd.traits = &check_bank_empty_traits;
+
+ return cmd;
+}
+
+
+/**
+ * Define a "bank check" CMD that takes the input
+ * data from another CMD that offers it.
+ *
+ * @param label command label.
+ * @param deposit_reference reference to a CMD that is
+ * able to provide the "check bank transfer" operation
+ * input data.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_bank_transfer_with_ref
+ (const char *label,
+ const char *deposit_reference)
+{
+
+ struct BankCheckState *bcs;
+ struct TALER_TESTING_Command cmd;
+
+ bcs = GNUNET_new (struct BankCheckState);
+ bcs->deposit_reference = deposit_reference;
+
+ cmd.label = label;
+ cmd.cls = bcs;
+ cmd.run = &check_bank_transfer_run;
+ cmd.cleanup = &check_bank_transfer_cleanup;
+ cmd.traits = &check_bank_transfer_traits;
+
+ return cmd;
+}
diff --git a/src/lib/testing_api_cmd_batch.c b/src/lib/testing_api_cmd_batch.c
new file mode 100644
index 00000000..a56c959a
--- /dev/null
+++ b/src/lib/testing_api_cmd_batch.c
@@ -0,0 +1,208 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange/testing_api_cmd_batch.c
+ * @brief Implement batch-execution of CMDs.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "batch" CMD.
+ */
+struct BatchState
+{
+ /**
+ * CMDs batch.
+ */
+ struct TALER_TESTING_Command *batch;
+
+ /**
+ * Internal comand pointer.
+ */
+ int batch_ip;
+};
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+batch_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct BatchState *bs = cls;
+
+ bs->batch_ip++;
+ if (NULL != bs->batch[bs->batch_ip].label)
+ TALER_LOG_DEBUG ("Running batched command: %s\n",
+ bs->batch[bs->batch_ip].label);
+
+ /* hit end command, leap to next top-level command. */
+ if (NULL == bs->batch[bs->batch_ip].label)
+ {
+ TALER_LOG_INFO ("Exiting from batch: %s\n",
+ cmd->label);
+ TALER_TESTING_interpreter_next (is);
+ return;
+ }
+
+ bs->batch[bs->batch_ip].run (bs->batch[bs->batch_ip].cls,
+ &bs->batch[bs->batch_ip],
+ is);
+}
+
+
+/**
+ * Cleanup the state from a "reserve status" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+batch_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct BatchState *bs = cls;
+
+ for (unsigned int i=0;
+ NULL != bs->batch[i].label;
+ i++)
+ bs->batch[i].cleanup (bs->batch[i].cls,
+ &bs->batch[i]);
+ GNUNET_free_non_null (bs->batch);
+ GNUNET_free (bs);
+}
+
+
+/**
+ * Offer internal data from a "batch" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param ret[out] result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static int
+batch_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+#define CURRENT_CMD_INDEX 0
+#define BATCH_INDEX 1
+
+ struct BatchState *bs = cls;
+
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_cmd
+ (CURRENT_CMD_INDEX, &bs->batch[bs->batch_ip]),
+ TALER_TESTING_make_trait_cmd
+ (BATCH_INDEX, bs->batch),
+ TALER_TESTING_trait_end ()
+ };
+
+ /* Always return current command. */
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+/**
+ * Create a "batch" command. Such command takes a
+ * end_CMD-terminated array of CMDs and executed them.
+ * Once it hits the end CMD, it passes the control
+ * to the next top-level CMD, regardless of it being
+ * another batch or ordinary CMD.
+ *
+ * @param label the command label.
+ * @param batch array of CMDs to execute.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_batch (const char *label,
+ struct TALER_TESTING_Command *batch)
+{
+ struct BatchState *bs;
+ unsigned int i;
+
+ bs = GNUNET_new (struct BatchState);
+ bs->batch_ip = -1;
+
+ /* Get number of commands. */
+ for (i=0;NULL != batch[i].label;i++)
+ /* noop */
+ ;
+
+ bs->batch = GNUNET_new_array (i + 1,
+ struct TALER_TESTING_Command);
+ memcpy (bs->batch,
+ batch,
+ sizeof (struct TALER_TESTING_Command) * i);
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = bs,
+ .label = label,
+ .run = &batch_run,
+ .cleanup = &batch_cleanup,
+ .traits = &batch_traits
+ };
+
+ return cmd;
+}
+
+
+/**
+ * Test if this command is a batch command.
+ *
+ * @return false if not, true if it is a batch command
+ */
+int
+TALER_TESTING_cmd_is_batch (const struct TALER_TESTING_Command *cmd)
+{
+ return cmd->run == &batch_run;
+}
+
+
+/**
+ * Obtain what command the batch is at.
+ *
+ * @return cmd current batch command
+ */
+struct TALER_TESTING_Command *
+TALER_TESTING_cmd_batch_get_current (const struct TALER_TESTING_Command *cmd)
+{
+ struct BatchState *bs = cmd->cls;
+
+ return &bs->batch[bs->batch_ip];
+}
diff --git a/src/lib/testing_api_cmd_check_keys.c b/src/lib/testing_api_cmd_check_keys.c
new file mode 100644
index 00000000..d329f31a
--- /dev/null
+++ b/src/lib/testing_api_cmd_check_keys.c
@@ -0,0 +1,165 @@
+/*
+ This file is part of TALER
+ (C) 2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/testing_api_cmd_check_keys.c
+ * @brief Implementation of "check keys" test command.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "check keys" CMD.
+ */
+struct CheckKeysState
+{
+ /**
+ * This number will instruct the CMD interpreter to
+ * make sure that /keys was downloaded `generation` times
+ * _before_ running the very CMD logic.
+ */
+ unsigned int generation;
+
+ /**
+ * How many denomination keys the exchange is
+ * supposed to have.
+ */
+ unsigned int num_denom_keys;
+};
+
+
+/**
+ * Run the "check keys" command.
+ *
+ * @param cls closure.
+ * @param cmd the command currently being executed.
+ * @param is the interpreter state.
+ */
+static void
+check_keys_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct CheckKeysState *cks = cls;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "cmd `%s', key generation: %d\n",
+ cmd->label,
+ is->key_generation);
+
+ if (is->key_generation < cks->generation)
+ {
+ is->working = GNUNET_NO;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Triggering GET /keys, cmd `%s'\n",
+ cmd->label);
+
+ /* Means re-download /keys. */
+ GNUNET_break (0 == TALER_EXCHANGE_check_keys_current
+ (is->exchange, GNUNET_YES).abs_value_us);
+ return;
+ }
+ if (is->key_generation > cks->generation)
+ {
+ /* We got /keys too often, strange. Fatal. May theoretically
+ happen if somehow we were really unlucky and /keys expired
+ "naturally", but obviously with a sane configuration this
+ should also not be. */
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ /* /keys was updated, let's check they were OK! */
+ if (cks->num_denom_keys != is->keys->num_denom_keys)
+ {
+ /* Did not get the expected number of denomination keys! */
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Got %u keys in step %s, expected %u\n",
+ is->keys->num_denom_keys,
+ cmd->label,
+ cks->num_denom_keys);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+check_keys_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct CheckKeysState *cks = cls;
+
+ GNUNET_free (cks);
+}
+
+
+/**
+ * Make a "check keys" command. This type of command
+ * checks whether the number of denomination keys from
+ * @a exchange matches @a num_denom_keys.
+ *
+ * @param label command label
+ * @param generation when this command is run, exactly @a
+ * generation /keys downloads took place. If the number
+ * of downloads is less than @a generation, the logic will
+ * first make sure that @a generation downloads are done,
+ * and _then_ execute the rest of the command.
+ * @param num_denom_keys expected number of denomination keys.
+ * @param exchange connection handle to the exchange to test.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_keys
+ (const char *label,
+ unsigned int generation,
+ unsigned int num_denom_keys)
+{
+ struct CheckKeysState *cks;
+
+ cks = GNUNET_new (struct CheckKeysState);
+ cks->generation = generation;
+ cks->num_denom_keys = num_denom_keys;
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = cks,
+ .label = label,
+ .run = &check_keys_run,
+ .cleanup = &check_keys_cleanup
+ };
+
+ return cmd;
+}
+
+/* end of testing_api_cmd_check_keys.c */
diff --git a/src/lib/testing_api_cmd_deposit.c b/src/lib/testing_api_cmd_deposit.c
new file mode 100644
index 00000000..6fa2310d
--- /dev/null
+++ b/src/lib/testing_api_cmd_deposit.c
@@ -0,0 +1,576 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/testing_api_cmd_deposit.c
+ * @brief command for testing /deposit.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * State for a "deposit" CMD.
+ */
+struct DepositState
+{
+
+ /**
+ * Amount to deposit.
+ */
+ const char *amount;
+
+ /**
+ * Reference to any command that is able to provide a coin.
+ */
+ const char *coin_reference;
+
+ /**
+ * If @e coin_reference refers to an operation that generated
+ * an array of coins, this value determines which coin to pick.
+ */
+ unsigned int coin_index;
+
+ /**
+ * Wire details of who is depositing -- this would be merchant
+ * wire details in a normal scenario.
+ */
+ json_t *wire_details;
+
+ /**
+ * JSON string describing what a proposal is about.
+ */
+ json_t *contract_terms;
+
+ /**
+ * Relative time (to add to 'now') to compute the refund
+ * deadline. Zero for no refunds.
+ */
+ struct GNUNET_TIME_Relative refund_deadline;
+
+ /**
+ * Set (by the interpreter) to a fresh private key. This
+ * key will be used to sign the deposit request.
+ */
+ struct TALER_MerchantPrivateKeyP merchant_priv;
+
+ /**
+ * Deposit handle while operation is running.
+ */
+ struct TALER_EXCHANGE_DepositHandle *dh;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Task scheduled to try later.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * How long do we wait until we retry?
+ */
+ struct GNUNET_TIME_Relative backoff;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Should we retry on (transient) failures?
+ */
+ int do_retry;
+
+ /**
+ * Set to #GNUNET_YES if the /deposit succeeded
+ * and we now can provide the resulting traits.
+ */
+ int traits_ready;
+
+ /**
+ * Signing key used by the exchange to sign the
+ * deposit confirmation.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Signature from the exchange on the
+ * deposit confirmation.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+};
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+deposit_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Task scheduled to re-try #deposit_run.
+ *
+ * @param cls a `struct DepositState`
+ */
+static void
+do_retry (void *cls)
+{
+ struct DepositState *ds = cls;
+
+ ds->retry_task = NULL;
+ deposit_run (ds,
+ NULL,
+ ds->is);
+}
+
+
+/**
+ * Callback to analyze the /deposit response, just used to
+ * check if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param http_status HTTP response code.
+ * @param ec taler-specific error code.
+ * @param exchange_sig signature provided by the exchange
+ * (NULL on errors)
+ * @param exchange_pub public key of the exchange,
+ * used for signing the response.
+ * @param obj raw response from the exchange.
+ */
+static void
+deposit_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const json_t *obj)
+{
+ struct DepositState *ds = cls;
+
+ ds->dh = NULL;
+ if (ds->expected_response_code != http_status)
+ {
+ if (GNUNET_YES == ds->do_retry)
+ {
+ if ( (0 == http_status) ||
+ (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Retrying deposit failed with %u/%d\n",
+ http_status,
+ (int) ec);
+ /* on DB conflicts, do not use backoff */
+ if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ ds->backoff = GNUNET_TIME_UNIT_ZERO;
+ else
+ ds->backoff = EXCHANGE_LIB_BACKOFF (ds->backoff);
+ ds->retry_task
+ = GNUNET_SCHEDULER_add_delayed (ds->backoff,
+ &do_retry,
+ ds);
+ return;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s in %s:%u\n",
+ http_status,
+ ds->is->commands[ds->is->ip].label,
+ __FILE__,
+ __LINE__);
+ json_dumpf (obj, stderr, 0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return;
+ }
+ if (MHD_HTTP_OK == http_status)
+ {
+ ds->traits_ready = GNUNET_YES;
+ ds->exchange_pub = *exchange_pub;
+ ds->exchange_sig = *exchange_sig;
+ }
+ TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+deposit_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct DepositState *ds = cls;
+ const struct TALER_TESTING_Command *coin_cmd;
+ struct TALER_TESTING_Command *this_cmd;
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+ const struct TALER_DenominationSignature *denom_pub_sig;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct GNUNET_TIME_Absolute refund_deadline;
+ struct GNUNET_TIME_Absolute wire_deadline;
+ struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_CRYPTO_EddsaPrivateKey *merchant_priv;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_HashCode h_contract_terms;
+ struct TALER_Amount amount;
+
+ ds->is = is;
+ this_cmd = &is->commands[is->ip];
+
+ GNUNET_assert (ds->coin_reference);
+ coin_cmd = TALER_TESTING_interpreter_lookup_command
+ (is,
+ ds->coin_reference);
+ if (NULL == coin_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ /* Fixme: do prefer "interpreter fail" over assertions,
+ * as the former takes care of shutting down processes too */
+ GNUNET_assert (NULL != coin_cmd);
+
+ GNUNET_assert (GNUNET_OK
+ == TALER_TESTING_get_trait_coin_priv (coin_cmd,
+ ds->coin_index,
+ &coin_priv));
+
+ GNUNET_assert (GNUNET_OK
+ == TALER_TESTING_get_trait_denom_pub (coin_cmd,
+ ds->coin_index,
+ &denom_pub));
+
+ GNUNET_assert (GNUNET_OK
+ == TALER_TESTING_get_trait_denom_sig (coin_cmd,
+ ds->coin_index,
+ &denom_pub_sig));
+ if (GNUNET_OK !=
+ TALER_string_to_amount (ds->amount,
+ &amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at '%u/%s'\n",
+ ds->amount, is->ip, this_cmd->label);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_JSON_hash (ds->contract_terms,
+ &h_contract_terms));
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+ &coin_pub.eddsa_pub);
+
+ merchant_priv = GNUNET_CRYPTO_eddsa_key_create ();
+ ds->merchant_priv.eddsa_priv = *merchant_priv;
+ GNUNET_free (merchant_priv);
+
+ if (0 != ds->refund_deadline.rel_value_us)
+ {
+ refund_deadline = GNUNET_TIME_relative_to_absolute
+ (ds->refund_deadline);
+ wire_deadline = GNUNET_TIME_relative_to_absolute
+ (GNUNET_TIME_relative_multiply
+ (ds->refund_deadline, 2));
+ }
+ else
+ {
+ refund_deadline = GNUNET_TIME_UNIT_ZERO_ABS;
+ wire_deadline = GNUNET_TIME_relative_to_absolute
+ (GNUNET_TIME_UNIT_ZERO);
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public
+ (&ds->merchant_priv.eddsa_priv,
+ &merchant_pub.eddsa_pub);
+
+ timestamp = GNUNET_TIME_absolute_get ();
+ GNUNET_TIME_round_abs (&timestamp);
+ GNUNET_TIME_round_abs (&refund_deadline);
+ GNUNET_TIME_round_abs (&wire_deadline);
+
+ {
+ struct TALER_DepositRequestPS dr;
+
+ memset (&dr, 0, sizeof (dr));
+ dr.purpose.size = htonl
+ (sizeof (struct TALER_DepositRequestPS));
+ dr.purpose.purpose = htonl
+ (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
+ dr.h_contract_terms = h_contract_terms;
+ GNUNET_assert
+ (GNUNET_OK ==
+ TALER_JSON_merchant_wire_signature_hash (ds->wire_details,
+ &dr.h_wire));
+ dr.timestamp = GNUNET_TIME_absolute_hton (timestamp);
+ dr.refund_deadline = GNUNET_TIME_absolute_hton
+ (refund_deadline);
+ TALER_amount_hton (&dr.amount_with_fee, &amount);
+ TALER_amount_hton
+ (&dr.deposit_fee, &denom_pub->fee_deposit);
+ dr.merchant = merchant_pub;
+ dr.coin_pub = coin_pub;
+ GNUNET_assert (GNUNET_OK == GNUNET_CRYPTO_eddsa_sign
+ (&coin_priv->eddsa_priv,
+ &dr.purpose,
+ &coin_sig.eddsa_signature));
+ }
+ ds->dh = TALER_EXCHANGE_deposit
+ (is->exchange,
+ &amount,
+ wire_deadline,
+ ds->wire_details,
+ &h_contract_terms,
+ &coin_pub,
+ denom_pub_sig,
+ &denom_pub->key,
+ timestamp,
+ &merchant_pub,
+ refund_deadline,
+ &coin_sig,
+ &deposit_cb,
+ ds);
+
+ if (NULL == ds->dh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ return;
+}
+
+
+/**
+ * Free the state of a "deposit" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, typically a #struct WireState.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+deposit_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct DepositState *ds = cls;
+
+ if (NULL != ds->dh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ ds->is->ip,
+ cmd->label);
+ TALER_EXCHANGE_deposit_cancel (ds->dh);
+ ds->dh = NULL;
+ }
+ if (NULL != ds->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (ds->retry_task);
+ ds->retry_task = NULL;
+ }
+ json_decref (ds->wire_details);
+ json_decref (ds->contract_terms);
+ GNUNET_free (ds);
+}
+
+
+/**
+ * Offer internal data from a "deposit" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param ret[out] result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ *
+ * @return #GNUNET_OK on success.
+ */
+static int
+deposit_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct DepositState *ds = cls;
+ const struct TALER_TESTING_Command *coin_cmd;
+ /* Will point to coin cmd internals. */
+ const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv;
+
+ coin_cmd = TALER_TESTING_interpreter_lookup_command
+ (ds->is,
+ ds->coin_reference);
+
+ if (NULL == coin_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (coin_cmd,
+ ds->coin_index,
+ &coin_spent_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ds->is);
+ return GNUNET_NO;
+ }
+
+ struct TALER_TESTING_Trait traits[] = {
+ /* First two traits are only available if
+ ds->traits is #GNUNET_YES */
+ TALER_TESTING_make_trait_exchange_pub (0,
+ &ds->exchange_pub),
+ TALER_TESTING_make_trait_exchange_sig (0,
+ &ds->exchange_sig),
+ /* These traits are always available */
+ TALER_TESTING_make_trait_coin_priv (0,
+ coin_spent_priv),
+ TALER_TESTING_make_trait_wire_details (0,
+ ds->wire_details),
+ TALER_TESTING_make_trait_contract_terms (0,
+ ds->contract_terms),
+ TALER_TESTING_make_trait_peer_key (0,
+ &ds->merchant_priv.eddsa_priv),
+ TALER_TESTING_make_trait_amount (0,
+ ds->amount),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait ((ds->traits_ready)
+ ? traits
+ : &traits[2],
+ ret,
+ trait,
+ index);
+}
+
+/**
+ * Create a "deposit" command.
+ *
+ * @param label command label.
+ * @param coin_reference reference to any operation that can
+ * provide a coin.
+ * @param coin_index if @a withdraw_reference offers an array of
+ * coins, this parameter selects which one in that array.
+ * This value is currently ignored, as only one-coin
+ * withdrawals are implemented.
+ * @param wire_details wire details associated with the "deposit"
+ * request.
+ * @param contract_terms contract terms to be signed over by the
+ * coin.
+ * @param refund_deadline refund deadline, zero means 'no refunds'.
+ * Note, if time were absolute, then it would have come
+ * one day and disrupt tests meaning.
+ * @param amount how much is going to be deposited.
+ * @param expected_response_code expected HTTP response code.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit
+ (const char *label,
+ const char *coin_reference,
+ unsigned int coin_index,
+ json_t *wire_details,
+ const char *contract_terms,
+ struct GNUNET_TIME_Relative refund_deadline,
+ const char *amount,
+ unsigned int expected_response_code)
+{
+ struct DepositState *ds;
+
+ ds = GNUNET_new (struct DepositState);
+ ds->coin_reference = coin_reference;
+ ds->coin_index = coin_index;
+ ds->wire_details = wire_details;
+ ds->contract_terms = json_loads (contract_terms,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == ds->contract_terms)
+ {
+ GNUNET_log
+ (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse contract terms `%s' for CMD `%s'\n",
+ contract_terms,
+ label);
+ GNUNET_assert (0);
+ }
+
+ ds->refund_deadline = refund_deadline;
+ ds->amount = amount;
+ ds->expected_response_code = expected_response_code;
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = ds,
+ .label = label,
+ .run = &deposit_run,
+ .cleanup = &deposit_cleanup,
+ .traits = &deposit_traits
+ };
+
+ return cmd;
+}
+
+
+/**
+ * Modify a deposit command to enable retries when we get transient
+ * errors from the exchange.
+ *
+ * @param cmd a deposit command
+ * @return the command with retries enabled
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit_with_retry (struct TALER_TESTING_Command cmd)
+{
+ struct DepositState *ds;
+
+ GNUNET_assert (&deposit_run == cmd.run);
+ ds = cmd.cls;
+ ds->do_retry = GNUNET_YES;
+ return cmd;
+}
+
+
+/* end of testing_api_cmd_deposit.c */
diff --git a/src/lib/testing_api_cmd_exec_aggregator.c b/src/lib/testing_api_cmd_exec_aggregator.c
new file mode 100644
index 00000000..c51d4498
--- /dev/null
+++ b/src/lib/testing_api_cmd_exec_aggregator.c
@@ -0,0 +1,166 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/testing_api_cmd_exec_aggregator.c
+ * @brief run the taler-exchange-aggregator command
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "aggregator" CMD.
+ */
+struct AggregatorState
+{
+
+ /**
+ * Aggregator process.
+ */
+ struct GNUNET_OS_Process *aggregator_proc;
+
+ /**
+ * Configuration file used by the aggregator.
+ */
+ const char *config_filename;
+};
+
+
+/**
+ * Run the command. Use the `taler-exchange-aggregator' program.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ * @param is interpreter state.
+ */
+static void
+aggregator_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AggregatorState *as = cls;
+
+ as->aggregator_proc
+ = GNUNET_OS_start_process (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-aggregator",
+ "taler-exchange-aggregator",
+ "-c", as->config_filename,
+ "-t", /* exit when done */
+ NULL);
+ if (NULL == as->aggregator_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "aggregator" CMD, and possibly kill its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+aggregator_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AggregatorState *as = cls;
+
+ if (NULL != as->aggregator_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (as->aggregator_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (as->aggregator_proc);
+ GNUNET_OS_process_destroy (as->aggregator_proc);
+ as->aggregator_proc = NULL;
+ }
+ GNUNET_free (as);
+}
+
+
+/**
+ * Offer "aggregator" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param ret[out] result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static int
+aggregator_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct AggregatorState *as = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (0, &as->aggregator_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Make a "aggregator" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration file for the
+ * aggregator to use.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_aggregator (const char *label,
+ const char *config_filename)
+{
+ struct AggregatorState *as;
+
+ as = GNUNET_new (struct AggregatorState);
+ as->config_filename = config_filename;
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = as,
+ .label = label,
+ .run = &aggregator_run,
+ .cleanup = &aggregator_cleanup,
+ .traits = &aggregator_traits
+ };
+
+ return cmd;
+}
+
+/* end of testing_api_cmd_exec_aggregator.c */
diff --git a/src/lib/testing_api_cmd_exec_auditor-sign.c b/src/lib/testing_api_cmd_exec_auditor-sign.c
new file mode 100644
index 00000000..90a1654c
--- /dev/null
+++ b/src/lib/testing_api_cmd_exec_auditor-sign.c
@@ -0,0 +1,232 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/testing_api_cmd_exec_auditor-sign.c
+ * @brief run the taler-exchange-aggregator command
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+
+/**
+ * State for a "auditor sign" CMD.
+ */
+struct AuditorSignState
+{
+
+ /**
+ * Handle to the process making the signature.
+ */
+ struct GNUNET_OS_Process *auditor_sign_proc;
+
+ /**
+ * Configuration file used by the command.
+ */
+ const char *config_filename;
+
+ /**
+ * File name of signed blob.
+ */
+ char *signed_keys_out;
+};
+
+
+/**
+ * Run the command; calls the `taler-auditor-sign' program.
+ *
+ * @param cls closure.
+ * @param cmd the command.
+ * @param is interpreter state.
+ */
+static void
+auditor_sign_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AuditorSignState *ass = cls;
+
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ char *test_home_dir;
+ char *exchange_master_pub;
+ struct GNUNET_TIME_Absolute now;
+
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK != GNUNET_CONFIGURATION_load
+ (cfg, ass->config_filename))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ "paths",
+ "TALER_TEST_HOME",
+ &test_home_dir))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "paths",
+ "TALER_TEST_HOME");
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ now = GNUNET_TIME_absolute_get ();
+ GNUNET_asprintf
+ (&ass->signed_keys_out,
+ "%s/.local/share/taler/auditors/auditor-%llu.out",
+ test_home_dir,
+ (unsigned long long) now.abs_value_us);
+ GNUNET_free (test_home_dir);
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "MASTER_PUBLIC_KEY",
+ &exchange_master_pub))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "MASTER_PUBLIC_KEY");
+ GNUNET_CONFIGURATION_destroy (cfg);
+
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ GNUNET_CONFIGURATION_destroy (cfg);
+
+ ass->auditor_sign_proc = GNUNET_OS_start_process
+ (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-auditor-sign",
+ "taler-auditor-sign",
+ "-c", ass->config_filename,
+ "-u", "http://auditor/",
+ "-m", exchange_master_pub,
+ "-r", "auditor.in",
+ "-o", ass->signed_keys_out,
+ NULL);
+ GNUNET_free (exchange_master_pub);
+ if (NULL == ass->auditor_sign_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "auditor sign" CMD, and possibly
+ * kill its process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+auditor_sign_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AuditorSignState *ass = cls;
+
+ if (NULL != ass->auditor_sign_proc)
+ {
+ GNUNET_break (0 == GNUNET_OS_process_kill
+ (ass->auditor_sign_proc, SIGKILL));
+ GNUNET_OS_process_wait (ass->auditor_sign_proc);
+ GNUNET_OS_process_destroy (ass->auditor_sign_proc);
+ ass->auditor_sign_proc = NULL;
+ }
+ GNUNET_free_non_null (ass->signed_keys_out);
+ GNUNET_free (ass);
+}
+
+
+/**
+ * Offer "auditor sign" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param ret[out] result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static int
+auditor_sign_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct AuditorSignState *ass = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (0, &ass->auditor_sign_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Make a "auditor sign" CMD.
+ *
+ * @param label command label
+ * @param config_filename configuration filename
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_auditor_sign (const char *label,
+ const char *config_filename)
+{
+ struct AuditorSignState *ass;
+
+ ass = GNUNET_new (struct AuditorSignState);
+ ass->config_filename = config_filename;
+
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = ass,
+ .label = label,
+ .run = &auditor_sign_run,
+ .cleanup = &auditor_sign_cleanup,
+ .traits = &auditor_sign_traits
+ };
+
+ return cmd;
+}
+
+/* end of testing_api_cmd_exec_auditor-sign.c */
diff --git a/src/lib/testing_api_cmd_exec_keyup.c b/src/lib/testing_api_cmd_exec_keyup.c
new file mode 100644
index 00000000..576aab3c
--- /dev/null
+++ b/src/lib/testing_api_cmd_exec_keyup.c
@@ -0,0 +1,169 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/testing_api_cmd_exec_keyup.c
+ * @brief run the taler-exchange-keyup command
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "keyup" CMD.
+ */
+struct KeyupState
+{
+
+ /**
+ * Process for the "keyup" command.
+ */
+ struct GNUNET_OS_Process *keyup_proc;
+
+ /**
+ * Configuration file used by the command.
+ */
+ const char *config_filename;
+};
+
+
+/**
+ * Run the command; calls the `taler-exchange-keyup' program.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+keyup_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct KeyupState *ks = cls;
+
+ ks->keyup_proc = GNUNET_OS_start_process
+ (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-keyup",
+ "taler-exchange-keyup",
+ "-c", ks->config_filename,
+ "-o", "auditor.in",
+ NULL);
+
+ if (NULL == ks->keyup_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "keyup" CMD, and possibly kills its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+keyup_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct KeyupState *ks = cls;
+
+ if (NULL != ks->keyup_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ks->keyup_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ks->keyup_proc);
+ GNUNET_OS_process_destroy (ks->keyup_proc);
+ ks->keyup_proc = NULL;
+ }
+ GNUNET_free (ks);
+}
+
+
+/**
+ * Offer "keyup" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param ret[out] result
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ *
+ * @return #GNUNET_OK on success.
+ */
+static int
+keyup_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct KeyupState *ks = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (0, &ks->keyup_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Make the "keyup" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_keyup (const char *label,
+ const char *config_filename)
+{
+ struct KeyupState *ks;
+
+ ks = GNUNET_new (struct KeyupState);
+ ks->config_filename = config_filename;
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = ks,
+ .label = label,
+ .run = &keyup_run,
+ .cleanup = &keyup_cleanup,
+ .traits = &keyup_traits
+ };
+
+ return cmd;
+}
+
+/* end of testing_api_cmd_exec_keyup.c */
diff --git a/src/lib/testing_api_cmd_exec_wirewatch.c b/src/lib/testing_api_cmd_exec_wirewatch.c
new file mode 100644
index 00000000..a81bf5c2
--- /dev/null
+++ b/src/lib/testing_api_cmd_exec_wirewatch.c
@@ -0,0 +1,168 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/testing_api_cmd_exec_wirewatch.c
+ * @brief run the taler-exchange-wirewatch command
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+
+/**
+ * State for a "wirewatch" CMD.
+ */
+struct WirewatchState
+{
+
+ /**
+ * Process for the wirewatcher.
+ */
+ struct GNUNET_OS_Process *wirewatch_proc;
+
+ /**
+ * Configuration file used by the wirewatcher.
+ */
+ const char *config_filename;
+};
+
+/**
+ * Run the command; use the `taler-exchange-wirewatch' program.
+ *
+ * @param cls closure.
+ * @param cmd command currently being executed.
+ * @param is interpreter state.
+ */
+static void
+wirewatch_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct WirewatchState *ws = cls;
+
+ ws->wirewatch_proc
+ = GNUNET_OS_start_process (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-wirewatch",
+ "taler-exchange-wirewatch",
+ "-c", ws->config_filename,
+ "-T", /* exit when done */
+ NULL);
+ if (NULL == ws->wirewatch_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "wirewatch" CMD, and possibly
+ * kills its process if it did not terminate regularly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+wirewatch_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct WirewatchState *ws = cls;
+
+ if (NULL != ws->wirewatch_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ws->wirewatch_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ws->wirewatch_proc);
+ GNUNET_OS_process_destroy (ws->wirewatch_proc);
+ ws->wirewatch_proc = NULL;
+ }
+ GNUNET_free (ws);
+}
+
+
+/**
+ * Offer "wirewatch" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param ret[out] result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static int
+wirewatch_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct WirewatchState *ws = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (0,
+ &ws->wirewatch_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Make a "wirewatch" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_wirewatch (const char *label,
+ const char *config_filename)
+{
+ struct WirewatchState *ws;
+
+ ws = GNUNET_new (struct WirewatchState);
+ ws->config_filename = config_filename;
+
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = ws,
+ .label = label,
+ .run = &wirewatch_run,
+ .cleanup = &wirewatch_cleanup,
+ .traits = &wirewatch_traits
+ };
+
+ return cmd;
+}
+
+/* end of testing_api_cmd_exec_wirewatch.c */
diff --git a/src/lib/testing_api_cmd_fakebank_transfer.c b/src/lib/testing_api_cmd_fakebank_transfer.c
new file mode 100644
index 00000000..43f72573
--- /dev/null
+++ b/src/lib/testing_api_cmd_fakebank_transfer.c
@@ -0,0 +1,756 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/testing_api_cmd_fakebank_transfer.c
+ * @brief implementation of a fakebank wire transfer command
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_bank_service.h"
+#include "taler_fakebank_lib.h"
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+#include "taler_testing_bank_lib.h"
+#include "backoff.h"
+
+/**
+ * State for a "fakebank transfer" CMD.
+ */
+struct FakebankTransferState
+{
+
+ /**
+ * Label of any command that can trait-offer a reserve priv.
+ */
+ const char *reserve_reference;
+
+ /**
+ * Wire transfer amount.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Wire transfer subject.
+ */
+ const char *subject;
+
+ /**
+ * Base URL of the bank serving the request.
+ */
+ const char *bank_url;
+
+ /**
+ * Money sender account number.
+ */
+ uint64_t debit_account_no;
+
+ /**
+ * Money receiver account number.
+ */
+ uint64_t credit_account_no;
+
+ /**
+ * Username to use for authentication.
+ */
+ const char *auth_username;
+
+ /**
+ * Password to use for authentication.
+ */
+ const char *auth_password;
+
+ /**
+ * Set (by the interpreter) to the reserve's private key
+ * we used to make a wire transfer subject line with.
+ */
+ struct TALER_ReservePrivateKeyP reserve_priv;
+
+ /**
+ * Handle to the pending request at the fakebank.
+ */
+ struct TALER_BANK_AdminAddIncomingHandle *aih;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Set to the wire transfer's unique ID.
+ */
+ uint64_t serial_id;
+
+ /**
+ * Exchange URL. This value is fed to the bank when requesting
+ * the wire transfer; note: the bank needs it because a merchant
+ * might want to know which exchange performed a wire transfer to
+ * them, just by looking at bank records.
+ */
+ const char *exchange_url;
+
+ /**
+ * Merchant instance. Sometimes used to get the tip reserve
+ * private key by reading the appropriate config section.
+ */
+ const char *instance;
+
+ /**
+ * Configuration filename. Used to get the tip reserve key
+ * filename (used to obtain a public key to write in the
+ * transfer subject).
+ */
+ const char *config_filename;
+
+ /**
+ * Task scheduled to try later.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * How long do we wait until we retry?
+ */
+ struct GNUNET_TIME_Relative backoff;
+
+ /**
+ * Was this command modified via
+ * #TALER_TESTING_cmd_fakebank_transfer_with_retry to
+ * enable retries?
+ */
+ int do_retry;
+};
+
+
+/**
+ * Run the "fakebank transfer" CMD.
+ *
+ * @param cls closure.
+ * @param cmd CMD being run.
+ * @param is interpreter state.
+ */
+static void
+fakebank_transfer_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Task scheduled to re-try #fakebank_transfer_run.
+ *
+ * @param cls a `struct FakebankTransferState`
+ */
+static void
+do_retry (void *cls)
+{
+ struct FakebankTransferState *fts = cls;
+
+ fts->retry_task = NULL;
+ fakebank_transfer_run (fts,
+ NULL,
+ fts->is);
+}
+
+
+/**
+ * This callback will process the fakebank response to the wire
+ * transfer. It just checks whether the HTTP response code is
+ * acceptable.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for
+ * successful status request; 0 if the exchange's reply is
+ * bogus (fails to follow the protocol)
+ * @param ec taler-specific error code, #TALER_EC_NONE on success
+ * @param serial_id unique ID of the wire transfer
+ * @param full_response full response from the exchange (for
+ * logging, in case of errors)
+ */
+static void
+add_incoming_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ uint64_t serial_id,
+ const json_t *full_response)
+{
+ struct FakebankTransferState *fts = cls;
+ struct TALER_TESTING_Interpreter *is = fts->is;
+
+ fts->aih = NULL;
+ fts->serial_id = serial_id;
+ if (MHD_HTTP_OK != http_status)
+ {
+ if (GNUNET_YES == fts->do_retry)
+ {
+ if ( (0 == http_status) ||
+ (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Retrying fakebank transfer failed with %u/%d\n",
+ http_status,
+ (int) ec);
+ /* on DB conflicts, do not use backoff */
+ if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ fts->backoff = GNUNET_TIME_UNIT_ZERO;
+ else
+ fts->backoff = EXCHANGE_LIB_BACKOFF (fts->backoff);
+ fts->retry_task = GNUNET_SCHEDULER_add_delayed (fts->backoff,
+ &do_retry,
+ fts);
+ return;
+ }
+ }
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Fakebank returned HTTP status %u/%d\n",
+ http_status,
+ (int) ec);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the "fakebank transfer" CMD.
+ *
+ * @param cls closure.
+ * @param cmd CMD being run.
+ * @param is interpreter state.
+ */
+static void
+fakebank_transfer_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct FakebankTransferState *fts = cls;
+ char *subject;
+ struct TALER_BANK_AuthenticationData auth;
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ if (NULL != fts->subject)
+ {
+ subject = GNUNET_strdup (fts->subject);
+ }
+ else
+ {
+ /* Use reserve public key as subject */
+ if (NULL != fts->reserve_reference)
+ {
+ const struct TALER_TESTING_Command *ref;
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ ref = TALER_TESTING_interpreter_lookup_command
+ (is, fts->reserve_reference);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_priv (ref,
+ 0,
+ &reserve_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ fts->reserve_priv.eddsa_priv = reserve_priv->eddsa_priv;
+ }
+ else
+ {
+ if (NULL != fts->instance)
+ {
+ char *section;
+ char *keys;
+ struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ GNUNET_assert (NULL != fts->config_filename);
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_load (cfg,
+ fts->config_filename))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ GNUNET_asprintf (&section,
+ "instance-%s",
+ fts->instance);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename
+ (cfg,
+ section,
+ "TIP_RESERVE_PRIV_FILENAME",
+ &keys))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Configuration fails to specify reserve"
+ " private key filename in section %s\n",
+ section);
+ GNUNET_free (section);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ priv = GNUNET_CRYPTO_eddsa_key_create_from_file (keys);
+ if (NULL == priv)
+ {
+ GNUNET_log_config_invalid
+ (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "TIP_RESERVE_PRIV_FILENAME",
+ "Failed to read private key");
+ GNUNET_free (keys);
+ GNUNET_free (section);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ fts->reserve_priv.eddsa_priv = *priv;
+ GNUNET_free (priv);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ }
+ else
+ {
+ /* No referenced reserve, no instance to take priv
+ * from, no explicit subject given: create new key! */
+ struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
+
+ priv = GNUNET_CRYPTO_eddsa_key_create ();
+ fts->reserve_priv.eddsa_priv = *priv;
+ GNUNET_free (priv);
+ }
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public
+ (&fts->reserve_priv.eddsa_priv, &reserve_pub.eddsa_pub);
+ subject = GNUNET_STRINGS_data_to_string_alloc
+ (&reserve_pub, sizeof (reserve_pub));
+ }
+
+ auth.method = TALER_BANK_AUTH_BASIC;
+ auth.details.basic.username = (char *) fts->auth_username;
+ auth.details.basic.password = (char *) fts->auth_password;
+ fts->is = is;
+ fts->aih = TALER_BANK_admin_add_incoming
+ (TALER_TESTING_interpreter_get_context (is),
+ fts->bank_url,
+ &auth,
+ fts->exchange_url,
+ subject,
+ &fts->amount,
+ fts->debit_account_no,
+ fts->credit_account_no,
+ &add_incoming_cb,
+ fts);
+ GNUNET_free (subject);
+ if (NULL == fts->aih)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "fakebank transfer" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure
+ * @param cmd current CMD being cleaned up.
+ */
+static void
+fakebank_transfer_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct FakebankTransferState *fts = cls;
+
+ if (NULL != fts->aih)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %s did not complete\n",
+ cmd->label);
+ TALER_BANK_admin_add_incoming_cancel (fts->aih);
+ }
+ if (NULL != fts->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (fts->retry_task);
+ fts->retry_task = NULL;
+ }
+ GNUNET_free (fts);
+}
+
+/**
+ * Offer internal data from a "fakebank transfer" CMD to other
+ * commands.
+ *
+ * @param cls closure.
+ * @param ret[out] result
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static int
+fakebank_transfer_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct FakebankTransferState *fts = cls;
+ #define MANDATORY 6
+ struct TALER_TESTING_Trait traits[MANDATORY + 1] = {
+ TALER_TESTING_MAKE_TRAIT_DEBIT_ACCOUNT
+ (&fts->debit_account_no),
+ TALER_TESTING_MAKE_TRAIT_CREDIT_ACCOUNT
+ (&fts->credit_account_no),
+ TALER_TESTING_make_trait_url (0, fts->exchange_url),
+ TALER_TESTING_MAKE_TRAIT_ROW_ID (&fts->serial_id),
+ TALER_TESTING_make_trait_amount_obj (0, &fts->amount),
+ };
+
+ /**
+ * The user gave explicit subject,
+ * there must be NO reserve priv. */
+ if (NULL != fts->subject)
+ traits[MANDATORY - 1] =
+ TALER_TESTING_make_trait_transfer_subject (0,
+ fts->subject);
+ /* A reserve priv must exist if no subject was given. */
+ else
+ traits[MANDATORY - 1] = TALER_TESTING_make_trait_reserve_priv
+ (0, &fts->reserve_priv),
+
+ traits[MANDATORY] = TALER_TESTING_trait_end ();
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Create fakebank_transfer command, the subject line will be
+ * derived from a randomly created reserve priv. Note that that
+ * reserve priv will then be offered as trait.
+ *
+ * @param label command label.
+ * @param amount amount to transfer.
+ * @param bank_url base URL of the bank that implements this
+ * wire transer. For simplicity, both credit and debit
+ * bank account exist at the same bank.
+ * @param debit_account_no which account (expressed as a number)
+ * gives money.
+ * @param credit_account_no which account (expressed as a number)
+ * receives money.
+ * @param auth_username username identifying the @a
+ * debit_account_no at the bank.
+ * @param auth_password password for @a auth_username.
+ * @param exchange_url which exchange is involved in this transfer.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_fakebank_transfer
+ (const char *label,
+ const char *amount,
+ const char *bank_url,
+ uint64_t debit_account_no,
+ uint64_t credit_account_no,
+ const char *auth_username,
+ const char *auth_password,
+ const char *exchange_url)
+{
+ struct FakebankTransferState *fts;
+
+ fts = GNUNET_new (struct FakebankTransferState);
+ fts->bank_url = bank_url;
+ fts->credit_account_no = credit_account_no;
+ fts->debit_account_no = debit_account_no;
+ fts->auth_username = auth_username;
+ fts->auth_password = auth_password;
+ fts->exchange_url = exchange_url;
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount,
+ &fts->amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %s\n",
+ amount,
+ label);
+ GNUNET_assert (0);
+ }
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = fts,
+ .label = label,
+ .run = &fakebank_transfer_run,
+ .cleanup = &fakebank_transfer_cleanup,
+ .traits = &fakebank_transfer_traits
+ };
+
+ return cmd;
+}
+
+
+/**
+ * Create "fakebank transfer" CMD, letting the caller specifying
+ * the subject line.
+ *
+ * @param label command label.
+ * @param amount amount to transfer.
+ * @param bank_url base URL of the bank that implements this
+ * wire transer. For simplicity, both credit and debit
+ * bank account exist at the same bank.
+ * @param debit_account_no which account (expressed as a number)
+ * gives money.
+ * @param credit_account_no which account (expressed as a number)
+ * receives money.
+ *
+ * @param auth_username username identifying the @a
+ * debit_account_no at the bank.
+ * @param auth_password password for @a auth_username.
+ * @param subject wire transfer's subject line.
+ * @param exchange_url which exchange is involved in this transfer.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_fakebank_transfer_with_subject
+ (const char *label,
+ const char *amount,
+ const char *bank_url,
+ uint64_t debit_account_no,
+ uint64_t credit_account_no,
+ const char *auth_username,
+ const char *auth_password,
+ const char *subject,
+ const char *exchange_url)
+{
+ struct FakebankTransferState *fts;
+
+ fts = GNUNET_new (struct FakebankTransferState);
+
+ TALER_LOG_DEBUG ("%s:FTS@%p\n",
+ label,
+ fts);
+
+ fts->bank_url = bank_url;
+ fts->credit_account_no = credit_account_no;
+ fts->debit_account_no = debit_account_no;
+ fts->auth_username = auth_username;
+ fts->auth_password = auth_password;
+ fts->subject = subject;
+ fts->exchange_url = exchange_url;
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount,
+ &fts->amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %s\n",
+ amount,
+ label);
+ GNUNET_assert (0);
+ }
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = fts,
+ .label = label,
+ .run = &fakebank_transfer_run,
+ .cleanup = &fakebank_transfer_cleanup,
+ .traits = &fakebank_transfer_traits
+ };
+
+ return cmd;
+}
+
+
+/**
+ * Create "fakebank transfer" CMD, letting the caller specify
+ * a reference to a command that can offer a reserve private key.
+ * This private key will then be used to construct the subject line
+ * of the wire transfer.
+ *
+ * @param label command label.
+ * @param amount the amount to transfer.
+ * @param bank_url base URL of the bank running the transfer.
+ * @param debit_account_no which account (expressed as a number)
+ * gives money.
+ * @param credit_account_no which account (expressed as a number)
+ * receives money.
+ * @param auth_username username identifying the @a
+ * debit_account_no at the bank.
+ * @param auth_password password for @a auth_username.
+ * @param ref reference to a command that can offer a reserve
+ * private key.
+ * @param exchange_url the exchage involved in the transfer,
+ * tipically receiving the money in order to fuel a reserve.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_fakebank_transfer_with_ref
+ (const char *label,
+ const char *amount,
+ const char *bank_url,
+ uint64_t debit_account_no,
+ uint64_t credit_account_no,
+ const char *auth_username,
+ const char *auth_password,
+ const char *ref,
+ const char *exchange_url)
+{
+ struct FakebankTransferState *fts;
+
+ fts = GNUNET_new (struct FakebankTransferState);
+ fts->bank_url = bank_url;
+ fts->credit_account_no = credit_account_no;
+ fts->debit_account_no = debit_account_no;
+ fts->auth_username = auth_username;
+ fts->auth_password = auth_password;
+ fts->reserve_reference = ref;
+ fts->exchange_url = exchange_url;
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount,
+ &fts->amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %s\n",
+ amount,
+ label);
+ GNUNET_assert (0);
+ }
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = fts,
+ .label = label,
+ .run = &fakebank_transfer_run,
+ .cleanup = &fakebank_transfer_cleanup,
+ .traits = &fakebank_transfer_traits
+ };
+
+ return cmd;
+}
+
+
+/**
+ * Create "fakebank transfer" CMD, letting the caller specifying
+ * the merchant instance. This version is useful when a tip
+ * reserve should be topped up, in fact the interpreter will need
+ * the "tipping instance" in order to get the instance public key
+ * and make a wire transfer subject out of it.
+ *
+ * @param label command label.
+ * @param amount amount to transfer.
+ * @param bank_url base URL of the bank that implements this
+ * wire transer. For simplicity, both credit and debit
+ * bank account exist at the same bank.
+ * @param debit_account_no which account (expressed as a number)
+ * gives money.
+ * @param credit_account_no which account (expressed as a number)
+ * receives money.
+ *
+ * @param auth_username username identifying the @a
+ * debit_account_no at the bank.
+ * @param auth_password password for @a auth_username.
+ * @param instance the instance that runs the tipping. Under this
+ * instance, the configuration file will provide the private
+ * key of the tipping reserve. This data will then used to
+ * construct the wire transfer subject line.
+ * @param exchange_url which exchange is involved in this transfer.
+ * @param config_filename configuration file to use.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_fakebank_transfer_with_instance
+ (const char *label,
+ const char *amount,
+ const char *bank_url,
+ uint64_t debit_account_no,
+ uint64_t credit_account_no,
+ const char *auth_username,
+ const char *auth_password,
+ const char *instance,
+ const char *exchange_url,
+ const char *config_filename)
+{
+ struct FakebankTransferState *fts;
+
+ fts = GNUNET_new (struct FakebankTransferState);
+ fts->bank_url = bank_url;
+ fts->credit_account_no = credit_account_no;
+ fts->debit_account_no = debit_account_no;
+ fts->auth_username = auth_username;
+ fts->auth_password = auth_password;
+ fts->instance = instance;
+ fts->exchange_url = exchange_url;
+ fts->config_filename = config_filename;
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount,
+ &fts->amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %s\n",
+ amount,
+ label);
+ GNUNET_assert (0);
+ }
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = fts,
+ .label = label,
+ .run = &fakebank_transfer_run,
+ .cleanup = &fakebank_transfer_cleanup,
+ .traits = &fakebank_transfer_traits
+ };
+
+ return cmd;
+}
+
+
+/**
+ * Modify a fakebank transfer command to enable retries when the
+ * reserve is not yet full or we get other transient errors from the
+ * fakebank.
+ *
+ * @param cmd a fakebank transfer command
+ * @return the command with retries enabled
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_fakebank_transfer_retry (struct TALER_TESTING_Command cmd)
+{
+ struct FakebankTransferState *fts;
+
+ GNUNET_assert (&fakebank_transfer_run == cmd.run);
+ fts = cmd.cls;
+ fts->do_retry = GNUNET_YES;
+ return cmd;
+}
+
+/* end of testing_api_cmd_fakebank_transfer.c */
diff --git a/src/lib/testing_api_cmd_payback.c b/src/lib/testing_api_cmd_payback.c
new file mode 100644
index 00000000..a4a3aeff
--- /dev/null
+++ b/src/lib/testing_api_cmd_payback.c
@@ -0,0 +1,498 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 3, or
+ (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange/testing_api_cmd_payback.c
+ * @brief Implement the /revoke and /payback test commands.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "revoke" CMD.
+ */
+struct RevokeState
+{
+ /**
+ * Expected HTTP status code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Command that offers a denomination to revoke.
+ */
+ const char *coin_reference;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * The revoke process handle.
+ */
+ struct GNUNET_OS_Process *revoke_proc;
+
+ /**
+ * Configuration file name.
+ */
+ const char *config_filename;
+
+ /**
+ * Encoding of the denomination (to revoke) public key hash.
+ */
+ char *dhks;
+
+};
+
+
+/**
+ * State for a "pay back" CMD.
+ */
+struct PaybackState
+{
+ /**
+ * Expected HTTP status code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Command that offers a reserve private key,
+ * plus a coin to be paid back.
+ */
+ const char *coin_reference;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Amount expected to be paid back.
+ */
+ const char *amount;
+
+ /**
+ * Handle to the ongoing operation.
+ */
+ struct TALER_EXCHANGE_PaybackHandle *ph;
+};
+
+/**
+ * Check the result of the payback request: checks whether
+ * the HTTP response code is good, and that the coin that
+ * was paid back belonged to the right reserve.
+ *
+ * @param cls closure
+ * @param http_status HTTP response code.
+ * @param ec taler-specific error code.
+ * @param amount amount the exchange will wire back for this coin.
+ * @param timestamp what time did the exchange receive the
+ * /payback request
+ * @param reserve_pub public key of the reserve affected by the
+ * payback.
+ * @param full_response raw response from the exchange.
+ */
+static void
+payback_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Absolute timestamp,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *full_response)
+{
+
+ struct PaybackState *ps = cls;
+ struct TALER_TESTING_Interpreter *is = ps->is;
+ struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
+ const struct TALER_TESTING_Command *reserve_cmd;
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+ struct TALER_ReservePublicKeyP rp;
+ struct TALER_Amount expected_amount;
+
+ ps->ph = NULL;
+ if (ps->expected_response_code != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s in %s:%u\n",
+ http_status,
+ cmd->label,
+ __FILE__,
+ __LINE__);
+ json_dumpf (full_response, stderr, 0);
+ fprintf (stderr, "\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ reserve_cmd = TALER_TESTING_interpreter_lookup_command
+ (is, ps->coin_reference);
+
+ if (NULL == reserve_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_reserve_priv
+ (reserve_cmd, 0, &reserve_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &rp.eddsa_pub);
+
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ if (GNUNET_OK != TALER_string_to_amount
+ (ps->amount, &expected_amount))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (0 != TALER_amount_cmp (amount, &expected_amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Total amount missmatch to command %s\n",
+ cmd->label);
+ json_dumpf (full_response, stderr, 0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (0 != memcmp (reserve_pub, &rp,
+ sizeof (struct TALER_ReservePublicKeyP)))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unmanaged HTTP status code.\n");
+ break;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+payback_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PaybackState *ps = cls;
+ const struct TALER_TESTING_Command *coin_cmd;
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ const struct TALER_DenominationBlindingKeyP *blinding_key;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+ const struct TALER_DenominationSignature *coin_sig;
+ struct TALER_PlanchetSecretsP planchet;
+
+ ps->is = is;
+ coin_cmd = TALER_TESTING_interpreter_lookup_command
+ (is, ps->coin_reference);
+
+ if (NULL == coin_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv
+ (coin_cmd, 0, &coin_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_blinding_key
+ (coin_cmd, 0, &blinding_key))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ planchet.coin_priv = *coin_priv;
+ planchet.blinding_key = *blinding_key;
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub
+ (coin_cmd, 0, &denom_pub))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_denom_sig
+ (coin_cmd, 0, &coin_sig))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to get '%s..' paid back\n",
+ TALER_B2S (&denom_pub->h_key));
+
+ ps->ph = TALER_EXCHANGE_payback (is->exchange,
+ denom_pub,
+ coin_sig,
+ &planchet,
+ payback_cb,
+ ps);
+ GNUNET_assert (NULL != ps->ph);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure, typically a #struct WireState.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+revoke_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+
+ struct RevokeState *rs = cls;
+
+ if (NULL != rs->revoke_proc)
+ {
+ GNUNET_break (0 == GNUNET_OS_process_kill
+ (rs->revoke_proc, SIGKILL));
+ GNUNET_OS_process_wait (rs->revoke_proc);
+ GNUNET_OS_process_destroy (rs->revoke_proc);
+ rs->revoke_proc = NULL;
+ }
+
+ GNUNET_free_non_null (rs->dhks);
+ GNUNET_free (rs);
+}
+
+
+/**
+ * Cleanup the "payback" CMD state, and possibly cancel
+ * a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+payback_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PaybackState *ps = cls;
+ if (NULL != ps->ph)
+ {
+ TALER_EXCHANGE_payback_cancel (ps->ph);
+ ps->ph = NULL;
+ }
+ GNUNET_free (ps);
+}
+
+
+/**
+ * Offer internal data from a "revoke" CMD to other CMDs.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static int
+revoke_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+
+ struct RevokeState *rs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ /* Needed by the handler which waits the proc'
+ * death and calls the next command */
+ TALER_TESTING_make_trait_process (0, &rs->revoke_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+/**
+ * Run the "revoke" command. The core of the function
+ * is to call the "keyup" utility passing it the base32
+ * encoding of the denomination to revoke.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+revoke_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RevokeState *rs = cls;
+ const struct TALER_TESTING_Command *coin_cmd;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+ rs->is = is;
+ /* Get denom pub from trait */
+ coin_cmd = TALER_TESTING_interpreter_lookup_command
+ (is, rs->coin_reference);
+
+ if (NULL == coin_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_denom_pub
+ (coin_cmd, 0, &denom_pub));
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to revoke denom '%s..'\n",
+ TALER_B2S (&denom_pub->h_key));
+
+ rs->dhks = GNUNET_STRINGS_data_to_string_alloc
+ (&denom_pub->h_key, sizeof (struct GNUNET_HashCode));
+
+ rs->revoke_proc = GNUNET_OS_start_process
+ (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-keyup",
+ "taler-exchange-keyup",
+ "-c", rs->config_filename,
+ "-r", rs->dhks,
+ NULL);
+
+ if (NULL == rs->revoke_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Revoke is ongoing..\n");
+
+ is->reload_keys = GNUNET_OK;
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Make a "payback" command.
+ *
+ * @param label the command label
+ * @param expected_response_code expected HTTP status code
+ * @param coin_reference reference to any command which
+ * offers a coin & reserve private key.
+ * @param amount denomination to pay back.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_payback (const char *label,
+ unsigned int expected_response_code,
+ const char *coin_reference,
+ const char *amount)
+{
+ struct PaybackState *ps;
+
+ ps = GNUNET_new (struct PaybackState);
+ ps->expected_response_code = expected_response_code;
+ ps->coin_reference = coin_reference;
+ ps->amount = amount;
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = ps,
+ .label = label,
+ .run = &payback_run,
+ .cleanup = &payback_cleanup
+ };
+
+ return cmd;
+}
+
+
+/**
+ * Make a "revoke" command.
+ *
+ * @param label the command label.
+ * @param expected_response_code expected HTTP status code.
+ * @param coin_reference reference to a CMD that will offer the
+ * denomination to revoke.
+ * @param config_filename configuration file name.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_revoke (const char *label,
+ unsigned int expected_response_code,
+ const char *coin_reference,
+ const char *config_filename)
+{
+
+ struct RevokeState *rs;
+
+ rs = GNUNET_new (struct RevokeState);
+ rs->expected_response_code = expected_response_code;
+ rs->coin_reference = coin_reference;
+ rs->config_filename = config_filename;
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = rs,
+ .label = label,
+ .run = &revoke_run,
+ .cleanup = &revoke_cleanup,
+ .traits = &revoke_traits
+ };
+
+ return cmd;
+}
diff --git a/src/lib/testing_api_cmd_refresh.c b/src/lib/testing_api_cmd_refresh.c
new file mode 100644
index 00000000..55900cf2
--- /dev/null
+++ b/src/lib/testing_api_cmd_refresh.c
@@ -0,0 +1,1317 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your
+ option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange-lib/testing_api_cmd_refresh.c
+ * @brief commands for testing all "refresh" features.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+/**
+ * Data for a coin to be melted.
+ */
+struct MeltDetails
+{
+
+ /**
+ * Amount to melt (including fee).
+ */
+ const char *amount;
+
+ /**
+ * Reference to reserve_withdraw operations for coin to
+ * be used for the /refresh/melt operation.
+ */
+ const char *coin_reference;
+};
+
+
+/**
+ * State for a "refresh melt" command.
+ */
+struct RefreshMeltState
+{
+
+ /**
+ * Information about coins to be melted.
+ */
+ struct MeltDetails melted_coin;
+
+ /**
+ * "Crypto data" used in the refresh operation.
+ */
+ char *refresh_data;
+
+ /**
+ * Reference to a previous melt command.
+ */
+ const char *melt_reference;
+
+ /**
+ * Melt handle while operation is running.
+ */
+ struct TALER_EXCHANGE_RefreshMeltHandle *rmh;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Array of the denomination public keys
+ * corresponding to the @e fresh_amounts.
+ */
+ struct TALER_EXCHANGE_DenomPublicKey *fresh_pks;
+
+ /**
+ * Task scheduled to try later.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * How long do we wait until we retry?
+ */
+ struct GNUNET_TIME_Relative backoff;
+
+ /**
+ * Number of bytes in @e refresh_data.
+ */
+ size_t refresh_data_length;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * if set to #GNUNET_YES, then two /refresh/melt operations
+ * will be performed. This is needed to trigger the logic
+ * that manages those already-made requests. Note: it
+ * is not possible to just copy-and-paste a test refresh melt
+ * CMD to have the same effect, because every data preparation
+ * generates new planchets that (in turn) make the whole "hash"
+ * different from any previous one, therefore NOT allowing the
+ * exchange to pick any previous /rerfesh/melt operation from
+ * the database.
+ */
+ unsigned int double_melt;
+
+ /**
+ * Should we retry on (transient) failures?
+ */
+ int do_retry;
+
+ /**
+ * Set by the melt callback as it comes from the exchange.
+ */
+ uint16_t noreveal_index;
+};
+
+
+/**
+ * State for a "refresh reveal" CMD.
+ */
+struct RefreshRevealState
+{
+ /**
+ * Link to a "refresh melt" command.
+ */
+ const char *melt_reference;
+
+ /**
+ * Reveal handle while operation is running.
+ */
+ struct TALER_EXCHANGE_RefreshRevealHandle *rrh;
+
+ /**
+ * Convenience struct to keep in one place all the
+ * data related to one fresh coin, set by the reveal callback
+ * as it comes from the exchange.
+ */
+ struct FreshCoin *fresh_coins;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Task scheduled to try later.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * How long do we wait until we retry?
+ */
+ struct GNUNET_TIME_Relative backoff;
+
+ /**
+ * Number of fresh coins withdrawn, set by the
+ * reveal callback as it comes from the exchange,
+ * it is the length of the @e fresh_coins array.
+ */
+ unsigned int num_fresh_coins;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Should we retry on (transient) failures?
+ */
+ int do_retry;
+
+};
+
+
+/**
+ * State for a "refresh link" CMD.
+ */
+struct RefreshLinkState
+{
+ /**
+ * Link to a "refresh reveal" command.
+ */
+ const char *reveal_reference;
+
+ /**
+ * Handle to the ongoing operation.
+ */
+ struct TALER_EXCHANGE_RefreshLinkHandle *rlh;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Task scheduled to try later.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * How long do we wait until we retry?
+ */
+ struct GNUNET_TIME_Relative backoff;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Should we retry on (transient) failures?
+ */
+ int do_retry;
+
+};
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+refresh_reveal_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Task scheduled to re-try #refresh_reveal_run.
+ *
+ * @param cls a `struct RefreshRevealState`
+ */
+static void
+do_reveal_retry (void *cls)
+{
+ struct RefreshRevealState *rrs = cls;
+
+ rrs->retry_task = NULL;
+ refresh_reveal_run (rrs,
+ NULL,
+ rrs->is);
+}
+
+
+/**
+ * "refresh reveal" request callback; it checks that the response
+ * code is expected and copies into its command's state the data
+ * coming from the exchange, namely the fresh coins.
+ *
+ * @param cls closure.
+ * @param http_status HTTP response code.
+ * @param ec taler-specific error code.
+ * @param num_coins number of fresh coins created, length of the
+ * @a sigs and @a coin_privs arrays, 0 if the operation
+ * failed.
+ * @param coin_privs array of @a num_coins private keys for the
+ * coins that were created, NULL on error.
+ * @param sigs array of signature over @a num_coins coins,
+ * NULL on error.
+ * @param full_response raw exchange response.
+ */
+static void
+reveal_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendPrivateKeyP *coin_privs,
+ const struct TALER_DenominationSignature *sigs,
+ const json_t *full_response)
+{
+ struct RefreshRevealState *rrs = cls;
+ const struct TALER_TESTING_Command *melt_cmd;
+
+ rrs->rrh = NULL;
+ if (rrs->expected_response_code != http_status)
+ {
+ if (GNUNET_YES == rrs->do_retry)
+ {
+ if ( (0 == http_status) ||
+ (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Retrying refresh reveal failed with %u/%d\n",
+ http_status,
+ (int) ec);
+ /* on DB conflicts, do not use backoff */
+ if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ rrs->backoff = GNUNET_TIME_UNIT_ZERO;
+ else
+ rrs->backoff = EXCHANGE_LIB_BACKOFF (rrs->backoff);
+ rrs->retry_task = GNUNET_SCHEDULER_add_delayed (rrs->backoff,
+ &do_reveal_retry,
+ rrs);
+ return;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d to command %s in %s:%u\n",
+ http_status,
+ (int) ec,
+ rrs->is->commands[rrs->is->ip].label,
+ __FILE__,
+ __LINE__);
+ json_dumpf (full_response, stderr, 0);
+ TALER_TESTING_interpreter_fail (rrs->is);
+ return;
+ }
+ melt_cmd = TALER_TESTING_interpreter_lookup_command
+ (rrs->is, rrs->melt_reference);
+ if (NULL == melt_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rrs->is);
+ return;
+ }
+ rrs->num_fresh_coins = num_coins;
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ rrs->fresh_coins = GNUNET_new_array
+ (num_coins, struct FreshCoin);
+
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (melt_cmd,
+ 0,
+ &fresh_pks))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rrs->is);
+ return;
+ }
+
+ for (unsigned int i=0; i<num_coins; i++)
+ {
+ struct FreshCoin *fc = &rrs->fresh_coins[i];
+
+ fc->pk = &fresh_pks[i];
+ fc->coin_priv = coin_privs[i];
+ fc->sig.rsa_signature = GNUNET_CRYPTO_rsa_signature_dup
+ (sigs[i].rsa_signature);
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unknown HTTP status %d\n",
+ http_status);
+ }
+ TALER_TESTING_interpreter_next (rrs->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+refresh_reveal_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RefreshRevealState *rrs = cls;
+ struct RefreshMeltState *rms;
+ const struct TALER_TESTING_Command *melt_cmd;
+
+ rrs->is = is;
+ melt_cmd = TALER_TESTING_interpreter_lookup_command
+ (is, rrs->melt_reference);
+
+ if (NULL == melt_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rrs->is);
+ return;
+ }
+ rms = melt_cmd->cls;
+ rrs->rrh = TALER_EXCHANGE_refresh_reveal
+ (is->exchange,
+ rms->refresh_data_length,
+ rms->refresh_data,
+ rms->noreveal_index,
+ &reveal_cb, rrs);
+
+ if (NULL == rrs->rrh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state from a "refresh reveal" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+refresh_reveal_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RefreshRevealState *rrs = cls;
+
+ if (NULL != rrs->rrh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ rrs->is->ip,
+ cmd->label);
+
+ TALER_EXCHANGE_refresh_reveal_cancel (rrs->rrh);
+ rrs->rrh = NULL;
+ }
+ if (NULL != rrs->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (rrs->retry_task);
+ rrs->retry_task = NULL;
+ }
+
+ for (unsigned int j=0; j < rrs->num_fresh_coins; j++)
+ GNUNET_CRYPTO_rsa_signature_free (rrs->fresh_coins[j].sig.rsa_signature);
+
+ GNUNET_free_non_null (rrs->fresh_coins);
+ rrs->fresh_coins = NULL;
+ rrs->num_fresh_coins = 0;
+ GNUNET_free (rrs);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+refresh_link_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Task scheduled to re-try #refresh_link_run.
+ *
+ * @param cls a `struct RefreshLinkState`
+ */
+static void
+do_link_retry (void *cls)
+{
+ struct RefreshLinkState *rls = cls;
+
+ rls->retry_task = NULL;
+ refresh_link_run (rls,
+ NULL,
+ rls->is);
+}
+
+
+/**
+ * "refresh link" operation callback, checks that HTTP response
+ * code is expected _and_ that all the linked coins were actually
+ * withdrawn by the "refresh reveal" CMD.
+ *
+ * @param cls closure.
+ * @param http_status HTTP response code.
+ * @param ec taler-specific error code
+ * @param num_coins number of fresh coins created, length of the
+ * @a sigs and @a coin_privs arrays, 0 if the operation
+ * failed.
+ * @param coin_privs array of @a num_coins private keys for the
+ * coins that were created, NULL on error.
+ * @param sigs array of signature over @a num_coins coins, NULL on
+ * error.
+ * @param pubs array of public keys for the @a sigs,
+ * NULL on error.
+ * @param full_response raw response from the exchange.
+ */
+static void
+link_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendPrivateKeyP *coin_privs,
+ const struct TALER_DenominationSignature *sigs,
+ const struct TALER_DenominationPublicKey *pubs,
+ const json_t *full_response)
+{
+
+ struct RefreshLinkState *rls = cls;
+ const struct TALER_TESTING_Command *reveal_cmd;
+ struct TALER_TESTING_Command *link_cmd
+ = &rls->is->commands[rls->is->ip];
+ unsigned int found;
+ const unsigned int *num_fresh_coins;
+
+ rls->rlh = NULL;
+ if (rls->expected_response_code != http_status)
+ {
+ if (GNUNET_YES == rls->do_retry)
+ {
+ if ( (0 == http_status) ||
+ (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Retrying refresh link failed with %u/%d\n",
+ http_status,
+ (int) ec);
+ /* on DB conflicts, do not use backoff */
+ if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ rls->backoff = GNUNET_TIME_UNIT_ZERO;
+ else
+ rls->backoff = EXCHANGE_LIB_BACKOFF (rls->backoff);
+ rls->retry_task = GNUNET_SCHEDULER_add_delayed (rls->backoff,
+ &do_link_retry,
+ rls);
+ return;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d to command %s in %s:%u\n",
+ http_status,
+ (int) ec,
+ link_cmd->label,
+ __FILE__,
+ __LINE__);
+ json_dumpf (full_response, stderr, 0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+ reveal_cmd = TALER_TESTING_interpreter_lookup_command
+ (rls->is, rls->reveal_reference);
+
+ if (NULL == reveal_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ /* check that number of coins returned matches */
+ if (GNUNET_OK != TALER_TESTING_get_trait_uint
+ (reveal_cmd, 0, &num_fresh_coins))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+ if (num_coins != *num_fresh_coins)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected number of fresh coins: %d vs %d in %s:%u\n",
+ num_coins,
+ *num_fresh_coins,
+ __FILE__,
+ __LINE__);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+ /* check that the coins match */
+ for (unsigned int i=0;i<num_coins;i++)
+ for (unsigned int j=i+1;j<num_coins;j++)
+ if (0 == memcmp
+ (&coin_privs[i], &coin_privs[j],
+ sizeof (struct TALER_CoinSpendPrivateKeyP)))
+ GNUNET_break (0);
+ /* Note: coins might be legitimately permutated in here... */
+ found = 0;
+
+ /* Will point to the pointer inside the cmd state. */
+ const struct FreshCoin *fc = NULL;
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_fresh_coins
+ (reveal_cmd, 0, &fc))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+
+ for (unsigned int i=0;i<num_coins;i++)
+ for (unsigned int j=0;j<num_coins;j++)
+ {
+ if ( (0 == memcmp
+ (&coin_privs[i], &fc[j].coin_priv,
+ sizeof (struct TALER_CoinSpendPrivateKeyP))) &&
+ (0 == GNUNET_CRYPTO_rsa_signature_cmp
+ (fc[i].sig.rsa_signature,
+ sigs[j].rsa_signature)) &&
+ (0 == GNUNET_CRYPTO_rsa_public_key_cmp
+ (fc[i].pk->key.rsa_public_key,
+ pubs[j].rsa_public_key)) )
+ {
+ found++;
+ break;
+ }
+ }
+ if (found != num_coins)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Only %u/%u coins match expectations\n",
+ found, num_coins);
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unknown HTTP response code %u.\n",
+ http_status);
+ }
+ TALER_TESTING_interpreter_next (rls->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+refresh_link_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RefreshLinkState *rls = cls;
+ struct RefreshRevealState *rrs;
+ struct RefreshMeltState *rms;
+ const struct TALER_TESTING_Command *reveal_cmd;
+ const struct TALER_TESTING_Command *melt_cmd;
+ const struct TALER_TESTING_Command *coin_cmd;
+ rls->is = is;
+
+ reveal_cmd = TALER_TESTING_interpreter_lookup_command
+ (rls->is, rls->reveal_reference);
+
+ if (NULL == reveal_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+ rrs = reveal_cmd->cls;
+ melt_cmd = TALER_TESTING_interpreter_lookup_command
+ (rls->is, rrs->melt_reference);
+
+ if (NULL == melt_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+
+ /* find reserve_withdraw command */
+ {
+ const struct MeltDetails *md;
+
+ rms = melt_cmd->cls;
+ md = &rms->melted_coin;
+ coin_cmd = TALER_TESTING_interpreter_lookup_command
+ (rls->is, md->coin_reference);
+ if (NULL == coin_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+ }
+
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv
+ (coin_cmd, 0, &coin_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+
+ /* finally, use private key from withdraw sign command */
+ rls->rlh = TALER_EXCHANGE_refresh_link
+ (is->exchange, coin_priv, &link_cb, rls);
+
+ if (NULL == rls->rlh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of the "refresh link" CMD, and possibly
+ * cancel a operation thereof.
+ *
+ * @param cls closure
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+refresh_link_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RefreshLinkState *rls = cls;
+
+ if (NULL != rls->rlh)
+ {
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ rls->is->ip,
+ cmd->label);
+ TALER_EXCHANGE_refresh_link_cancel (rls->rlh);
+ rls->rlh = NULL;
+ }
+ if (NULL != rls->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (rls->retry_task);
+ rls->retry_task = NULL;
+ }
+ GNUNET_free (rls);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+refresh_melt_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Task scheduled to re-try #refresh_melt_run.
+ *
+ * @param cls a `struct RefreshMeltState`
+ */
+static void
+do_melt_retry (void *cls)
+{
+ struct RefreshMeltState *rms = cls;
+
+ rms->retry_task = NULL;
+ refresh_melt_run (rms,
+ NULL,
+ rms->is);
+}
+
+
+/**
+ * Callback for a "refresh melt" operation; checks if the HTTP
+ * response code is okay and re-run the melt operation if the
+ * CMD was set to do so.
+ *
+ * @param cls closure.
+ * @param http_status HTTP response code.
+ * @param ec taler-specific error code.
+ * @param noreveal_index choice by the exchange in the
+ * cut-and-choose protocol, UINT16_MAX on error.
+ * @param exchange_pub public key the exchange used for signing.
+ * @param full_response raw response body from the exchange.
+ */
+static void
+melt_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ uint32_t noreveal_index,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const json_t *full_response)
+{
+ struct RefreshMeltState *rms = cls;
+
+ rms->rmh = NULL;
+ if (rms->expected_response_code != http_status)
+ {
+ if (GNUNET_YES == rms->do_retry)
+ {
+ if ( (0 == http_status) ||
+ (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) ||
+ (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Retrying refresh melt failed with %u/%d\n",
+ http_status,
+ (int) ec);
+ /* on DB conflicts, do not use backoff */
+ if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec)
+ rms->backoff = GNUNET_TIME_UNIT_ZERO;
+ else
+ rms->backoff = EXCHANGE_LIB_BACKOFF (rms->backoff);
+ rms->retry_task = GNUNET_SCHEDULER_add_delayed (rms->backoff,
+ &do_melt_retry,
+ rms);
+ return;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d to command %s in %s:%u\n",
+ http_status,
+ (int) ec,
+ rms->is->commands[rms->is->ip].label,
+ __FILE__,
+ __LINE__);
+ json_dumpf (full_response, stderr, 0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+ rms->noreveal_index = noreveal_index;
+
+ if (GNUNET_YES == rms->double_melt)
+ {
+ TALER_LOG_DEBUG ("Doubling the melt (%s)\n",
+ rms->is->commands[rms->is->ip].label);
+ rms->rmh = TALER_EXCHANGE_refresh_melt
+ (rms->is->exchange, rms->refresh_data_length,
+ rms->refresh_data, &melt_cb, rms);
+ rms->double_melt = GNUNET_NO;
+ return;
+ }
+ TALER_TESTING_interpreter_next (rms->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+refresh_melt_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RefreshMeltState *rms = cls;
+ unsigned int num_fresh_coins;
+ const struct TALER_TESTING_Command *coin_command;
+ /* FIXME: this should be dynamic */
+ const char *melt_fresh_amounts[] = {
+ "EUR:1", "EUR:1", "EUR:1", "EUR:0.1",
+ NULL};
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk;
+
+ rms->is = is;
+ rms->noreveal_index = UINT16_MAX;
+ for (num_fresh_coins=0;
+ NULL != melt_fresh_amounts[num_fresh_coins];
+ num_fresh_coins++) ;
+
+ rms->fresh_pks = GNUNET_new_array
+ (num_fresh_coins,
+ struct TALER_EXCHANGE_DenomPublicKey);
+ {
+ const struct TALER_CoinSpendPrivateKeyP *melt_priv;
+ struct TALER_Amount melt_amount;
+ struct TALER_Amount fresh_amount;
+ const struct TALER_DenominationSignature *melt_sig;
+ const struct TALER_EXCHANGE_DenomPublicKey *melt_denom_pub;
+
+ const struct MeltDetails *md = &rms->melted_coin;
+ if (NULL == (coin_command
+ = TALER_TESTING_interpreter_lookup_command
+ (is, md->coin_reference)))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv
+ (coin_command, 0, &melt_priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TALER_string_to_amount (md->amount,
+ &melt_amount))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %u\n",
+ md->amount,
+ is->ip);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_sig (coin_command,
+ 0,
+ &melt_sig))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+ if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub
+ (coin_command, 0, &melt_denom_pub))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+
+ for (unsigned int i=0;i<num_fresh_coins;i++)
+ {
+ if (GNUNET_OK != TALER_string_to_amount
+ (melt_fresh_amounts[i], &fresh_amount))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at index %u\n",
+ melt_fresh_amounts[i], i);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+ fresh_pk = TALER_TESTING_find_pk
+ (TALER_EXCHANGE_get_keys (is->exchange), &fresh_amount);
+ if (NULL == fresh_pk)
+ {
+ GNUNET_break (0);
+ /* Subroutine logs specific error */
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+
+ rms->fresh_pks[i] = *fresh_pk;
+ }
+ rms->refresh_data = TALER_EXCHANGE_refresh_prepare
+ (melt_priv, &melt_amount, melt_sig, melt_denom_pub,
+ GNUNET_YES, num_fresh_coins, rms->fresh_pks,
+ &rms->refresh_data_length);
+
+ if (NULL == rms->refresh_data)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+ rms->rmh = TALER_EXCHANGE_refresh_melt
+ (is->exchange, rms->refresh_data_length,
+ rms->refresh_data, &melt_cb, rms);
+
+ if (NULL == rms->rmh)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+ }
+}
+
+
+/**
+ * Free the "refresh melt" CMD state, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, typically a #struct RefreshMeltState.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+refresh_melt_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RefreshMeltState *rms = cls;
+
+ if (NULL != rms->rmh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ rms->is->ip, rms->is->commands[rms->is->ip].label);
+ TALER_EXCHANGE_refresh_melt_cancel (rms->rmh);
+ rms->rmh = NULL;
+ }
+ if (NULL != rms->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (rms->retry_task);
+ rms->retry_task = NULL;
+ }
+ GNUNET_free_non_null (rms->fresh_pks);
+ rms->fresh_pks = NULL;
+ GNUNET_free_non_null (rms->refresh_data);
+ rms->refresh_data = NULL;
+ rms->refresh_data_length = 0;
+ GNUNET_free (rms);
+}
+
+
+/**
+ * Offer internal data to the "refresh melt" CMD.
+ *
+ * @param cls closure.
+ * @param ret[out] result (could be anything).
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ *
+ * @return #GNUNET_OK on success.
+ */
+static int
+refresh_melt_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct RefreshMeltState *rms = cls;
+
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_denom_pub (0, rms->fresh_pks),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Create a "refresh melt" command.
+ *
+ * @param label command label.
+ * @param amount amount to be melted.
+ * @param coin_reference reference to a command
+ * that will provide a coin to refresh.
+ * @param expected_response_code expected HTTP code.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_melt
+ (const char *label,
+ const char *amount,
+ const char *coin_reference,
+ unsigned int expected_response_code)
+{
+ struct RefreshMeltState *rms;
+ struct MeltDetails md;
+
+ md.coin_reference = coin_reference;
+ md.amount = amount;
+
+ rms = GNUNET_new (struct RefreshMeltState);
+ rms->melted_coin = md;
+ rms->expected_response_code = expected_response_code;
+
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .cls = rms,
+ .run = &refresh_melt_run,
+ .cleanup = &refresh_melt_cleanup,
+ .traits = &refresh_melt_traits
+ };
+
+ return cmd;
+}
+
+
+/**
+ * Create a "refresh melt" CMD that does TWO /refresh/melt
+ * requests. This was needed to test the replay of a valid melt
+ * request, see #5312.
+ *
+ * @param label command label
+ * @param exchange connection to the exchange
+ * @param amount amount to be melted.
+ * @param coin_reference reference to a command that will provide
+ * a coin to refresh
+ * @param expected_response_code expected HTTP code
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_melt_double
+ (const char *label,
+ const char *amount,
+ const char *coin_reference,
+ unsigned int expected_response_code)
+{
+ struct RefreshMeltState *rms;
+ struct MeltDetails md;
+
+ md.coin_reference = coin_reference;
+ md.amount = amount;
+
+ rms = GNUNET_new (struct RefreshMeltState);
+ rms->melted_coin = md;
+ rms->expected_response_code = expected_response_code;
+ rms->double_melt = GNUNET_YES;
+
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .cls = rms,
+ .run = &refresh_melt_run,
+ .cleanup = &refresh_melt_cleanup,
+ .traits = &refresh_melt_traits
+ };
+
+ return cmd;
+}
+
+
+/**
+ * Modify a "refresh melt" command to enable retries.
+ *
+ * @param cmd command
+ * @return modified command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_melt_with_retry (struct TALER_TESTING_Command cmd)
+{
+ struct RefreshMeltState *rms;
+
+ GNUNET_assert (&refresh_melt_run == cmd.run);
+ rms = cmd.cls;
+ rms->do_retry = GNUNET_YES;
+ return cmd;
+}
+
+
+/**
+ * Offer internal data from a "refresh reveal" CMD.
+ *
+ * @param cls closure.
+ * @param ret[out] result (could be anything).
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ *
+ * @return #GNUNET_OK on success.
+ */
+static int
+refresh_reveal_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct RefreshRevealState *rrs = cls;
+ unsigned int num_coins = rrs->num_fresh_coins;
+#define NUM_TRAITS ((num_coins * 3) + 3)
+ struct TALER_TESTING_Trait traits[NUM_TRAITS];
+
+ /* Making coin privs traits */
+ for (unsigned int i=0; i<num_coins; i++)
+ traits[i] = TALER_TESTING_make_trait_coin_priv
+ (i, &rrs->fresh_coins[i].coin_priv);
+
+ /* Making denom pubs traits */
+ for (unsigned int i=0; i<num_coins; i++)
+ traits[num_coins + i]
+ = TALER_TESTING_make_trait_denom_pub
+ (i, rrs->fresh_coins[i].pk);
+
+ /* Making denom sigs traits */
+ for (unsigned int i=0; i<num_coins; i++)
+ traits[(num_coins * 2) + i]
+ = TALER_TESTING_make_trait_denom_sig
+ (i, &rrs->fresh_coins[i].sig);
+
+ /* number of fresh coins */
+ traits[(num_coins * 3)] = TALER_TESTING_make_trait_uint
+ (0, &rrs->num_fresh_coins);
+
+ /* whole array of fresh coins */
+ traits[(num_coins * 3) + 1]
+ = TALER_TESTING_make_trait_fresh_coins (0, rrs->fresh_coins),
+
+ /* end of traits */
+ traits[(num_coins * 3) + 2] = TALER_TESTING_trait_end ();
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Create a "refresh reveal" command.
+ *
+ * @param label command label.
+ * @param exchange connection to the exchange.
+ * @param melt_reference reference to a "refresh melt" command.
+ * @param expected_response_code expected HTTP response code.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_reveal
+ (const char *label,
+ const char *melt_reference,
+ unsigned int expected_response_code)
+{
+ struct RefreshRevealState *rrs;
+
+ rrs = GNUNET_new (struct RefreshRevealState);
+ rrs->melt_reference = melt_reference;
+ rrs->expected_response_code = expected_response_code;
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = rrs,
+ .label = label,
+ .run = &refresh_reveal_run,
+ .cleanup = &refresh_reveal_cleanup,
+ .traits = &refresh_reveal_traits
+ };
+
+ return cmd;
+}
+
+
+/**
+ * Modify a "refresh reveal" command to enable retries.
+ *
+ * @param cmd command
+ * @return modified command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_reveal_with_retry (struct TALER_TESTING_Command cmd)
+{
+ struct RefreshRevealState *rrs;
+
+ GNUNET_assert (&refresh_reveal_run == cmd.run);
+ rrs = cmd.cls;
+ rrs->do_retry = GNUNET_YES;
+ return cmd;
+}
+
+
+/**
+ * Create a "refresh link" command.
+ *
+ * @param label command label.
+ * @param reveal_reference reference to a "refresh reveal" CMD.
+ * @param expected_response_code expected HTTP response code
+ *
+ * @return the "refresh link" command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_link
+ (const char *label,
+ const char *reveal_reference,
+ unsigned int expected_response_code)
+{
+ struct RefreshLinkState *rrs;
+
+ rrs = GNUNET_new (struct RefreshLinkState);
+ rrs->reveal_reference = reveal_reference;
+ rrs->expected_response_code = expected_response_code;
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = rrs,
+ .label = label,
+ .run = &refresh_link_run,
+ .cleanup = &refresh_link_cleanup
+ };
+
+ return cmd;
+}
+
+
+/**
+ * Modify a "ref