summaryrefslogtreecommitdiff
path: root/src/wire-plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/wire-plugins')
-rw-r--r--src/wire-plugins/Makefile.am101
-rw-r--r--src/wire-plugins/plugin_wire_ebics.c753
-rw-r--r--src/wire-plugins/plugin_wire_taler-bank.c1284
-rw-r--r--src/wire-plugins/plugin_wire_template.c345
-rw-r--r--src/wire-plugins/test_ebics_wireformat.c80
-rw-r--r--src/wire-plugins/test_wire_plugin.c186
-rw-r--r--src/wire-plugins/test_wire_plugin.conf25
-rw-r--r--src/wire-plugins/test_wire_plugin_transactions_taler-bank.c338
-rw-r--r--src/wire-plugins/test_wire_plugin_transactions_taler-bank.conf12
9 files changed, 3124 insertions, 0 deletions
diff --git a/src/wire-plugins/Makefile.am b/src/wire-plugins/Makefile.am
new file mode 100644
index 000000000..9f6029d7f
--- /dev/null
+++ b/src/wire-plugins/Makefile.am
@@ -0,0 +1,101 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+pkgcfgdir = $(prefix)/share/taler/config.d/
+
+EXTRA_DIST = \
+ test_wire_plugin.conf \
+ test_wire_plugin_transactions_taler-bank.conf \
+ test_wire_plugin_key.priv \
+ test_wire_plugin_test.json \
+ test_wire_plugin_sepa.json
+
+plugindir = $(libdir)/taler
+
+plugin_LTLIBRARIES = \
+ libtaler_plugin_wire_ebics.la \
+ libtaler_plugin_wire_taler_bank.la
+
+noinst_LTLIBRARIES = \
+ libtaler_plugin_wire_template.la
+
+
+libtaler_plugin_wire_taler_bank_la_SOURCES = \
+ plugin_wire_taler-bank.c
+libtaler_plugin_wire_taler_bank_la_LIBADD = \
+ $(LTLIBINTL)
+libtaler_plugin_wire_taler_bank_la_LDFLAGS = \
+ $(TALER_PLUGIN_LDFLAGS) \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetutil $(XLIB)
+
+
+libtaler_plugin_wire_ebics_la_SOURCES = \
+ plugin_wire_ebics.c
+libtaler_plugin_wire_ebics_la_LIBADD = \
+ $(LTLIBINTL)
+libtaler_plugin_wire_ebics_la_LDFLAGS = \
+ $(TALER_PLUGIN_LDFLAGS) \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetjson \
+ -lgnunetutil $(XLIB)
+
+
+libtaler_plugin_wire_template_la_SOURCES = \
+ plugin_wire_template.c
+libtaler_plugin_wire_template_la_LIBADD = \
+ $(LTLIBINTL)
+libtaler_plugin_wire_template_la_LDFLAGS = \
+ $(TALER_PLUGIN_LDFLAGS) \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil $(XLIB)
+
+
+AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
+
+TESTS = \
+ test_ebics_wireformat \
+ test_wire_plugin \
+ test_wire_plugin_transactions_taler_bank
+
+check_PROGRAMS= \
+ test_ebics_wireformat \
+ test_wire_plugin \
+ test_wire_plugin_transactions_taler_bank
+
+
+test_ebics_wireformat_SOURCES = \
+ test_ebics_wireformat.c
+test_ebics_wireformat_LDADD = \
+ -lgnunetutil \
+ $(top_builddir)/src/wire/libtalerwire.la \
+ $(top_builddir)/src/util/libtalerutil.la
+
+
+test_wire_plugin_SOURCES = \
+ test_wire_plugin.c
+test_wire_plugin_LDADD = \
+ -lgnunetutil \
+ $(top_builddir)/src/wire/libtalerwire.la \
+ $(top_builddir)/src/util/libtalerutil.la
+
+
+test_wire_plugin_transactions_taler_bank_SOURCES = \
+ test_wire_plugin_transactions_taler-bank.c
+test_wire_plugin_transactions_taler_bank_LDADD = \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ $(top_builddir)/src/wire/libtalerwire.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/util/libtalerutil.la
diff --git a/src/wire-plugins/plugin_wire_ebics.c b/src/wire-plugins/plugin_wire_ebics.c
new file mode 100644
index 000000000..9aad9df08
--- /dev/null
+++ b/src/wire-plugins/plugin_wire_ebics.c
@@ -0,0 +1,753 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2016, 2017, 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 plugin_wire_ebics.c
+ * @brief wire plugin for transfers using SEPA/EBICS
+ * @author Florian Dold
+ * @author Christian Grothoff
+ * @author Sree Harsha Totakura
+ */
+#include "platform.h"
+#include "taler_wire_plugin.h"
+#include "taler_signatures.h"
+#include <gnunet/gnunet_json_lib.h>
+
+
+/**
+ * Type of the "cls" argument given to each of the functions in
+ * our API.
+ */
+struct EbicsClosure
+{
+
+ /**
+ * Which currency do we support?
+ */
+ char *currency;
+
+ /**
+ * Configuration we use to lookup account information.
+ */
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+};
+
+
+/**
+ * Round amount DOWN to the amount that can be transferred via the wire
+ * method. For example, Taler may support 0.000001 EUR as a unit of
+ * payment, but SEPA only supports 0.01 EUR. This function would
+ * round 0.125 EUR to 0.12 EUR in this case.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param[in,out] amount amount to round down
+ * @return #GNUNET_OK on success, #GNUNET_NO if rounding was unnecessary,
+ * #GNUNET_SYSERR if the amount or currency was invalid
+ */
+static int
+ebics_amount_round (void *cls,
+ struct TALER_Amount *amount)
+{
+ struct EbicsClosure *sc = cls;
+ uint32_t delta;
+
+ if (NULL == sc->currency)
+ return GNUNET_SYSERR;
+ if (0 != strcasecmp (amount->currency,
+ sc->currency))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ delta = amount->fraction % (TALER_AMOUNT_FRAC_BASE / 100);
+ if (0 == delta)
+ return GNUNET_NO;
+ amount->fraction -= delta;
+ return GNUNET_OK;
+}
+
+
+/* Taken from GNU gettext */
+
+/**
+ * Entry in the country table.
+ */
+struct CountryTableEntry
+{
+ /**
+ * 2-Character international country code.
+ */
+ const char *code;
+
+ /**
+ * Long English name of the country.
+ */
+ const char *english;
+};
+
+
+/* Keep the following table in sync with gettext.
+ WARNING: the entries should stay sorted according to the code */
+/**
+ * List of country codes.
+ */
+static const struct CountryTableEntry country_table[] =
+ {
+ { "AE", "U.A.E." },
+ { "AF", "Afghanistan" },
+ { "AL", "Albania" },
+ { "AM", "Armenia" },
+ { "AN", "Netherlands Antilles" },
+ { "AR", "Argentina" },
+ { "AT", "Austria" },
+ { "AU", "Australia" },
+ { "AZ", "Azerbaijan" },
+ { "BA", "Bosnia and Herzegovina" },
+ { "BD", "Bangladesh" },
+ { "BE", "Belgium" },
+ { "BG", "Bulgaria" },
+ { "BH", "Bahrain" },
+ { "BN", "Brunei Darussalam" },
+ { "BO", "Bolivia" },
+ { "BR", "Brazil" },
+ { "BT", "Bhutan" },
+ { "BY", "Belarus" },
+ { "BZ", "Belize" },
+ { "CA", "Canada" },
+ { "CG", "Congo" },
+ { "CH", "Switzerland" },
+ { "CI", "Cote d'Ivoire" },
+ { "CL", "Chile" },
+ { "CM", "Cameroon" },
+ { "CN", "People's Republic of China" },
+ { "CO", "Colombia" },
+ { "CR", "Costa Rica" },
+ { "CS", "Serbia and Montenegro" },
+ { "CZ", "Czech Republic" },
+ { "DE", "Germany" },
+ { "DK", "Denmark" },
+ { "DO", "Dominican Republic" },
+ { "DZ", "Algeria" },
+ { "EC", "Ecuador" },
+ { "EE", "Estonia" },
+ { "EG", "Egypt" },
+ { "ER", "Eritrea" },
+ { "ES", "Spain" },
+ { "ET", "Ethiopia" },
+ { "FI", "Finland" },
+ { "FO", "Faroe Islands" },
+ { "FR", "France" },
+ { "GB", "United Kingdom" },
+ { "GD", "Caribbean" },
+ { "GE", "Georgia" },
+ { "GL", "Greenland" },
+ { "GR", "Greece" },
+ { "GT", "Guatemala" },
+ { "HK", "Hong Kong" },
+ { "HK", "Hong Kong S.A.R." },
+ { "HN", "Honduras" },
+ { "HR", "Croatia" },
+ { "HT", "Haiti" },
+ { "HU", "Hungary" },
+ { "ID", "Indonesia" },
+ { "IE", "Ireland" },
+ { "IL", "Israel" },
+ { "IN", "India" },
+ { "IQ", "Iraq" },
+ { "IR", "Iran" },
+ { "IS", "Iceland" },
+ { "IT", "Italy" },
+ { "JM", "Jamaica" },
+ { "JO", "Jordan" },
+ { "JP", "Japan" },
+ { "KE", "Kenya" },
+ { "KG", "Kyrgyzstan" },
+ { "KH", "Cambodia" },
+ { "KR", "South Korea" },
+ { "KW", "Kuwait" },
+ { "KZ", "Kazakhstan" },
+ { "LA", "Laos" },
+ { "LB", "Lebanon" },
+ { "LI", "Liechtenstein" },
+ { "LK", "Sri Lanka" },
+ { "LT", "Lithuania" },
+ { "LU", "Luxembourg" },
+ { "LV", "Latvia" },
+ { "LY", "Libya" },
+ { "MA", "Morocco" },
+ { "MC", "Principality of Monaco" },
+ { "MD", "Moldava" },
+ { "MD", "Moldova" },
+ { "ME", "Montenegro" },
+ { "MK", "Former Yugoslav Republic of Macedonia" },
+ { "ML", "Mali" },
+ { "MM", "Myanmar" },
+ { "MN", "Mongolia" },
+ { "MO", "Macau S.A.R." },
+ { "MT", "Malta" },
+ { "MV", "Maldives" },
+ { "MX", "Mexico" },
+ { "MY", "Malaysia" },
+ { "NG", "Nigeria" },
+ { "NI", "Nicaragua" },
+ { "NL", "Netherlands" },
+ { "NO", "Norway" },
+ { "NP", "Nepal" },
+ { "NZ", "New Zealand" },
+ { "OM", "Oman" },
+ { "PA", "Panama" },
+ { "PE", "Peru" },
+ { "PH", "Philippines" },
+ { "PK", "Islamic Republic of Pakistan" },
+ { "PL", "Poland" },
+ { "PR", "Puerto Rico" },
+ { "PT", "Portugal" },
+ { "PY", "Paraguay" },
+ { "QA", "Qatar" },
+ { "RE", "Reunion" },
+ { "RO", "Romania" },
+ { "RS", "Serbia" },
+ { "RU", "Russia" },
+ { "RW", "Rwanda" },
+ { "SA", "Saudi Arabia" },
+ { "SE", "Sweden" },
+ { "SG", "Singapore" },
+ { "SI", "Slovenia" },
+ { "SK", "Slovak" },
+ { "SN", "Senegal" },
+ { "SO", "Somalia" },
+ { "SR", "Suriname" },
+ { "SV", "El Salvador" },
+ { "SY", "Syria" },
+ { "TH", "Thailand" },
+ { "TJ", "Tajikistan" },
+ { "TM", "Turkmenistan" },
+ { "TN", "Tunisia" },
+ { "TR", "Turkey" },
+ { "TT", "Trinidad and Tobago" },
+ { "TW", "Taiwan" },
+ { "TZ", "Tanzania" },
+ { "UA", "Ukraine" },
+ { "US", "United States" },
+ { "UY", "Uruguay" },
+ { "VA", "Vatican" },
+ { "VE", "Venezuela" },
+ { "VN", "Viet Nam" },
+ { "YE", "Yemen" },
+ { "ZA", "South Africa" },
+ { "ZW", "Zimbabwe" }
+ };
+
+
+/**
+ * Country code comparator function, for binary search with bsearch().
+ *
+ * @param ptr1 pointer to a `struct table_entry`
+ * @param ptr2 pointer to a `struct table_entry`
+ * @return result of strncmp()'ing the 2-digit country codes of the entries
+ */
+static int
+cmp_country_code (const void *ptr1,
+ const void *ptr2)
+{
+ const struct CountryTableEntry *cc1 = ptr1;
+ const struct CountryTableEntry *cc2 = ptr2;
+
+ return strncmp (cc1->code,
+ cc2->code,
+ 2);
+}
+
+
+/**
+ * Validates given IBAN according to the European Banking Standards. See:
+ * http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf
+ *
+ * @param iban the IBAN number to validate
+ * @return #GNUNET_YES if correctly formatted; #GNUNET_NO if not
+ */
+static int
+validate_iban (const char *iban)
+{
+ char cc[2];
+ char ibancpy[35];
+ struct CountryTableEntry cc_entry;
+ unsigned int len;
+ char *nbuf;
+ unsigned long long dividend;
+ unsigned long long remainder;
+ int nread;
+ int ret;
+ unsigned int i;
+ unsigned int j;
+
+ len = strlen (iban);
+ if (len > 34)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "IBAN number too long to be valid\n");
+ return GNUNET_NO;
+ }
+ strncpy (cc, iban, 2);
+ strncpy (ibancpy, iban + 4, len - 4);
+ strncpy (ibancpy + len - 4, iban, 4);
+ ibancpy[len] = '\0';
+ cc_entry.code = cc;
+ cc_entry.english = NULL;
+ if (NULL ==
+ bsearch (&cc_entry,
+ country_table,
+ sizeof (country_table) / sizeof (struct CountryTableEntry),
+ sizeof (struct CountryTableEntry),
+ &cmp_country_code))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Country code `%c%c' not supported\n",
+ cc[0],
+ cc[1]);
+ return GNUNET_NO;
+ }
+ nbuf = GNUNET_malloc ((len * 2) + 1);
+ for (i=0, j=0; i < len; i++)
+ {
+ if (isalpha ((unsigned char) ibancpy[i]))
+ {
+ if (2 != snprintf(&nbuf[j],
+ 3,
+ "%2u",
+ (ibancpy[i] - 'A' + 10)))
+ {
+ GNUNET_free (nbuf);
+ return GNUNET_NO;
+ }
+ j += 2;
+ continue;
+ }
+ nbuf[j] = ibancpy[i];
+ j++;
+ }
+ for (j=0;'\0' != nbuf[j];j++)
+ GNUNET_assert (isdigit( (unsigned char) nbuf[j]));
+ GNUNET_assert (sizeof(dividend) >= 8);
+ remainder = 0;
+ for (i=0; i<j; i+=16)
+ {
+ if (1 !=
+ (ret = sscanf (&nbuf[i],
+ "%16llu %n",
+ &dividend,
+ &nread)))
+ {
+ GNUNET_free (nbuf);
+ GNUNET_break_op (0);
+ return GNUNET_NO;
+ }
+ if (0 != remainder)
+ dividend += remainder * (pow (10, nread));
+ remainder = dividend % 97;
+ }
+ GNUNET_free (nbuf);
+ if (1 == remainder)
+ return GNUNET_YES;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "IBAN checksum wrong\n");
+ return GNUNET_NO;
+}
+
+
+/**
+ * Information about an account extracted from a payto://-URL.
+ */
+struct Account
+{
+ /**
+ * The IBAN number.
+ */
+ char *iban;
+
+};
+
+
+/**
+ * Parse payto:// account URL (only account information,
+ * wire subject and amount are ignored).
+ *
+ * @param account_url URL to parse
+ * @param account[out] set to information, can be NULL
+ * @return #TALER_EC_NONE if @a account_url is well-formed
+ */
+static enum TALER_ErrorCode
+parse_payto (const char *account_url,
+ struct Account *account)
+{
+ const char *iban;
+ const char *q;
+ char *result;
+
+#define PREFIX "payto://sepa/"
+ if (0 != strncasecmp (account_url,
+ PREFIX,
+ strlen (PREFIX)))
+ return TALER_EC_PAYTO_WRONG_METHOD;
+ iban = &account_url[strlen (PREFIX)];
+ q = strchr (iban,
+ '?');
+ if (NULL != q)
+ {
+ result = GNUNET_strndup (iban,
+ q - iban);
+ }
+ else
+ {
+ result = GNUNET_strdup (iban);
+ }
+ if (GNUNET_OK !=
+ validate_iban (result))
+ {
+ GNUNET_free (result);
+ return TALER_EC_PAYTO_MALFORMED;
+ }
+ if (NULL != account)
+ {
+ account->iban = result;
+ }
+ else
+ {
+ GNUNET_free (result);
+ }
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Check if the given payto:// URL is correctly formatted for this plugin
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param account_url the payto:// URL
+ * @return #TALER_EC_NONE if correctly formatted
+ */
+static enum TALER_ErrorCode
+ebics_wire_validate (void *cls,
+ const char *account_url)
+{
+ return parse_payto (account_url,
+ NULL);
+}
+
+
+/**
+ * Prepare for exeuction of a wire transfer.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param wire valid wire account information
+ * @param amount amount to transfer, already rounded
+ * @param exchange_base_url base URL of the exchange (for tracking)
+ * @param wtid wire transfer identifier to use
+ * @param psc function to call with the prepared data to persist
+ * @param psc_cls closure for @a psc
+ * @return NULL on failure
+ */
+static struct TALER_WIRE_PrepareHandle *
+ebics_prepare_wire_transfer (void *cls,
+ const char *origin_account_section,
+ const char *destination_account_url,
+ const struct TALER_Amount *amount,
+ const char *exchange_base_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_WIRE_PrepareTransactionCallback psc,
+ void *psc_cls)
+{
+ GNUNET_break (0); // FIXME: not implemented
+ return NULL;
+}
+
+
+/**
+ * Abort preparation of a wire transfer. For example,
+ * because we are shutting down.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pth preparation to cancel
+ */
+static void
+ebics_prepare_wire_transfer_cancel (void *cls,
+ struct TALER_WIRE_PrepareHandle *pth)
+{
+ GNUNET_break (0); // FIXME: not implemented
+}
+
+
+/**
+ * Execute a wire transfer.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param buf buffer with the prepared execution details
+ * @param buf_size number of bytes in @a buf
+ * @param cc function to call upon success
+ * @param cc_cls closure for @a cc
+ * @return NULL on error
+ */
+static struct TALER_WIRE_ExecuteHandle *
+ebics_execute_wire_transfer (void *cls,
+ const char *buf,
+ size_t buf_size,
+ TALER_WIRE_ConfirmationCallback cc,
+ void *cc_cls)
+{
+ GNUNET_break (0); // FIXME: not implemented
+ return NULL;
+}
+
+
+/**
+ * Abort execution of a wire transfer. For example, because we are
+ * shutting down. Note that if an execution is aborted, it may or
+ * may not still succeed. The caller MUST run @e
+ * execute_wire_transfer again for the same request as soon as
+ * possilbe, to ensure that the request either ultimately succeeds
+ * or ultimately fails. Until this has been done, the transaction is
+ * in limbo (i.e. may or may not have been committed).
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param eh execution to cancel
+ */
+static void
+ebics_execute_wire_transfer_cancel (void *cls,
+ struct TALER_WIRE_ExecuteHandle *eh)
+{
+ GNUNET_break (0); // FIXME: not implemented
+}
+
+
+/**
+ * Query transfer history of an account. We use the variable-size
+ * @a start_off to indicate which transfers we are interested in as
+ * different banking systems may have different ways to identify
+ * transfers. The @a start_off value must thus match the value of
+ * a `row_off` argument previously given to the @a hres_cb. Use
+ * NULL to query transfers from the beginning of time (with
+ * positive @a num_results) or from the latest committed transfers
+ * (with negative @a num_results).
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param account_section specifies the configuration section which
+ * identifies the account for which we should get the history
+ * @param direction what kinds of wire transfers should be returned
+ * @param start_off from which row on do we want to get results, use NULL for the latest; exclusive
+ * @param start_off_len number of bytes in @a start_off; must be `sizeof(uint64_t)`.
+ * @param num_results how many results do we want; negative numbers to go into the past,
+ * positive numbers to go into the future starting at @a start_row;
+ * must not be zero.
+ * @param hres_cb the callback to call with the transaction history
+ * @param hres_cb_cls closure for the above callback
+ */
+static struct TALER_WIRE_HistoryHandle *
+ebics_get_history (void *cls,
+ const char *account_section,
+ enum TALER_BANK_Direction direction,
+ const void *start_off,
+ size_t start_off_len,
+ int64_t num_results,
+ TALER_WIRE_HistoryResultCallback hres_cb,
+ void *hres_cb_cls)
+{
+ GNUNET_break (0);
+ return NULL;
+}
+
+
+/**
+ * Cancel going over the account's history.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param whh operation to cancel
+ */
+static void
+ebics_get_history_cancel (void *cls,
+ struct TALER_WIRE_HistoryHandle *whh)
+{
+ GNUNET_break (0);
+}
+
+
+
+/**
+ * Context for a rejection operation.
+ */
+struct TALER_WIRE_RejectHandle
+{
+ /**
+ * Function to call with the result.
+ */
+ TALER_WIRE_RejectTransferCallback rej_cb;
+
+ /**
+ * Closure for @e rej_cb.
+ */
+ void *rej_cb_cls;
+
+ /**
+ * Handle to task for timeout of operation.
+ */
+ struct GNUNET_SCHEDULER_Task *timeout_task;
+};
+
+
+/**
+ * Rejection operation failed with timeout, notify callback
+ * and clean up.
+ *
+ * @param cls closure with `struct TALER_WIRE_RejectHandle`
+ */
+static void
+timeout_reject (void *cls)
+{
+ struct TALER_WIRE_RejectHandle *rh = cls;
+
+ rh->timeout_task = NULL;
+ rh->rej_cb (rh->rej_cb_cls,
+ TALER_EC_NOT_IMPLEMENTED /* in the future: TALER_EC_TIMEOUT */);
+ GNUNET_free (rh);
+}
+
+
+/**
+ * Reject an incoming wire transfer that was obtained from the
+ * history. This function can be used to transfer funds back to
+ * the sender if the WTID was malformed (i.e. due to a typo).
+ *
+ * Calling `reject_transfer` twice on the same wire transfer should
+ * be idempotent, i.e. not cause the funds to be wired back twice.
+ * Furthermore, the transfer should henceforth be removed from the
+ * results returned by @e get_history.
+ *
+ * @param cls plugin's closure
+ * @param account_section specifies the configuration section which
+ * identifies the account to use to reject the transfer
+ * @param start_off offset of the wire transfer in plugin-specific format
+ * @param start_off_len number of bytes in @a start_off
+ * @param rej_cb function to call with the result of the operation
+ * @param rej_cb_cls closure for @a rej_cb
+ * @return handle to cancel the operation
+ */
+static struct TALER_WIRE_RejectHandle *
+ebics_reject_transfer (void *cls,
+ const char *account_section,
+ const void *start_off,
+ size_t start_off_len,
+ TALER_WIRE_RejectTransferCallback rej_cb,
+ void *rej_cb_cls)
+{
+ struct TALER_WIRE_RejectHandle *rh;
+
+ GNUNET_break (0); /* not implemented, just a stub! */
+ rh = GNUNET_new (struct TALER_WIRE_RejectHandle);
+ rh->rej_cb = rej_cb;
+ rh->rej_cb_cls = rej_cb_cls;
+ rh->timeout_task = GNUNET_SCHEDULER_add_now (&timeout_reject,
+ rh);
+ return rh;
+}
+
+
+/**
+ * Cancel ongoing reject operation. Note that the rejection may still
+ * proceed. Basically, if this function is called, the rejection may
+ * have happened or not. This function is usually used during shutdown
+ * or system upgrades. At a later point, the application must call
+ * @e reject_transfer again for this wire transfer, unless the
+ * @e get_history shows that the wire transfer no longer exists.
+ *
+ * @param cls plugins' closure
+ * @param rh operation to cancel
+ * @return closure of the callback of the operation
+ */
+static void *
+ebics_reject_transfer_cancel (void *cls,
+ struct TALER_WIRE_RejectHandle *rh)
+{
+ void *ret = rh->rej_cb_cls;
+
+ GNUNET_SCHEDULER_cancel (rh->timeout_task);
+ GNUNET_free (rh);
+ return ret;
+}
+
+
+/**
+ * Initialize ebics-wire subsystem.
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_WIRE_Plugin`
+ */
+void *
+libtaler_plugin_wire_ebics_init (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct EbicsClosure *sc;
+ struct TALER_WIRE_Plugin *plugin;
+
+ sc = GNUNET_new (struct EbicsClosure);
+ sc->cfg = cfg;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "taler",
+ "CURRENCY",
+ &sc->currency))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler",
+ "CURRENCY");
+ GNUNET_free (sc);
+ return NULL;
+ }
+ plugin = GNUNET_new (struct TALER_WIRE_Plugin);
+ plugin->cls = sc;
+ plugin->method = "sepa";
+ plugin->amount_round = &ebics_amount_round;
+ plugin->wire_validate = &ebics_wire_validate;
+ plugin->prepare_wire_transfer = &ebics_prepare_wire_transfer;
+ plugin->prepare_wire_transfer_cancel = &ebics_prepare_wire_transfer_cancel;
+ plugin->execute_wire_transfer = &ebics_execute_wire_transfer;
+ plugin->execute_wire_transfer_cancel = &ebics_execute_wire_transfer_cancel;
+ plugin->get_history = &ebics_get_history;
+ plugin->get_history_cancel = &ebics_get_history_cancel;
+ plugin->reject_transfer = &ebics_reject_transfer;
+ plugin->reject_transfer_cancel = &ebics_reject_transfer_cancel;
+ return plugin;
+}
+
+
+/**
+ * Shutdown Ebics wire subsystem.
+ *
+ * @param cls a `struct TALER_WIRE_Plugin`
+ * @return NULL (always)
+ */
+void *
+libtaler_plugin_wire_ebics_done (void *cls)
+{
+ struct TALER_WIRE_Plugin *plugin = cls;
+ struct EbicsClosure *sc = plugin->cls;
+
+ GNUNET_free_non_null (sc->currency);
+ GNUNET_free (sc);
+ GNUNET_free (plugin);
+ return NULL;
+}
+
+/* end of plugin_wire_ebics.c */
diff --git a/src/wire-plugins/plugin_wire_taler-bank.c b/src/wire-plugins/plugin_wire_taler-bank.c
new file mode 100644
index 000000000..2d2235343
--- /dev/null
+++ b/src/wire-plugins/plugin_wire_taler-bank.c
@@ -0,0 +1,1284 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017, 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 plugin_wire_taler_bank.c
+ * @brief plugin for the "x-taler-bank" wire method
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_wire_plugin.h"
+#include "taler_json_lib.h"
+#include "taler_bank_service.h"
+#include "taler_signatures.h"
+#include <gnunet/gnunet_curl_lib.h>
+
+/* only for HTTP status codes */
+#include <microhttpd.h>
+
+/**
+ * Maximum legal 'value' for an account number, based on IEEE double (for JavaScript compatibility).
+ */
+#define MAX_ACCOUNT_NO (1LLU << 52)
+
+/**
+ * Type of the "cls" argument given to each of the functions in
+ * our API.
+ */
+struct TalerBankClosure
+{
+
+ /**
+ * Which currency do we support?
+ */
+ char *currency;
+
+ /**
+ * Handle to the context for sending funds to the bank.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Scheduler context for running the @e ctx.
+ */
+ struct GNUNET_CURL_RescheduleContext *rc;
+
+ /**
+ * Configuration we use to lookup account information.
+ */
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+};
+
+
+/**
+ * Handle returned by #taler_bank_prepare_wire_transfer.
+ */
+struct TALER_WIRE_PrepareHandle
+{
+
+ /**
+ * Task we use for async execution.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * TalerBank closure we run in.
+ */
+ struct TalerBankClosure *tc;
+
+ /**
+ * Authentication information.
+ */
+ struct TALER_BANK_AuthenticationData auth;
+
+ /**
+ * Which account should be debited? Given as the respective
+ * section in the configuration file.
+ */
+ char *origin_account_url;
+
+ /**
+ * Which account should be credited?
+ */
+ char *destination_account_url;
+
+ /**
+ * Base URL to use for the exchange.
+ */
+ char *exchange_base_url;
+
+ /**
+ * Function to call with the serialized data.
+ */
+ TALER_WIRE_PrepareTransactionCallback ptc;
+
+ /**
+ * Closure for @e ptc.
+ */
+ void *ptc_cls;
+
+ /**
+ * Amount to transfer.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Subject of the wire transfer.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+
+};
+
+
+/**
+ * Handle returned by #taler_bank_execute_wire_transfer.
+ */
+struct TALER_WIRE_ExecuteHandle
+{
+
+ /**
+ * Handle to the HTTP request to the bank.
+ */
+ struct TALER_BANK_AdminAddIncomingHandle *aaih;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_WIRE_ConfirmationCallback cc;
+
+ /**
+ * Closure for @e cc.
+ */
+ void *cc_cls;
+};
+
+
+/**
+ * Round amount DOWN to the amount that can be transferred via the wire
+ * method. For example, Taler may support 0.000001 EUR as a unit of
+ * payment, but SEPA only supports 0.01 EUR. This function would
+ * round 0.125 EUR to 0.12 EUR in this case.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param[in,out] amount amount to round down
+ * @return #GNUNET_OK on success, #GNUNET_NO if rounding was unnecessary,
+ * #GNUNET_SYSERR if the amount or currency was invalid
+ */
+static int
+taler_bank_amount_round (void *cls,
+ struct TALER_Amount *amount)
+{
+ struct TalerBankClosure *tc = cls;
+ uint32_t delta;
+
+ if (NULL == tc->currency)
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler",
+ "CURRENCY");
+ return GNUNET_SYSERR; /* not configured with currency */
+ }
+ if (0 != strcasecmp (amount->currency,
+ tc->currency))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* 'taler_bank' method supports 1/100 of the unit currency, i.e. 0.01 CUR */
+ delta = amount->fraction % (TALER_AMOUNT_FRAC_BASE / 100);
+ if (0 == delta)
+ return GNUNET_NO;
+ amount->fraction -= delta;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Information about an account extracted from a payto://-URL.
+ */
+struct Account
+{
+ /**
+ * Hostname of the bank (possibly including port).
+ */
+ char *hostname;
+
+ /**
+ * Bank account number.
+ */
+ unsigned long long no;
+};
+
+
+/**
+ * Parse payto:// account URL (only account information,
+ * wire subject and amount are ignored).
+ *
+ * @param account_url URL to parse
+ * @param account[out] set to information, can be NULL
+ * @return #TALER_EC_NONE if @a account_url is well-formed
+ */
+static enum TALER_ErrorCode
+parse_payto (const char *account_url,
+ struct Account *r_account)
+{
+ const char *hostname;
+ const char *account;
+ const char *q;
+ unsigned long long no;
+
+#define PREFIX "payto://x-taler-bank/"
+ if (0 != strncasecmp (account_url,
+ PREFIX,
+ strlen (PREFIX)))
+ return TALER_EC_PAYTO_WRONG_METHOD;
+ hostname = &account_url[strlen (PREFIX)];
+ if (NULL == (account = strchr (hostname,
+ (unsigned char) '/')))
+ return TALER_EC_PAYTO_MALFORMED;
+ account++;
+ if (NULL != (q = strchr (account,
+ (unsigned char) '?')))
+ {
+ char *s;
+
+ s = GNUNET_strndup (account,
+ q - account);
+ if (1 != sscanf (s,
+ "%llu",
+ &no))
+ {
+ GNUNET_free (s);
+ return TALER_EC_PAYTO_MALFORMED;
+ }
+ GNUNET_free (s);
+ }
+ else
+ {
+ if (1 != sscanf (account,
+ "%llu",
+ &no))
+ return TALER_EC_PAYTO_MALFORMED;
+ }
+ if (no > MAX_ACCOUNT_NO)
+ return TALER_EC_PAYTO_MALFORMED;
+ if (NULL != r_account)
+ {
+ r_account->hostname = GNUNET_strndup (hostname,
+ account - hostname);
+ r_account->no = no;
+ }
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Check if the given payto:// URL is correctly formatted.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param account_url an account URL
+ * @return #TALER_EC_NONE if correctly formatted
+ */
+static enum TALER_ErrorCode
+taler_bank_wire_validate (void *cls,
+ const char *account_url)
+{
+ (void) cls;
+
+ return parse_payto (account_url,
+ NULL);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+/**
+ * Format we used for serialized transaction data.
+ */
+struct BufFormatP
+{
+
+ /**
+ * The wire transfer identifier.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * The amount.
+ */
+ struct TALER_AmountNBO amount;
+
+ /* followed by 0-terminated origin account URL */
+
+ /* followed by 0-terminated destination account URL */
+
+ /* followed by 0-terminated exchange base URL */
+
+ /* optionally followed by 0-terminated origin username URL */
+
+ /* optionally followed by 0-terminated origin password URL */
+
+};
+GNUNET_NETWORK_STRUCT_END
+
+
+/**
+ * Abort preparation of a wire transfer. For example,
+ * because we are shutting down.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pth preparation to cancel
+ */
+static void
+taler_bank_prepare_wire_transfer_cancel (void *cls,
+ struct TALER_WIRE_PrepareHandle *pth)
+{
+ if (NULL != pth->task)
+ GNUNET_SCHEDULER_cancel (pth->task);
+ TALER_BANK_auth_free (&pth->auth);
+ GNUNET_free (pth->origin_account_url);
+ GNUNET_free (pth->destination_account_url);
+ GNUNET_free (pth->exchange_base_url);
+ GNUNET_free (pth);
+}
+
+
+/**
+ * Prepare for exeuction of a wire transfer. Calls the
+ * callback with the serialized state.
+ *
+ * @param cls the `struct TALER_WIRE_PrepareHandle`
+ */
+static void
+do_prepare (void *cls)
+{
+ struct TALER_WIRE_PrepareHandle *pth = cls;
+ size_t len_i;
+ size_t len_o;
+ size_t len_au;
+ size_t len_ap;
+ size_t len_b;
+ struct BufFormatP bf;
+
+ pth->task = NULL;
+ /* serialize the state into a 'buf' */
+ len_o = strlen (pth->origin_account_url) + 1;
+ len_i = strlen (pth->destination_account_url) + 1;
+ len_b = strlen (pth->exchange_base_url) + 1;
+ switch (pth->auth.method)
+ {
+ case TALER_BANK_AUTH_NONE:
+ len_au = 0;
+ len_ap = 0;
+ break;
+ case TALER_BANK_AUTH_BASIC:
+ len_au = strlen (pth->auth.details.basic.username) + 1;
+ len_ap = strlen (pth->auth.details.basic.password) + 1;
+ break;
+ }
+ bf.wtid = pth->wtid;
+ TALER_amount_hton (&bf.amount,
+ &pth->amount);
+ {
+ char buf[sizeof (struct BufFormatP) + len_o + len_i + len_b + len_au + len_ap];
+
+ memcpy (buf,
+ &bf,
+ sizeof (struct BufFormatP));
+ memcpy (&buf[sizeof (struct BufFormatP)],
+ pth->origin_account_url,
+ len_o);
+ memcpy (&buf[sizeof (struct BufFormatP) + len_o],
+ pth->destination_account_url,
+ len_i);
+ memcpy (&buf[sizeof (struct BufFormatP) + len_o + len_i],
+ pth->exchange_base_url,
+ len_b);
+ switch (pth->auth.method)
+ {
+ case TALER_BANK_AUTH_NONE:
+ break;
+ case TALER_BANK_AUTH_BASIC:
+ memcpy (&buf[sizeof (struct BufFormatP) + len_o + len_i + len_b],
+ pth->auth.details.basic.username,
+ len_au);
+ memcpy (&buf[sizeof (struct BufFormatP) + len_o + len_i + len_b + len_au],
+ pth->auth.details.basic.password,
+ len_ap);
+ break;
+ }
+ /* finally give the state back */
+ pth->ptc (pth->ptc_cls,
+ buf,
+ sizeof (buf));
+ }
+ taler_bank_prepare_wire_transfer_cancel (NULL,
+ pth);
+}
+
+
+/**
+ * Parse account configuration from @a cfg in @a section into @a account.
+ * Obtains the URL option and initializes @a account from it.
+ *
+ * @param cfg configuration to parse
+ * @param section section with the account configuration
+ * @param account[out] account information to initialize
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_account_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section,
+ struct Account *account)
+{
+ char *account_url;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "URL",
+ &account_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "URL");
+ return GNUNET_SYSERR;
+ }
+
+ if (TALER_EC_NONE !=
+ parse_payto (account_url,
+ account))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "URL",
+ "Malformed payto:// URL for x-taler-bank method");
+ GNUNET_free (account_url);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (account_url);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Prepare for exeuction of a wire transfer. Note that we should call
+ * @a ptc asynchronously (as that is what the API requires, because
+ * some transfer methods need it). So while we could immediately call
+ * @a ptc, we first bundle up all the data and schedule a task to do
+ * the work.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param origin_account_section configuration section specifying the origin
+ * account of the exchange to use
+ * @param destination_account_url payto:// URL identifying where to send the money
+ * @param amount amount to transfer, already rounded
+ * @param exchange_base_url base URL of this exchange
+ * @param wtid wire transfer identifier to use
+ * @param ptc function to call with the prepared data to persist
+ * @param ptc_cls closure for @a ptc
+ * @return NULL on failure
+ */
+static struct TALER_WIRE_PrepareHandle *
+taler_bank_prepare_wire_transfer (void *cls,
+ const char *origin_account_section,
+ const char *destination_account_url,
+ const struct TALER_Amount *amount,
+ const char *exchange_base_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_WIRE_PrepareTransactionCallback ptc,
+ void *ptc_cls)
+{
+ struct TalerBankClosure *tc = cls;
+ struct TALER_WIRE_PrepareHandle *pth;
+ char *origin_account_url;
+ struct Account a_in;
+ struct Account a_out;
+
+ /* Check that payto:// URLs are valid */
+ if (TALER_EC_NONE !=
+ parse_payto (destination_account_url,
+ &a_out))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "payto://-URL `%s' is invalid!\n",
+ destination_account_url);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (tc->cfg,
+ origin_account_section,
+ "URL",
+ &origin_account_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ origin_account_section,
+ "URL");
+ GNUNET_free (a_out.hostname);
+ return NULL;
+ }
+ if (TALER_EC_NONE !=
+ parse_payto (origin_account_url,
+ &a_in))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ origin_account_section,
+ "URL",
+ "Malformed payto:// URL for x-taler-bank method");
+ GNUNET_free (origin_account_url);
+ GNUNET_free (a_out.hostname);
+ return NULL;
+ }
+
+ /* Make sure the bank is the same! */
+ if (0 != strcasecmp (a_in.hostname,
+ a_out.hostname))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "x-taler-bank hostname missmatch: `%s' != `%s'\n",
+ a_in.hostname,
+ a_out.hostname);
+ GNUNET_free (a_in.hostname);
+ GNUNET_free (a_out.hostname);
+ return NULL;
+ }
+ GNUNET_free (a_in.hostname);
+ GNUNET_free (a_out.hostname);
+
+ pth = GNUNET_new (struct TALER_WIRE_PrepareHandle);
+ if (GNUNET_OK !=
+ TALER_BANK_auth_parse_cfg (tc->cfg,
+ origin_account_section,
+ &pth->auth))
+ {
+ GNUNET_free (pth);
+ return NULL;
+ }
+
+ pth->tc = tc;
+ pth->origin_account_url = origin_account_url;
+ pth->destination_account_url = GNUNET_strdup (destination_account_url);
+ pth->exchange_base_url = GNUNET_strdup (exchange_base_url);
+ pth->wtid = *wtid;
+ pth->ptc = ptc;
+ pth->ptc_cls = ptc_cls;
+ pth->amount = *amount;
+ pth->task = GNUNET_SCHEDULER_add_now (&do_prepare,
+ pth);
+ return pth;
+}
+
+
+/**
+ * Called with the result of submitting information about an incoming
+ * transaction to a bank.
+ *
+ * @param cls closure with the `struct TALER_WIRE_ExecuteHandle`
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
+ * 0 if the bank's reply is bogus (fails to follow the protocol)
+ * @param ec error code from the bank
+ * @param serial_id unique ID of the wire transfer in the bank's records; UINT64_MAX on error
+ * @param json detailed response from the HTTPD, or NULL if reply was not JSON
+ */
+static void
+execute_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ uint64_t serial_id,
+ const json_t *json)
+{
+ struct TALER_WIRE_ExecuteHandle *eh = cls;
+ json_t *reason;
+ const char *emsg;
+ char *s;
+
+ eh->aaih = NULL;
+ emsg = NULL;
+ if (NULL != json)
+ {
+ reason = json_object_get (json,
+ "reason");
+ if (NULL != reason)
+ emsg = json_string_value (reason);
+ }
+ if (NULL != emsg)
+ GNUNET_asprintf (&s,
+ "%u/%u (%s)",
+ http_status,
+ (unsigned int) ec,
+ emsg);
+ else
+ GNUNET_asprintf (&s,
+ "%u/%u",
+ http_status,
+ (unsigned int) ec);
+ eh->cc (eh->cc_cls,
+ (MHD_HTTP_OK == http_status) ? GNUNET_OK : GNUNET_SYSERR,
+ serial_id,
+ (MHD_HTTP_OK == http_status) ? NULL : s);
+ GNUNET_free (s);
+ GNUNET_free (eh);
+}
+
+
+/**
+ * Execute a wire transfer.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param buf buffer with the prepared execution details
+ * @param buf_size number of bytes in @a buf
+ * @param cc function to call upon success
+ * @param cc_cls closure for @a cc
+ * @return NULL on error
+ */
+static struct TALER_WIRE_ExecuteHandle *
+taler_bank_execute_wire_transfer (void *cls,
+ const char *buf,
+ size_t buf_size,
+ TALER_WIRE_ConfirmationCallback cc,
+ void *cc_cls)
+{
+ struct TalerBankClosure *tc = cls;
+ struct TALER_WIRE_ExecuteHandle *eh;
+ struct TALER_Amount amount;
+ struct Account origin_account;
+ struct Account destination_account;
+ struct BufFormatP bf;
+ const char *exchange_base_url;
+ const char *origin_account_url;
+ const char *destination_account_url;
+ struct TALER_BANK_AuthenticationData auth;
+ size_t left;
+ size_t slen;
+ char *wire_s;
+
+ if (NULL == tc->ctx)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Bank not initialized, cannot do transfers!\n");
+ return NULL; /* not initialized with configuration, cannot do transfers */
+ }
+ if ( (buf_size <= sizeof (struct BufFormatP)) ||
+ ('\0' != buf[buf_size - 1]) )
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ memcpy (&bf,
+ buf,
+ sizeof (bf));
+ TALER_amount_ntoh (&amount,
+ &bf.amount);
+ origin_account_url = &buf[sizeof (struct BufFormatP)];
+ left = buf_size - sizeof (struct BufFormatP);
+ slen = strlen (origin_account_url) + 1;
+ GNUNET_assert (left >= slen);
+ left -= slen;
+ if (0 == left)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ destination_account_url = &origin_account_url[slen];
+ slen = strlen (destination_account_url) + 1;
+ GNUNET_assert (left >= slen);
+ left -= slen;
+ if (0 == left)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ exchange_base_url = &destination_account_url[slen];
+ slen = strlen (exchange_base_url) + 1;
+ GNUNET_assert (left >= slen);
+ left -= slen;
+ if (0 == left)
+ {
+ auth.method = TALER_BANK_AUTH_NONE;
+ }
+ else
+ {
+ auth.details.basic.username = (char *) &exchange_base_url[slen];
+ slen = strlen (auth.details.basic.username) + 1;
+ GNUNET_assert (left >= slen);
+ left -= slen;
+ if (0 == left)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ auth.details.basic.password = &auth.details.basic.username[slen];
+ slen = strlen (auth.details.basic.username) + 1;
+ GNUNET_assert (left >= slen);
+ left -= slen;
+ if (0 != left)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ }
+
+ if (TALER_EC_NONE !=
+ parse_payto (origin_account_url,
+ &origin_account))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (TALER_EC_NONE !=
+ parse_payto (destination_account_url,
+ &destination_account))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (0 != strcasecmp (origin_account.hostname,
+ destination_account.hostname))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ eh = GNUNET_new (struct TALER_WIRE_ExecuteHandle);
+ eh->cc = cc;
+ eh->cc_cls = cc_cls;
+ wire_s = GNUNET_STRINGS_data_to_string_alloc (&bf.wtid,
+ sizeof (bf.wtid));
+ eh->aaih = TALER_BANK_admin_add_incoming (tc->ctx,
+ origin_account.hostname,
+ &auth,
+ exchange_base_url,
+ wire_s,
+ &amount,
+ (uint64_t) origin_account.no,
+ (uint64_t) destination_account.no,
+ &execute_cb,
+ eh);
+ GNUNET_free (wire_s);
+ if (NULL == eh->aaih)
+ {
+ GNUNET_break (0);
+ GNUNET_free (eh);
+ return NULL;
+ }
+ return eh;
+}
+
+
+/**
+ * Abort execution of a wire transfer. For example, because we are
+ * shutting down. Note that if an execution is aborted, it may or
+ * may not still succeed. The caller MUST run @e
+ * execute_wire_transfer again for the same request as soon as
+ * possilbe, to ensure that the request either ultimately succeeds
+ * or ultimately fails. Until this has been done, the transaction is
+ * in limbo (i.e. may or may not have been committed).
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param eh execution to cancel
+ */
+static void
+taler_bank_execute_wire_transfer_cancel (void *cls,
+ struct TALER_WIRE_ExecuteHandle *eh)
+{
+ TALER_BANK_admin_add_incoming_cancel (eh->aaih);
+ GNUNET_free (eh);
+}
+
+
+/**
+ * Handle for a #taler_bank_get_history() request.
+ */
+struct TALER_WIRE_HistoryHandle
+{
+
+ /**
+ * Function to call with results.
+ */
+ TALER_WIRE_HistoryResultCallback hres_cb;
+
+ /**
+ * Closure for @e hres_cb.
+ */
+ void *hres_cb_cls;
+
+ /**
+ * Request to the bank.
+ */
+ struct TALER_BANK_HistoryHandle *hh;
+
+ /**
+ * Authentication to use for access.
+ */
+ struct TALER_BANK_AuthenticationData auth;
+
+};
+
+
+/**
+ * Cancel going over the account's history.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param whh operation to cancel
+ */
+static void
+taler_bank_get_history_cancel (void *cls,
+ struct TALER_WIRE_HistoryHandle *whh)
+{
+ if (NULL != whh->hh)
+ {
+ TALER_BANK_history_cancel (whh->hh);
+ whh->hh = NULL;
+ }
+ TALER_BANK_auth_free (&whh->auth);
+ GNUNET_free (whh);
+}
+
+
+/**
+ * Function called with results from the bank about the transaction history.
+ *
+ * @param cls the `struct TALER_WIRE_HistoryHandle`
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
+ * 0 if the bank's reply is bogus (fails to follow the protocol),
+ * #MHD_HTTP_NO_CONTENT if there are no more results; on success the
+ * last callback is always of this status (even if `abs(num_results)` were
+ * already returned).
+ * @param ec taler error code
+ * @param dir direction of the transfer
+ * @param serial_id monotonically increasing counter corresponding to the transaction
+ * @param details details about the wire transfer
+ * @param json detailed response from the HTTPD, or NULL if reply was not in JSON
+ */
+static void
+bhist_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ enum TALER_BANK_Direction dir,
+ uint64_t serial_id,
+ const struct TALER_BANK_TransferDetails *details,
+ const json_t *json)
+{
+ struct TALER_WIRE_HistoryHandle *whh = cls;
+ uint64_t bserial_id = GNUNET_htonll (serial_id);
+ struct TALER_WIRE_TransferDetails wd;
+
+ switch (http_status) {
+ case MHD_HTTP_OK:
+ {
+ char *subject;
+ char *space;
+
+ wd.amount = details->amount;
+ wd.execution_date = details->execution_date;
+ subject = GNUNET_strdup (details->wire_transfer_subject);
+ space = strchr (subject,
+ (unsigned char) ' ');
+ if (NULL != space)
+ {
+ /* Space separates the actual wire transfer subject from the
+ exchange base URL (if present, expected only for outgoing
+ transactions). So we cut the string off at the space. */
+ *space = '\0';
+ }
+ /* NOTE: For a real bank, the subject should include a checksum! */
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (subject,
+ strlen (subject),
+ &wd.wtid,
+ sizeof (wd.wtid)))
+ {
+ /* Ill-formed wire subject, set binary version to all zeros
+ and pass as a string, this time including the part after
+ the space. */
+ memset (&wd.wtid,
+ 0,
+ sizeof (wd.wtid));
+ wd.wtid_s = details->wire_transfer_subject;
+ }
+ else
+ {
+ wd.wtid_s = NULL;
+ }
+ GNUNET_free (subject);
+ wd.account_url = details->account_url;
+ if ( (NULL != whh->hres_cb) &&
+ (GNUNET_OK !=
+ whh->hres_cb (whh->hres_cb_cls,
+ TALER_EC_NONE,
+ dir,
+ &bserial_id,
+ sizeof (bserial_id),
+ &wd)) )
+ whh->hres_cb = NULL;
+ return; /* do NOT yet clean up! */
+ }
+ case MHD_HTTP_NO_CONTENT:
+ if (NULL != whh->hres_cb)
+ (void) whh->hres_cb (whh->hres_cb_cls,
+ ec,
+ TALER_BANK_DIRECTION_NONE,
+ NULL,
+ 0,
+ NULL);
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Bank failed with HTTP status %u (EC: %u)\n",
+ http_status,
+ ec);
+ if (NULL != whh->hres_cb)
+ (void) whh->hres_cb (whh->hres_cb_cls,
+ ec,
+ TALER_BANK_DIRECTION_NONE,
+ NULL,
+ 0,
+ NULL);
+ break;
+ }
+ whh->hh = NULL;
+ taler_bank_get_history_cancel (NULL,
+ whh);
+}
+
+
+/**
+ * Query transfer history of an account. We use the variable-size
+ * @a start_off to indicate which transfers we are interested in as
+ * different banking systems may have different ways to identify
+ * transfers. The @a start_off value must thus match the value of
+ * a `row_off` argument previously given to the @a hres_cb. Use
+ * NULL to query transfers from the beginning of time (with
+ * positive @a num_results) or from the lataler_bank committed transfers
+ * (with negative @a num_results).
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param account_section specifies the configuration section which
+ * identifies the account for which we should get the history
+ * @param direction what kinds of wire transfers should be returned
+ * @param start_off from which row on do we want to get results, use NULL for the lataler_bank; exclusive
+ * @param start_off_len number of bytes in @a start_off; must be `sizeof(uint64_t)`.
+ * @param num_results how many results do we want; negative numbers to go into the past,
+ * positive numbers to go into the future starting at @a start_row;
+ * must not be zero.
+ * @param hres_cb the callback to call with the transaction history
+ * @param hres_cb_cls closure for the above callback
+ */
+static struct TALER_WIRE_HistoryHandle *
+taler_bank_get_history (void *cls,
+ const char *account_section,
+ enum TALER_BANK_Direction direction,
+ const void *start_off,
+ size_t start_off_len,
+ int64_t num_results,
+ TALER_WIRE_HistoryResultCallback hres_cb,
+ void *hres_cb_cls)
+{
+ struct TalerBankClosure *tc = cls;
+ struct TALER_WIRE_HistoryHandle *whh;
+ const uint64_t *start_off_b64;
+ uint64_t start_row;
+ struct Account account;
+
+ if (0 == num_results)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (TALER_BANK_DIRECTION_NONE == direction)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if ( (NULL != start_off) &&
+ (sizeof (uint64_t) != start_off_len) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Wire plugin 'taler_bank' got start offset of wrong size (%llu instead of %llu)\n",
+ (unsigned long long) start_off_len,
+ (unsigned long long) sizeof (uint64_t));
+ GNUNET_break (0);
+ /* Probably something is wrong with the DB, some other component
+ * wrote a wrong value to it. Instead of completely stopping to work,
+ * we just scan from the beginning. */
+ start_off = NULL;
+ }
+ if (NULL == start_off)
+ {
+ start_row = UINT64_MAX; /* no start row */
+ }
+ else
+ {
+ start_off_b64 = start_off;
+ start_row = GNUNET_ntohll (*start_off_b64);
+ }
+ if (GNUNET_OK !=
+ parse_account_cfg (tc->cfg,
+ account_section,
+ &account))
+ return NULL;
+
+ whh = GNUNET_new (struct TALER_WIRE_HistoryHandle);
+ if (GNUNET_OK !=
+ TALER_BANK_auth_parse_cfg (tc->cfg,
+ account_section,
+ &whh->auth))
+ {
+ GNUNET_free (whh);
+ return NULL;
+ }
+
+ whh->hres_cb = hres_cb;
+ whh->hres_cb_cls = hres_cb_cls;
+ whh->hh = TALER_BANK_history (tc->ctx,
+ account.hostname,
+ &whh->auth,
+ (uint64_t) account.no,
+ direction,
+ start_row,
+ num_results,
+ &bhist_cb,
+ whh);
+ if (NULL == whh->hh)
+ {
+ GNUNET_break (0);
+ taler_bank_get_history_cancel (NULL,
+ whh);
+ GNUNET_free (account.hostname);
+ return NULL;
+ }
+ GNUNET_free (account.hostname);
+ return whh;
+}
+
+
+/**
+ * Context for a rejection operation.
+ */
+struct TALER_WIRE_RejectHandle
+{
+ /**
+ * Function to call with the result.
+ */
+ TALER_WIRE_RejectTransferCallback rej_cb;
+
+ /**
+ * Closure for @e rej_cb.
+ */
+ void *rej_cb_cls;
+
+ /**
+ * Handle for the reject operation.
+ */
+ struct TALER_BANK_RejectHandle *brh;
+
+ /**
+ * Authentication information to use.
+ */
+ struct TALER_BANK_AuthenticationData auth;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of asking
+ * the bank to reject an incoming wire transfer.
+ *
+ * @param cls closure
+ * @param http_status HTTP response code, #MHD_HTTP_NO_CONTENT (204) for successful status request;
+ * #MHD_HTTP_NOT_FOUND if the rowid is unknown;
+ * 0 if the bank's reply is bogus (fails to follow the protocol),
+ * @param ec detailed error code
+ */
+static void
+reject_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec)
+{
+ struct TALER_WIRE_RejectHandle *rh = cls;
+
+ rh->brh = NULL;
+ rh->rej_cb (rh->rej_cb_cls,
+ ec);
+ GNUNET_free (rh);
+}
+
+
+/**
+ * Cancel ongoing reject operation. Note that the rejection may still
+ * proceed. Basically, if this function is called, the rejection may
+ * have happened or not. This function is usually used during shutdown
+ * or system upgrades. At a later point, the application must call
+ * @e reject_transfer again for this wire transfer, unless the
+ * @e get_history shows that the wire transfer no longer exists.
+ *
+ * @param cls plugins' closure
+ * @param rh operation to cancel
+ * @return closure of the callback of the operation
+ */
+static void *
+taler_bank_reject_transfer_cancel (void *cls,
+ struct TALER_WIRE_RejectHandle *rh)
+{
+ void *ret = rh->rej_cb_cls;
+
+ if (NULL != rh->brh)
+ TALER_BANK_reject_cancel (rh->brh);
+ TALER_BANK_auth_free (&rh->auth);
+ GNUNET_free (rh);
+ return ret;
+}
+
+
+/**
+ * Reject an incoming wire transfer that was obtained from the
+ * history. This function can be used to transfer funds back to
+ * the sender if the WTID was malformed (i.e. due to a typo).
+ *
+ * Calling `reject_transfer` twice on the same wire transfer should
+ * be idempotent, i.e. not cause the funds to be wired back twice.
+ * Furthermore, the transfer should henceforth be removed from the
+ * results returned by @e get_history.
+ *
+ * @param cls plugin's closure
+ * @param account_section specifies the configuration section which
+ * identifies the account to use to reject the transfer
+ * @param start_off offset of the wire transfer in plugin-specific format
+ * @param start_off_len number of bytes in @a start_off
+ * @param rej_cb function to call with the result of the operation
+ * @param rej_cb_cls closure for @a rej_cb
+ * @return handle to cancel the operation
+ */
+static struct TALER_WIRE_RejectHandle *
+taler_bank_reject_transfer (void *cls,
+ const char *account_section,
+ const void *start_off,
+ size_t start_off_len,
+ TALER_WIRE_RejectTransferCallback rej_cb,
+ void *rej_cb_cls)
+{
+ struct TalerBankClosure *tc = cls;
+ const uint64_t *rowid_b64 = start_off;
+ struct TALER_WIRE_RejectHandle *rh;
+ struct Account account;
+
+ if (sizeof (uint64_t) != start_off_len)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ rh = GNUNET_new (struct TALER_WIRE_RejectHandle);
+ if (GNUNET_OK !=
+ TALER_BANK_auth_parse_cfg (tc->cfg,
+ account_section,
+ &rh->auth))
+ {
+ GNUNET_free (rh);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ parse_account_cfg (tc->cfg,
+ account_section,
+ &account))
+ {
+ (void) taler_bank_reject_transfer_cancel (tc,
+ rh);
+ return NULL;
+ }
+ rh->rej_cb = rej_cb;
+ rh->rej_cb_cls = rej_cb_cls;
+ rh->brh = TALER_BANK_reject (tc->ctx,
+ account.hostname,
+ &rh->auth,
+ (uint64_t) account.no,
+ GNUNET_ntohll (*rowid_b64),
+ &reject_cb,
+ rh);
+ if (NULL == rh->brh)
+ {
+ (void) taler_bank_reject_transfer_cancel (tc,
+ rh);
+ GNUNET_free (account.hostname);
+ return NULL;
+ }
+ GNUNET_free (account.hostname);
+ return rh;
+}
+
+
+/**
+ * Initialize taler_bank-wire subsystem.
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_WIRE_Plugin`
+ */
+void *
+libtaler_plugin_wire_taler_bank_init (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct TalerBankClosure *tc;
+ struct TALER_WIRE_Plugin *plugin;
+
+ tc = GNUNET_new (struct TalerBankClosure);
+ tc->cfg = cfg;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "taler",
+ "CURRENCY",
+ &tc->currency))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler",
+ "CURRENCY");
+ GNUNET_free (tc);
+ return NULL;
+ }
+ tc->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &tc->rc);
+ tc->rc = GNUNET_CURL_gnunet_rc_create (tc->ctx);
+ if (NULL == tc->ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_free (tc->currency);
+ GNUNET_free (tc);
+ return NULL;
+ }
+ plugin = GNUNET_new (struct TALER_WIRE_Plugin);
+ plugin->cls = tc;
+ plugin->method = "x-taler-bank";
+ plugin->amount_round = &taler_bank_amount_round;
+ plugin->wire_validate = &taler_bank_wire_validate;
+ plugin->prepare_wire_transfer = &taler_bank_prepare_wire_transfer;
+ plugin->prepare_wire_transfer_cancel = &taler_bank_prepare_wire_transfer_cancel;
+ plugin->execute_wire_transfer = &taler_bank_execute_wire_transfer;
+ plugin->execute_wire_transfer_cancel = &taler_bank_execute_wire_transfer_cancel;
+ plugin->get_history = &taler_bank_get_history;
+ plugin->get_history_cancel = &taler_bank_get_history_cancel;
+ plugin->reject_transfer = &taler_bank_reject_transfer;
+ plugin->reject_transfer_cancel = &taler_bank_reject_transfer_cancel;
+ return plugin;
+}
+
+
+/**
+ * Shutdown taler-bank wire subsystem.
+ *
+ * @param cls a `struct TALER_WIRE_Plugin`
+ * @return NULL (always)
+ */
+void *
+libtaler_plugin_wire_taler_bank_done (void *cls)
+{
+ struct TALER_WIRE_Plugin *plugin = cls;
+ struct TalerBankClosure *tc = plugin->cls;
+
+ if (NULL != tc->ctx)
+ {
+ GNUNET_CURL_fini (tc->ctx);
+ tc->ctx = NULL;
+ }
+ if (NULL != tc->rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (tc->rc);
+ tc->rc = NULL;
+ }
+ GNUNET_free_non_null (tc->currency);
+ GNUNET_free (tc);
+ GNUNET_free (plugin);
+ return NULL;
+}
+
+/* end of plugin_wire_taler-bank.c */
diff --git a/src/wire-plugins/plugin_wire_template.c b/src/wire-plugins/plugin_wire_template.c
new file mode 100644
index 000000000..6fadb88ce
--- /dev/null
+++ b/src/wire-plugins/plugin_wire_template.c
@@ -0,0 +1,345 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2016, 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 plugin_wire_template.c
+ * @brief template for wire plugins; replace "template" with real plugin name!
+ * @author Florian Dold
+ * @author Christian Grothoff
+ * @author Sree Harsha Totakura
+ */
+#include "platform.h"
+#include "taler_wire_plugin.h"
+
+
+/**
+ * Type of the "cls" argument given to each of the functions in
+ * our API.
+ */
+struct TemplateClosure
+{
+
+ /**
+ * Which currency do we support?
+ */
+ char *currency;
+
+ /**
+ * Which configuration do we use to lookup accounts?
+ */
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+};
+
+
+/**
+ * Round amount DOWN to the amount that can be transferred via the wire
+ * method. For example, Taler may support 0.000001 EUR as a unit of
+ * payment, but SEPA only supports 0.01 EUR. This function would
+ * round 0.125 EUR to 0.12 EUR in this case.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param[in,out] amount amount to round down
+ * @return #GNUNET_OK on success, #GNUNET_NO if rounding was unnecessary,
+ * #GNUNET_SYSERR if the amount or currency was invalid
+ */
+static int
+template_amount_round (void *cls,
+ struct TALER_Amount *amount)
+{
+ struct TemplateClosure *tc = cls;
+
+ if (0 != strcasecmp (amount->currency,
+ tc->currency))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_break (0); // not implemented
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Check if the given payto:// URL is correctly formatted for this plugin
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param account_url the payto:// URL
+ * @return #TALER_EC_NONE if correctly formatted
+ */
+static enum TALER_ErrorCode
+template_wire_validate (void *cls,
+ const char *account_url)
+{
+ GNUNET_break (0);
+ return TALER_EC_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Prepare for exeuction of a wire transfer.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param origin_account_section configuration section specifying the origin
+ * account of the exchange to use
+ * @param destination_account_url payto:// URL identifying where to send the money
+ * @param amount amount to transfer, already rounded
+ * @param exchange_base_url base URL of the exchange (for tracking)
+ * @param wtid wire transfer identifier to use
+ * @param ptc function to call with the prepared data to persist
+ * @param ptc_cls closure for @a ptc
+ * @return NULL on failure
+ */
+static struct TALER_WIRE_PrepareHandle *
+template_prepare_wire_transfer (void *cls,
+ const char *origin_account_section,
+ const char *destination_account_url,
+ const struct TALER_Amount *amount,
+ const char *exchange_base_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_WIRE_PrepareTransactionCallback ptc,
+ void *ptc_cls)
+{
+ GNUNET_break (0);
+ return NULL;
+}
+
+
+/**
+ * Abort preparation of a wire transfer. For example,
+ * because we are shutting down.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pth preparation to cancel
+ */
+static void
+template_prepare_wire_transfer_cancel (void *cls,
+ struct TALER_WIRE_PrepareHandle *pth)
+{
+ GNUNET_break (0);
+}
+
+
+/**
+ * Execute a wire transfer.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param buf buffer with the prepared execution details
+ * @param buf_size number of bytes in @a buf
+ * @param cc function to call upon success
+ * @param cc_cls closure for @a cc
+ * @return NULL on error
+ */
+static struct TALER_WIRE_ExecuteHandle *
+template_execute_wire_transfer (void *cls,
+ const char *buf,
+ size_t buf_size,
+ TALER_WIRE_ConfirmationCallback cc,
+ void *cc_cls)
+{
+ GNUNET_break (0);
+ return NULL;
+}
+
+
+/**
+ * Abort execution of a wire transfer. For example, because we are
+ * shutting down. Note that if an execution is aborted, it may or
+ * may not still succeed. The caller MUST run @e
+ * execute_wire_transfer again for the same request as soon as
+ * possilbe, to ensure that the request either ultimately succeeds
+ * or ultimately fails. Until this has been done, the transaction is
+ * in limbo (i.e. may or may not have been committed).
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param eh execution to cancel
+ */
+static void
+template_execute_wire_transfer_cancel (void *cls,
+ struct TALER_WIRE_ExecuteHandle *eh)
+{
+ GNUNET_break (0);
+}
+
+
+/**
+ * Query transfer history of an account. We use the variable-size
+ * @a start_off to indicate which transfers we are interested in as
+ * different banking systems may have different ways to identify
+ * transfers. The @a start_off value must thus match the value of
+ * a `row_off` argument previously given to the @a hres_cb. Use
+ * NULL to query transfers from the beginning of time (with
+ * positive @a num_results) or from the latest committed transfers
+ * (with negative @a num_results).
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param account_section specifies the configuration section which
+ * identifies the account for which we should get the history
+ * @param direction what kinds of wire transfers should be returned
+ * @param start_off from which row on do we want to get results, use NULL for the latest; exclusive
+ * @param start_off_len number of bytes in @a start_off; must be `sizeof(uint64_t)`.
+ * @param num_results how many results do we want; negative numbers to go into the past,
+ * positive numbers to go into the future starting at @a start_row;
+ * must not be zero.
+ * @param hres_cb the callback to call with the transaction history
+ * @param hres_cb_cls closure for the above callback
+ */
+static struct TALER_WIRE_HistoryHandle *
+template_get_history (void *cls,
+ const char *account_section,
+ enum TALER_BANK_Direction direction,
+ const void *start_off,
+ size_t start_off_len,
+ int64_t num_results,
+ TALER_WIRE_HistoryResultCallback hres_cb,
+ void *hres_cb_cls)
+{
+ GNUNET_break (0);
+ return NULL;
+}
+
+
+/**
+ * Cancel going over the account's history.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param whh operation to cancel
+ */
+static void
+template_get_history_cancel (void *cls,
+ struct TALER_WIRE_HistoryHandle *whh)
+{
+ GNUNET_break (0);
+}
+
+
+/**
+ * Reject an incoming wire transfer that was obtained from the
+ * history. This function can be used to transfer funds back to
+ * the sender if the WTID was malformed (i.e. due to a typo).
+ *
+ * Calling `reject_transfer` twice on the same wire transfer should
+ * be idempotent, i.e. not cause the funds to be wired back twice.
+ * Furthermore, the transfer should henceforth be removed from the
+ * results returned by @e get_history.
+ *
+ * @param cls plugin's closure
+ * @param account_section specifies the configuration section which
+ * identifies the account to use to reject the transfer
+ * @param start_off offset of the wire transfer in plugin-specific format
+ * @param start_off_len number of bytes in @a start_off
+ * @param rej_cb function to call with the result of the operation
+ * @param rej_cb_cls closure for @a rej_cb
+ * @return handle to cancel the operation
+ */
+static struct TALER_WIRE_RejectHandle *
+template_reject_transfer (void *cls,
+ const char *account_section,
+ const void *start_off,
+ size_t start_off_len,
+ TALER_WIRE_RejectTransferCallback rej_cb,
+ void *rej_cb_cls)
+{
+ GNUNET_break (0);
+ return NULL;
+}
+
+
+/**
+ * Cancel ongoing reject operation. Note that the rejection may still
+ * proceed. Basically, if this function is called, the rejection may
+ * have happened or not. This function is usually used during shutdown
+ * or system upgrades. At a later point, the application must call
+ * @e reject_transfer again for this wire transfer, unless the
+ * @e get_history shows that the wire transfer no longer exists.
+ *
+ * @param cls plugins' closure
+ * @param rh operation to cancel
+ * @return closure of the callback of the operation
+ */
+static void *
+template_reject_transfer_cancel (void *cls,
+ struct TALER_WIRE_RejectHandle *rh)
+{
+ GNUNET_break (0);
+ return NULL;
+}
+
+
+/**
+ * Initialize template-wire subsystem.
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_WIRE_Plugin`
+ */
+void *
+libtaler_plugin_wire_template_init (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct TemplateClosure *tc;
+ struct TALER_WIRE_Plugin *plugin;
+
+ tc = GNUNET_new (struct TemplateClosure);
+ tc->cfg = cfg;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "taler",
+ "CURRENCY",
+ &tc->currency))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler",
+ "CURRENCY");
+ GNUNET_free (tc);
+ return NULL;
+ }
+
+ plugin = GNUNET_new (struct TALER_WIRE_Plugin);
+ plugin->cls = tc;
+ plugin->method = "FIXME-REPLACE-BY-METHOD";
+ plugin->amount_round = &template_amount_round;
+ plugin->wire_validate = &template_wire_validate;
+ plugin->prepare_wire_transfer = &template_prepare_wire_transfer;
+ plugin->prepare_wire_transfer_cancel = &template_prepare_wire_transfer_cancel;
+ plugin->execute_wire_transfer = &template_execute_wire_transfer;
+ plugin->execute_wire_transfer_cancel = &template_execute_wire_transfer_cancel;
+ plugin->get_history = &template_get_history;
+ plugin->get_history_cancel = &template_get_history_cancel;
+ plugin->reject_transfer = &template_reject_transfer;
+ plugin->reject_transfer_cancel = &template_reject_transfer_cancel;
+ return plugin;
+}
+
+
+/**
+ * Shutdown Template wire subsystem.
+ *
+ * @param cls a `struct TALER_WIRE_Plugin`
+ * @return NULL (always)
+ */
+void *
+libtaler_plugin_wire_template_done (void *cls)
+{
+ struct TALER_WIRE_Plugin *plugin = cls;
+ struct TemplateClosure *tc = plugin->cls;
+
+ GNUNET_free (tc->currency);
+ GNUNET_free (tc);
+ GNUNET_free (plugin);
+ return NULL;
+}
+
+/* end of plugin_wire_template.c */
diff --git a/src/wire-plugins/test_ebics_wireformat.c b/src/wire-plugins/test_ebics_wireformat.c
new file mode 100644
index 000000000..46989d503
--- /dev/null
+++ b/src/wire-plugins/test_ebics_wireformat.c
@@ -0,0 +1,80 @@
+/*
+ This file is part of TALER
+ (C) 2015, 2016, 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 wire/test_ebics_wireformat.c
+ * @brief Tests for SEPA format validation by the EBICS plugin
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_wire_lib.h"
+
+
+/**
+ * Valid SEPA data
+ */
+static const char *valid_wire_str = "payto://sepa/DE67830654080004822650";
+
+/**
+ * IBAN has wrong country code
+ */
+static const char *invalid_wire_str = "payto://sepa/XX67830654080004822650";
+
+/**
+ * IBAN has wrong checksum
+ */
+static const char *invalid_wire_str2 = "payto://sepa/DE67830654080004822651";
+
+/**
+ * Unsupported wireformat type
+ */
+static const char *unsupported_wire_str = "payto://sega/DE67830654080004822650";
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ struct TALER_WIRE_Plugin *plugin;
+
+ GNUNET_log_setup ("test-sepa-wireformats",
+ "WARNING",
+ NULL);
+ cfg = GNUNET_CONFIGURATION_create ();
+ GNUNET_CONFIGURATION_set_value_string (cfg,
+ "taler",
+ "currency",
+ "EUR");
+ plugin = TALER_WIRE_plugin_load (cfg,
+ "ebics");
+ GNUNET_assert (NULL != plugin);
+ GNUNET_assert (TALER_EC_NONE !=
+ plugin->wire_validate (plugin->cls,
+ unsupported_wire_str));
+ GNUNET_assert (TALER_EC_NONE !=
+ plugin->wire_validate (plugin->cls,
+ invalid_wire_str));
+ GNUNET_assert (TALER_EC_NONE !=
+ plugin->wire_validate (plugin->cls,
+ invalid_wire_str2));
+ GNUNET_assert (TALER_EC_NONE ==
+ plugin->wire_validate (plugin->cls,
+ valid_wire_str));
+ TALER_WIRE_plugin_unload (plugin);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return 0;
+}
diff --git a/src/wire-plugins/test_wire_plugin.c b/src/wire-plugins/test_wire_plugin.c
new file mode 100644
index 000000000..0e149dde7
--- /dev/null
+++ b/src/wire-plugins/test_wire_plugin.c
@@ -0,0 +1,186 @@
+/*
+ This file is part of TALER
+ (C) 2015, 2016, 2017 GNUnet e.V. and Inria
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 wire/test_wire_plugin.c
+ * @brief Tests for wire plugins
+ * @author Christian Grothoff
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_wire_lib.h"
+#include "taler_wire_plugin.h"
+#include <gnunet/gnunet_json_lib.h>
+
+
+/**
+ * Definitions for a test with a plugin.
+ */
+struct TestBlock {
+
+ /**
+ * Name of the plugin to test.
+ */
+ const char *plugin_name;
+
+ /**
+ * Amount to give to the rounding function.
+ */
+ const char *round_in;
+
+ /**
+ * Expected result from rounding.
+ */
+ const char *round_out;
+
+ /**
+ * Currency to give to the plugin.
+ */
+ const char *currency;
+};
+
+
+/**
+ * List of plugins and (unsigned) JSON account definitions
+ * to use for the tests.
+ */
+static struct TestBlock tests[] = {
+ {
+ .plugin_name = "ebics",
+ .round_in = "EUR:0.123456",
+ .round_out = "EUR:0.12",
+ .currency = "EUR"
+ },
+ {
+ .plugin_name = "taler_bank",
+ .round_in = "KUDOS:0.123456",
+ .round_out = "KUDOS:0.12",
+ .currency = "KUDOS"
+ },
+ {
+ NULL, NULL, NULL, NULL
+ }
+};
+
+
+/**
+ * Our configuration.
+ */
+static struct GNUNET_CONFIGURATION_Handle *cfg;
+
+
+/**
+ * Run the test.
+ *
+ * @param test details of the test
+ * @param plugin plugin to test
+ * @return #GNUNET_OK on success
+ */
+static int
+run_test (const struct TestBlock *test,
+ struct TALER_WIRE_Plugin *plugin)
+{
+ struct GNUNET_HashCode salt;
+ struct TALER_Amount in;
+ struct TALER_Amount expect;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &salt,
+ sizeof (salt));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (test->round_in,
+ &in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (test->round_out,
+ &expect));
+ if (GNUNET_OK !=
+ plugin->amount_round (plugin->cls,
+ &in))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 != TALER_amount_cmp (&in, &expect))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_NO !=
+ plugin->amount_round (plugin->cls,
+ &in))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ memset (&in, 0, sizeof (in));
+ GNUNET_log_skip (GNUNET_ERROR_TYPE_ERROR, 1);
+ if (GNUNET_SYSERR !=
+ plugin->amount_round (plugin->cls,
+ &in))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ int ret;
+ struct TALER_WIRE_Plugin *plugin;
+ const struct TestBlock *test;
+
+ GNUNET_log_setup ("test-wire-plugin",
+ "WARNING",
+ NULL);
+ cfg = GNUNET_CONFIGURATION_create ();
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONFIGURATION_load (cfg,
+ "test_wire_plugin.conf"));
+ ret = GNUNET_OK;
+ for (unsigned int i=0;NULL != (test = &tests[i])->plugin_name;i++)
+ {
+ GNUNET_CONFIGURATION_set_value_string (cfg,
+ "taler",
+ "CURRENCY",
+ test->currency);
+ plugin = TALER_WIRE_plugin_load (cfg,
+ test->plugin_name);
+ GNUNET_assert (NULL != plugin);
+ ret = run_test (test, plugin);
+ TALER_WIRE_plugin_unload (plugin);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%s FAILED\n",
+ test->plugin_name);
+ break;
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "%s PASS\n",
+ test->plugin_name);
+ }
+ }
+ GNUNET_CONFIGURATION_destroy (cfg);
+ if (GNUNET_OK != ret)
+ return 1;
+ return 0;
+}
diff --git a/src/wire-plugins/test_wire_plugin.conf b/src/wire-plugins/test_wire_plugin.conf
new file mode 100644
index 000000000..d1d699b0f
--- /dev/null
+++ b/src/wire-plugins/test_wire_plugin.conf
@@ -0,0 +1,25 @@
+# This file is in the public domain.
+#
+[account-taler-bank]
+# This is the response we give out for the /wire request. It provides
+# wallets with the bank information for transfers to the exchange.
+WIRE_JSON = test_wire_plugin_test.json
+
+# Our bank account URL
+URL = payto://x-taler-bank/2
+
+# Which wire plugin should we used to access the account?
+PLUGIN = taler_bank
+
+
+[account-sepa]
+# This is the response we give out for the /wire request. It provides
+# wallets with the bank information for transfers to the exchange.
+WIRE_JSON = test_wire_plugin_sepa.json
+
+# Which wire plugin should we used to access the account?
+PLUGIN = ebics
+
+
+[taler]
+CURRENCY = "EUR"
diff --git a/src/wire-plugins/test_wire_plugin_transactions_taler-bank.c b/src/wire-plugins/test_wire_plugin_transactions_taler-bank.c
new file mode 100644
index 000000000..06aef8aa1
--- /dev/null
+++ b/src/wire-plugins/test_wire_plugin_transactions_taler-bank.c
@@ -0,0 +1,338 @@
+/*
+ This file is part of TALER
+ (C) 2015-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 wire/test_wire_plugin_transactions_taler-bank.c
+ * @brief Tests performing actual transactions with the taler-bank wire plugin against FAKEBANK
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_wire_lib.h"
+#include "taler_wire_plugin.h"
+#include "taler_fakebank_lib.h"
+#include <gnunet/gnunet_json_lib.h>
+
+
+/**
+ * When does the test timeout? Right now, we expect this to be very
+ * fast.
+ */
+#define TIMEOUT GNUNET_TIME_UNIT_SECONDS
+
+
+/**
+ * Destination account to use.
+ */
+static const char *dest_account = "payto://x-taler-bank/localhost:8088/42";
+
+/**
+ * Origin account, section in the configuration file.
+ */
+static const char *my_account = "account-test";
+
+/**
+ * Our configuration.
+ */
+static struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Set to #GNUNET_SYSERR if the test failed.
+ */
+static int global_ret;
+
+/**
+ * The 'test' plugin that we are using for the test.
+ */
+static struct TALER_WIRE_Plugin *plugin;
+
+/**
+ * Active preparation handle, or NULL if not active.
+ */
+static struct TALER_WIRE_PrepareHandle *ph;
+
+/**
+ * Active execution handle, or NULL if not active.
+ */
+static struct TALER_WIRE_ExecuteHandle *eh;
+
+/**
+ * Handle to the bank.
+ */
+static struct TALER_FAKEBANK_Handle *fb;
+
+/**
+ * Handle to the history request.
+ */
+static struct TALER_WIRE_HistoryHandle *hh;
+
+/**
+ * Handle for the timeout task.
+ */
+static struct GNUNET_SCHEDULER_Task *tt;
+
+/**
+ * Which serial ID do we expect to get from /history?
+ */
+static uint64_t serial_target;
+
+/**
+ * Wire transfer identifier we are using.
+ */
+static struct TALER_WireTransferIdentifierRawP wtid;
+
+
+/**
+ * Function called on shutdown (regular, error or CTRL-C).
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ TALER_FAKEBANK_stop (fb);
+ fb = NULL;
+ if (NULL != eh)
+ {
+ plugin->execute_wire_transfer_cancel (plugin->cls,
+ eh);
+ eh = NULL;
+ }
+ if (NULL != ph)
+ {
+ plugin->prepare_wire_transfer_cancel (plugin->cls,
+ ph);
+ ph = NULL;
+ }
+ if (NULL != hh)
+ {
+ plugin->get_history_cancel (plugin->cls,
+ hh);
+ hh = NULL;
+ }
+ if (NULL != tt)
+ {
+ GNUNET_SCHEDULER_cancel (tt);
+ tt = NULL;
+ }
+ TALER_WIRE_plugin_unload (plugin);
+}
+
+
+/**
+ * Function called on timeout.
+ *
+ * @param cls NULL
+ */
+static void
+timeout_cb (void *cls)
+{
+ tt = NULL;
+ GNUNET_break (0);
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of asking
+ * the bank for the transaction history.
+ *
+ * @param cls closure
+ * @param ec taler status code
+ * @param dir direction of the transfer
+ * @param row_off identification of the position at which we are querying
+ * @param row_off_size number of bytes in @a row_off
+ * @param details details about the wire transfer
+ * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
+ */
+static int
+history_result_cb (void *cls,
+ enum TALER_ErrorCode ec,
+ enum TALER_BANK_Direction dir,
+ const void *row_off,
+ size_t row_off_size,
+ const struct TALER_WIRE_TransferDetails *details)
+{
+ uint64_t *serialp;
+ uint64_t serialh;
+ struct TALER_Amount amount;
+
+ hh = NULL;
+ if ( (TALER_BANK_DIRECTION_NONE == dir) &&
+ (GNUNET_OK == global_ret) )
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_OK;
+ }
+ if (sizeof (uint64_t) != row_off_size)
+ {
+ GNUNET_break (0);
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ serialp = (uint64_t *) row_off;
+ serialh = GNUNET_ntohll (*serialp);
+ if (serialh != serial_target)
+ {
+ GNUNET_break (0);
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("KUDOS:5.01",
+ &amount));
+ if (0 != TALER_amount_cmp (&amount,
+ &details->amount))
+ {
+ GNUNET_break (0);
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ if (0 != memcmp (&wtid,
+ &details->wtid,
+ sizeof (struct TALER_WireTransferIdentifierRawP)))
+ {
+ GNUNET_break (0);
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ global_ret = GNUNET_OK;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with the result from the execute step.
+ *
+ * @param cls closure
+ * @param success #GNUNET_OK on success, #GNUNET_SYSERR on failure
+ * @param serial_id unique ID of the wire transfer in the bank's records; UINT64_MAX on error
+ * @param emsg NULL on success, otherwise an error message
+ */
+static void
+confirmation_cb (void *cls,
+ int success,
+ uint64_t serial_id,
+ const char *emsg)
+{
+ eh = NULL;
+ if (GNUNET_OK != success)
+ {
+ GNUNET_break (0);
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ serial_target = serial_id;
+ hh = plugin->get_history (plugin->cls,
+ my_account,
+ TALER_BANK_DIRECTION_BOTH,
+ NULL, 0,
+ 5,
+ &history_result_cb,
+ NULL);
+}
+
+
+/**
+ * Callback with prepared transaction.
+ *
+ * @param cls closure
+ * @param buf transaction data to persist, NULL on error
+ * @param buf_size number of bytes in @a buf, 0 on error
+ */
+static void
+prepare_cb (void *cls,
+ const char *buf,
+ size_t buf_size)
+{
+ ph = NULL;
+ if (NULL == buf)
+ {
+ GNUNET_break (0);
+ global_ret = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ plugin->execute_wire_transfer (plugin->cls,
+ buf,
+ buf_size,
+ &confirmation_cb,
+ NULL);
+}
+
+
+/**
+ * Run the test.
+ *
+ * @param cls NULL
+ */
+static void
+run (void *cls)
+{
+ struct TALER_Amount amount;
+
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ tt = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
+ &timeout_cb,
+ NULL);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &wtid,
+ sizeof (wtid));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("KUDOS:5.01",
+ &amount));
+ fb = TALER_FAKEBANK_start (8088);
+ ph = plugin->prepare_wire_transfer (plugin->cls,
+ my_account,
+ dest_account,
+ &amount,
+ "https://exchange.net/",
+ &wtid,
+ &prepare_cb,
+ NULL);
+}
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ GNUNET_log_setup ("test-wire-plugin-transactions-test",
+ "WARNING",
+ NULL);
+ cfg = GNUNET_CONFIGURATION_create ();
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONFIGURATION_load (cfg,
+ "test_wire_plugin_transactions_taler-bank.conf"));
+ global_ret = GNUNET_OK;
+ plugin = TALER_WIRE_plugin_load (cfg,
+ "taler_bank");
+ GNUNET_assert (NULL != plugin);
+ GNUNET_SCHEDULER_run (&run,
+ NULL);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ if (GNUNET_OK != global_ret)
+ return 1;
+ return 0;
+}
+
+/* end of test_wire_plugin_transactions_taler-bank.c */
diff --git a/src/wire-plugins/test_wire_plugin_transactions_taler-bank.conf b/src/wire-plugins/test_wire_plugin_transactions_taler-bank.conf
new file mode 100644
index 000000000..d6d2e8346
--- /dev/null
+++ b/src/wire-plugins/test_wire_plugin_transactions_taler-bank.conf
@@ -0,0 +1,12 @@
+# This file is in the public domain.
+#
+[account-test]
+# This is the response we give out for the /wire request. It provides
+# wallets with the bank information for transfers to the exchange.
+
+TALER_BANK_AUTH_METHOD = NONE
+
+URL = payto://x-taler-bank/localhost:8088/2
+
+[taler]
+CURRENCY = "KUDOS"