summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Makefile.am47
-rw-r--r--src/lib/merchant_api_context.c525
-rw-r--r--src/lib/merchant_api_context.h169
-rw-r--r--src/lib/merchant_api_json.c491
-rw-r--r--src/lib/merchant_api_json.h331
-rw-r--r--src/lib/merchant_api_pay.c372
-rw-r--r--src/lib/test_merchant_api.c1345
7 files changed, 3280 insertions, 0 deletions
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
new file mode 100644
index 00000000..8696db40
--- /dev/null
+++ b/src/lib/Makefile.am
@@ -0,0 +1,47 @@
+# 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
+
+lib_LTLIBRARIES = \
+ libtalermerchant.la
+
+libtalermerchant_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+
+libtalermerchant_la_SOURCES = \
+ merchant_api_context.c merchant_api_context.h \
+ merchant_api_json.c merchant_api_json.h \
+ merchant_api_pay.c
+
+libtalermerchant_la_LIBADD = \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+if HAVE_LIBCURL
+libtalermerchant_la_LIBADD += -lcurl
+else
+if HAVE_LIBGNURL
+libtalermerchant_la_LIBADD += -lgnurl
+endif
+endif
+
+check_PROGRAMS = \
+ test_merchant_api
+
+TESTS = \
+ $(check_PROGRAMS)
+
+test_merchant_api_SOURCES = \
+ test_merchant_api.c
+test_merchant_api_LDADD = \
+ libtalermerchant.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil \
+ -ljansson
diff --git a/src/lib/merchant_api_context.c b/src/lib/merchant_api_context.c
new file mode 100644
index 00000000..5b8b9e4f
--- /dev/null
+++ b/src/lib/merchant_api_context.c
@@ -0,0 +1,525 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
+
+ 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, If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant-lib/merchant_api_context.c
+ * @brief Implementation of the context part of the merchant's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_context.h"
+
+
+/**
+ * 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\n", \
+ function, __FILE__, __LINE__, curl_easy_strerror (code));
+
+/**
+ * Print JSON parsing related error information
+ */
+#define JSON_WARN(error) \
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \
+ "JSON parsing failed at %s:%u: %s (%s)\n", \
+ __FILE__, __LINE__, error.text, error.source)
+
+
+/**
+ * Failsafe flag. Raised if our constructor fails to initialize
+ * the Curl library.
+ */
+static int TALER_MERCHANT_curl_fail;
+
+
+/**
+ * Jobs are CURL requests running within a `struct TALER_MERCHANT_Context`.
+ */
+struct MAC_Job
+{
+
+ /**
+ * We keep jobs in a DLL.
+ */
+ struct MAC_Job *next;
+
+ /**
+ * We keep jobs in a DLL.
+ */
+ struct MAC_Job *prev;
+
+ /**
+ * Easy handle of the job.
+ */
+ CURL *easy_handle;
+
+ /**
+ * Context this job runs in.
+ */
+ struct TALER_MERCHANT_Context *ctx;
+
+ /**
+ * Function to call upon completion.
+ */
+ MAC_JobCompletionCallback jcc;
+
+ /**
+ * Closure for @e jcc.
+ */
+ void *jcc_cls;
+
+};
+
+
+/**
+ * Context
+ */
+struct TALER_MERCHANT_Context
+{
+ /**
+ * Curl multi handle
+ */
+ CURLM *multi;
+
+ /**
+ * Curl share handle
+ */
+ CURLSH *share;
+
+ /**
+ * We keep jobs in a DLL.
+ */
+ struct MAC_Job *jobs_head;
+
+ /**
+ * We keep jobs in a DLL.
+ */
+ struct MAC_Job *jobs_tail;
+
+ /**
+ * HTTP header "application/json", created once and used
+ * for all requests that need it.
+ */
+ struct curl_slist *json_header;
+
+};
+
+
+/**
+ * Initialise this library. This function should be called before using any of
+ * the following functions.
+ *
+ * @return library context
+ */
+struct TALER_MERCHANT_Context *
+TALER_MERCHANT_init ()
+{
+ struct TALER_MERCHANT_Context *ctx;
+ CURLM *multi;
+ CURLSH *share;
+
+ if (TALER_MERCHANT_curl_fail)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Curl was not initialised properly\n");
+ return NULL;
+ }
+ if (NULL == (multi = curl_multi_init ()))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to create a Curl multi handle\n");
+ return NULL;
+ }
+ if (NULL == (share = curl_share_init ()))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to create a Curl share handle\n");
+ return NULL;
+ }
+ ctx = GNUNET_new (struct TALER_MERCHANT_Context);
+ ctx->multi = multi;
+ ctx->share = share;
+ GNUNET_assert (NULL != (ctx->json_header =
+ curl_slist_append (NULL,
+ "Content-Type: application/json")));
+ return ctx;
+}
+
+
+/**
+ * Schedule a CURL request to be executed and call the given @a jcc
+ * upon its completion. Note that the context will make use of the
+ * CURLOPT_PRIVATE facility of the CURL @a eh. Applications can
+ * instead use #MAC_easy_to_closure to extract the @a jcc_cls argument
+ * from a valid @a eh afterwards.
+ *
+ * This function modifies the CURL handle to add the
+ * "Content-Type: application/json" header if @a add_json is set.
+ *
+ * @param ctx context to execute the job in
+ * @param eh curl easy handle for the request, will
+ * be executed AND cleaned up
+ * @param add_json add "application/json" content type header
+ * @param jcc callback to invoke upon completion
+ * @param jcc_cls closure for @a jcc
+ */
+struct MAC_Job *
+MAC_job_add (struct TALER_MERCHANT_Context *ctx,
+ CURL *eh,
+ int add_json,
+ MAC_JobCompletionCallback jcc,
+ void *jcc_cls)
+{
+ struct MAC_Job *job;
+
+ if (GNUNET_YES == add_json)
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HTTPHEADER,
+ ctx->json_header));
+
+ job = GNUNET_new (struct MAC_Job);
+ job->easy_handle = eh;
+ job->ctx = ctx;
+ job->jcc = jcc;
+ job->jcc_cls = jcc_cls;
+ GNUNET_CONTAINER_DLL_insert (ctx->jobs_head,
+ ctx->jobs_tail,
+ job);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_PRIVATE,
+ job));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_SHARE,
+ ctx->share));
+ GNUNET_assert (CURLM_OK ==
+ curl_multi_add_handle (ctx->multi,
+ eh));
+ return job;
+}
+
+
+/**
+ * Obtain the `jcc_cls` argument from an `eh` that was
+ * given to #MAC_job_add().
+ *
+ * @param eh easy handle that was used
+ * @return the `jcc_cls` that was given to #MAC_job_add().
+ */
+void *
+MAC_easy_to_closure (CURL *eh)
+{
+ struct MAC_Job *job;
+
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_getinfo (eh,
+ CURLINFO_PRIVATE,
+ (char **) &job));
+ return job->jcc_cls;
+}
+
+
+/**
+ * Cancel a job. Must only be called before the job completion
+ * callback is called for the respective job.
+ *
+ * @param job job to cancel
+ */
+void
+MAC_job_cancel (struct MAC_Job *job)
+{
+ struct TALER_MERCHANT_Context *ctx = job->ctx;
+
+ GNUNET_CONTAINER_DLL_remove (ctx->jobs_head,
+ ctx->jobs_tail,
+ job);
+ GNUNET_assert (CURLM_OK ==
+ curl_multi_remove_handle (ctx->multi,
+ job->easy_handle));
+ curl_easy_cleanup (job->easy_handle);
+ GNUNET_free (job);
+}
+
+
+/**
+ * Run the main event loop for the Taler interaction.
+ *
+ * @param ctx the library context
+ */
+void
+TALER_MERCHANT_perform (struct TALER_MERCHANT_Context *ctx)
+{
+ CURLMsg *cmsg;
+ struct MAC_Job *job;
+ int n_running;
+ int n_completed;
+
+ (void) curl_multi_perform (ctx->multi,
+ &n_running);
+ while (NULL != (cmsg = curl_multi_info_read (ctx->multi,
+ &n_completed)))
+ {
+ /* Only documented return value is CURLMSG_DONE */
+ GNUNET_break (CURLMSG_DONE == cmsg->msg);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_getinfo (cmsg->easy_handle,
+ CURLINFO_PRIVATE,
+ (char *) &job));
+ GNUNET_assert (job->ctx == ctx);
+ job->jcc (job->jcc_cls,
+ cmsg->easy_handle);
+ MAC_job_cancel (job);
+ }
+}
+
+
+/**
+ * Obtain the information for a select() call to wait until
+ * #TALER_MERCHANT_perform() is ready again. Note that calling
+ * any other TALER_MERCHANT-API may also imply that the library
+ * is again ready for #TALER_MERCHANT_perform().
+ *
+ * Basically, a client should use this API to prepare for select(),
+ * then block on select(), then call #TALER_MERCHANT_perform() and then
+ * start again until the work with the context is done.
+ *
+ * This function will NOT zero out the sets and assumes that @a max_fd
+ * and @a timeout are already set to minimal applicable values. It is
+ * safe to give this API FD-sets and @a max_fd and @a timeout that are
+ * already initialized to some other descriptors that need to go into
+ * the select() call.
+ *
+ * @param ctx context to get the event loop information for
+ * @param read_fd_set will be set for any pending read operations
+ * @param write_fd_set will be set for any pending write operations
+ * @param except_fd_set is here because curl_multi_fdset() has this argument
+ * @param max_fd set to the highest FD included in any set;
+ * if the existing sets have no FDs in it, the initial
+ * value should be "-1". (Note that `max_fd + 1` will need
+ * to be passed to select().)
+ * @param timeout set to the timeout in milliseconds (!); -1 means
+ * no timeout (NULL, blocking forever is OK), 0 means to
+ * proceed immediately with #TALER_MERCHANT_perform().
+ */
+void
+TALER_MERCHANT_get_select_info (struct TALER_MERCHANT_Context *ctx,
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set,
+ int *max_fd,
+ long *timeout)
+{
+ GNUNET_assert (CURLM_OK ==
+ curl_multi_fdset (ctx->multi,
+ read_fd_set,
+ write_fd_set,
+ except_fd_set,
+ max_fd));
+ GNUNET_assert (CURLM_OK ==
+ curl_multi_timeout (ctx->multi,
+ timeout));
+ if ( (-1 == (*timeout)) &&
+ (NULL != ctx->jobs_head) )
+ *timeout = 1000 * 60 * 5; /* curl is not always good about giving timeouts */
+}
+
+
+/**
+ * Cleanup library initialisation resources. This function should be called
+ * after using this library to cleanup the resources occupied during library's
+ * initialisation.
+ *
+ * @param ctx the library context
+ */
+void
+TALER_MERCHANT_fini (struct TALER_MERCHANT_Context *ctx)
+{
+ /* all jobs must have been cancelled at this time, assert this */
+ GNUNET_assert (NULL == ctx->jobs_head);
+ curl_share_cleanup (ctx->share);
+ curl_multi_cleanup (ctx->multi);
+ curl_slist_free_all (ctx->json_header);
+ GNUNET_free (ctx);
+}
+
+
+/**
+ * Callback used when downloading the reply to an HTTP request.
+ * Just appends all of the data to the `buf` in the
+ * `struct MAC_DownloadBuffer` for further processing. The size of
+ * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if
+ * the download exceeds this size, we abort with an error.
+ *
+ * @param bufptr data downloaded via HTTP
+ * @param size size of an item in @a bufptr
+ * @param nitems number of items in @a bufptr
+ * @param cls the `struct KeysRequest`
+ * @return number of bytes processed from @a bufptr
+ */
+size_t
+MAC_download_cb (char *bufptr,
+ size_t size,
+ size_t nitems,
+ void *cls)
+{
+ struct MAC_DownloadBuffer *db = cls;
+ size_t msize;
+ void *buf;
+
+ if (0 == size * nitems)
+ {
+ /* Nothing (left) to do */
+ return 0;
+ }
+ msize = size * nitems;
+ if ( (msize + db->buf_size) >= GNUNET_MAX_MALLOC_CHECKED)
+ {
+ db->eno = ENOMEM;
+ return 0; /* signals an error to curl */
+ }
+ db->buf = GNUNET_realloc (db->buf,
+ db->buf_size + msize);
+ buf = db->buf + db->buf_size;
+ memcpy (buf, bufptr, msize);
+ db->buf_size += msize;
+ return msize;
+}
+
+
+/**
+ * Obtain information about the final result about the
+ * HTTP download. If the download was successful, parses
+ * the JSON in the @a db and returns it. Also returns
+ * the HTTP @a response_code. If the download failed,
+ * the return value is NULL. The response code is set
+ * in any case, on download errors to zero.
+ *
+ * Calling this function also cleans up @a db.
+ *
+ * @param db download buffer
+ * @param eh CURL handle (to get the response code)
+ * @param[out] response_code set to the HTTP response code
+ * (or zero if we aborted the download, i.e.
+ * because the response was too big, or if
+ * the JSON we received was malformed).
+ * @return NULL if downloading a JSON reply failed
+ */
+json_t *
+MAC_download_get_result (struct MAC_DownloadBuffer *db,
+ CURL *eh,
+ long *response_code)
+{
+ json_t *json;
+ json_error_t error;
+ char *ct;
+
+ if ( (CURLE_OK !=
+ curl_easy_getinfo (eh,
+ CURLINFO_CONTENT_TYPE,
+ &ct)) ||
+ (NULL == ct) ||
+ (0 != strcasecmp (ct,
+ "application/json")) )
+ {
+ /* No content type or explicitly not JSON, refuse to parse
+ (but keep response code) */
+ if (CURLE_OK !=
+ curl_easy_getinfo (eh,
+ CURLINFO_RESPONSE_CODE,
+ response_code))
+ {
+ /* unexpected error... */
+ GNUNET_break (0);
+ *response_code = 0;
+ }
+ return NULL;
+ }
+
+ json = NULL;
+ if (0 == db->eno)
+ {
+ json = json_loadb (db->buf,
+ db->buf_size,
+ JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK,
+ &error);
+ if (NULL == json)
+ {
+ JSON_WARN (error);
+ *response_code = 0;
+ }
+ }
+ GNUNET_free_non_null (db->buf);
+ db->buf = NULL;
+ db->buf_size = 0;
+ if (NULL != json)
+ {
+ if (CURLE_OK !=
+ curl_easy_getinfo (eh,
+ CURLINFO_RESPONSE_CODE,
+ response_code))
+ {
+ /* unexpected error... */
+ GNUNET_break (0);
+ *response_code = 0;
+ }
+ }
+ return json;
+}
+
+
+/**
+ * Initial global setup logic, specifically runs the Curl setup.
+ */
+__attribute__ ((constructor))
+void
+TALER_MERCHANT_constructor__ (void)
+{
+ CURLcode ret;
+
+ if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT)))
+ {
+ CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR,
+ "curl_global_init",
+ ret);
+ TALER_MERCHANT_curl_fail = 1;
+ }
+}
+
+
+/**
+ * Cleans up after us, specifically runs the Curl cleanup.
+ */
+__attribute__ ((destructor))
+void
+TALER_MERCHANT_destructor__ (void)
+{
+ if (TALER_MERCHANT_curl_fail)
+ return;
+ curl_global_cleanup ();
+}
+
+/* end of merchant_api_context.c */
diff --git a/src/lib/merchant_api_context.h b/src/lib/merchant_api_context.h
new file mode 100644
index 00000000..e54d34cc
--- /dev/null
+++ b/src/lib/merchant_api_context.h
@@ -0,0 +1,169 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
+
+ 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, If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant-lib/merchant_api_context.h
+ * @brief Internal interface to the context part of the merchant's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Entry in the context's job queue.
+ */
+struct MAC_Job;
+
+/**
+ * Function to call upon completion of a job.
+ *
+ * @param cls closure
+ * @param eh original easy handle (for inspection)
+ */
+typedef void
+(*MAC_JobCompletionCallback)(void *cls,
+ CURL *eh);
+
+
+/**
+ * Schedule a CURL request to be executed and call the given @a jcc
+ * upon its completion. Note that the context will make use of the
+ * CURLOPT_PRIVATE facility of the CURL @a eh. Applications can
+ * instead use #MAC_easy_to_closure to extract the @a jcc_cls argument
+ * from a valid @a eh afterwards.
+ *
+ * This function modifies the CURL handle to add the
+ * "Content-Type: application/json" header if @a add_json is set.
+ *
+ * @param ctx context to execute the job in
+ * @param eh curl easy handle for the request, will
+ * be executed AND cleaned up
+ * @param add_json add "application/json" content type header
+ * @param jcc callback to invoke upon completion
+ * @param jcc_cls closure for @a jcc
+ */
+struct MAC_Job *
+MAC_job_add (struct TALER_MERCHANT_Context *ctx,
+ CURL *eh,
+ int add_json,
+ MAC_JobCompletionCallback jcc,
+ void *jcc_cls);
+
+
+/**
+ * Obtain the `jcc_cls` argument from an `eh` that was
+ * given to #MAC_job_add().
+ *
+ * @param eh easy handle that was used
+ * @return the `jcc_cls` that was given to #MAC_job_add().
+ */
+void *
+MAC_easy_to_closure (CURL *eh);
+
+
+/**
+ * Cancel a job. Must only be called before the job completion
+ * callback is called for the respective job.
+ *
+ * @param job job to cancel
+ */
+void
+MAC_job_cancel (struct MAC_Job *job);
+
+
+/**
+ * @brief Buffer data structure we use to buffer the HTTP download
+ * before giving it to the JSON parser.
+ */
+struct MAC_DownloadBuffer
+{
+
+ /**
+ * Download buffer
+ */
+ void *buf;
+
+ /**
+ * The size of the download buffer
+ */
+ size_t buf_size;
+
+ /**
+ * Error code (based on libc errno) if we failed to download
+ * (i.e. response too large).
+ */
+ int eno;
+
+};
+
+
+/**
+ * Callback used when downloading the reply to an HTTP request.
+ * Just appends all of the data to the `buf` in the
+ * `struct MAC_DownloadBuffer` for further processing. The size of
+ * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if
+ * the download exceeds this size, we abort with an error.
+ *
+ * Should be used by the various routines as the
+ * CURLOPT_WRITEFUNCTION. A `struct MAC_DownloadBuffer` needs to be
+ * passed to the CURLOPT_WRITEDATA.
+ *
+ * Afterwards, `eno` needs to be checked to ensure that the download
+ * completed correctly.
+ *
+ * @param bufptr data downloaded via HTTP
+ * @param size size of an item in @a bufptr
+ * @param nitems number of items in @a bufptr
+ * @param cls the `struct KeysRequest`
+ * @return number of bytes processed from @a bufptr
+ */
+size_t
+MAC_download_cb (char *bufptr,
+ size_t size,
+ size_t nitems,
+ void *cls);
+
+
+/**
+ * Obtain information about the final result about the
+ * HTTP download. If the download was successful, parses
+ * the JSON in the @a db and returns it. Also returns
+ * the HTTP @a response_code. If the download failed,
+ * the return value is NULL. The response code is set
+ * in any case, on download errors to zero.
+ *
+ * Calling this function also cleans up @a db.
+ *
+ * @param db download buffer
+ * @param eh CURL handle (to get the response code)
+ * @param[out] response_code set to the HTTP response code
+ * (or zero if we aborted the download, i.e.
+ * because the response was too big, or if
+ * the JSON we received was malformed).
+ * @return NULL if downloading a JSON reply failed
+ */
+json_t *
+MAC_download_get_result (struct MAC_DownloadBuffer *db,
+ CURL *eh,
+ long *response_code);
+
+
+/* end of merchant_api_context.h */
diff --git a/src/lib/merchant_api_json.c b/src/lib/merchant_api_json.c
new file mode 100644
index 00000000..9d03db65
--- /dev/null
+++ b/src/lib/merchant_api_json.c
@@ -0,0 +1,491 @@
+/*
+ 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 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/>
+*/
+/**
+ * @file lib/merchant_api_json.c
+ * @brief functions to parse incoming requests (JSON snippets)
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "merchant_api_json.h"
+
+/**
+ * Navigate and parse data in a JSON tree.
+ *
+ * @param root the JSON node to start the navigation at.
+ * @param spec parse specification array
+ * @return offset in @a spec where parsing failed, -1 on success (!)
+ */
+static int
+parse_json (json_t *root,
+ struct MAJ_Specification *spec)
+{
+ int i;
+ json_t *pos; /* what's our current position? */
+
+ pos = root;
+ for (i=0;MAJ_CMD_END != spec[i].cmd;i++)
+ {
+ pos = json_object_get (root,
+ spec[i].field);
+ if (NULL == pos)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ switch (spec[i].cmd)
+ {
+ case MAJ_CMD_END:
+ GNUNET_assert (0);
+ return i;
+ case MAJ_CMD_AMOUNT:
+ if (GNUNET_OK !=
+ TALER_json_to_amount (pos,
+ spec[i].details.amount))
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ break;
+ case MAJ_CMD_TIME_ABSOLUTE:
+ if (GNUNET_OK !=
+ TALER_json_to_abs (pos,
+ spec[i].details.abs_time))
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ break;
+
+ case MAJ_CMD_STRING:
+ {
+ const char *str;
+
+ str = json_string_value (pos);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ *spec[i].details.strptr = str;
+ }
+ break;
+
+ case MAJ_CMD_BINARY_FIXED:
+ {
+ const char *str;
+ int res;
+
+ str = json_string_value (pos);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ res = GNUNET_STRINGS_string_to_data (str, strlen (str),
+ spec[i].details.fixed_data.dest,
+ spec[i].details.fixed_data.dest_size);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ }
+ break;
+
+ case MAJ_CMD_BINARY_VARIABLE:
+ {
+ const char *str;
+ size_t size;
+ void *data;
+ int res;
+
+ str = json_string_value (pos);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ size = (strlen (str) * 5) / 8;
+ if (size >= 1024)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ data = GNUNET_malloc (size);
+ res = GNUNET_STRINGS_string_to_data (str,
+ strlen (str),
+ data,
+ size);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (data);
+ return i;
+ }
+ *spec[i].details.variable_data.dest_p = data;
+ *spec[i].details.variable_data.dest_size_p = size;
+ }
+ break;
+
+ case MAJ_CMD_RSA_PUBLIC_KEY:
+ {
+ size_t size;
+ const char *str;
+ int res;
+ void *buf;
+
+ str = json_string_value (pos);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ size = (strlen (str) * 5) / 8;
+ buf = GNUNET_malloc (size);
+ res = GNUNET_STRINGS_string_to_data (str,
+ strlen (str),
+ buf,
+ size);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_free (buf);
+ GNUNET_break_op (0);
+ return i;
+ }
+ *spec[i].details.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_decode (buf,
+ size);
+ GNUNET_free (buf);
+ if (NULL == spec[i].details.rsa_public_key)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ }
+ break;
+
+ case MAJ_CMD_RSA_SIGNATURE:
+ {
+ size_t size;
+ const char *str;
+ int res;
+ void *buf;
+
+ str = json_string_value (pos);
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ size = (strlen (str) * 5) / 8;
+ buf = GNUNET_malloc (size);
+ res = GNUNET_STRINGS_string_to_data (str,
+ strlen (str),
+ buf,
+ size);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_free (buf);
+ GNUNET_break_op (0);
+ return i;
+ }
+ *spec[i].details.rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_decode (buf,
+ size);
+ GNUNET_free (buf);
+ if (NULL == spec[i].details.rsa_signature)
+ return i;
+ }
+ break;
+
+ case MAJ_CMD_UINT16:
+ {
+ json_int_t val;
+
+ if (! json_is_integer (pos))
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ val = json_integer_value (pos);
+ if ( (0 > val) || (val > UINT16_MAX) )
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ *spec[i].details.u16 = (uint16_t) val;
+ }
+ break;
+
+ case MAJ_CMD_JSON_OBJECT:
+ {
+ if (! (json_is_object (pos) || json_is_array (pos)) )
+ {
+ GNUNET_break_op (0);
+ return i;
+ }
+ json_incref (pos);
+ *spec[i].details.obj = pos;
+ }
+ break;
+
+ default:
+ GNUNET_break (0);
+ return i;
+ }
+ }
+ return -1; /* all OK! */
+}
+
+
+/**
+ * Free all elements allocated during a
+ * #MAJ_parse_json() operation.
+ *
+ * @param spec specification of the parse operation
+ * @param end number of elements in @a spec to process
+ */
+static void
+parse_free (struct MAJ_Specification *spec,
+ int end)
+{
+ int i;
+
+ for (i=0;i<end;i++)
+ {
+ switch (spec[i].cmd)
+ {
+ case MAJ_CMD_END:
+ GNUNET_assert (0);
+ return;
+ case MAJ_CMD_AMOUNT:
+ break;
+ case MAJ_CMD_TIME_ABSOLUTE:
+ break;
+ case MAJ_CMD_BINARY_FIXED:
+ break;
+ case MAJ_CMD_STRING:
+ break;
+ case MAJ_CMD_BINARY_VARIABLE:
+ GNUNET_free (*spec[i].details.variable_data.dest_p);
+ *spec[i].details.variable_data.dest_p = NULL;
+ *spec[i].details.variable_data.dest_size_p = 0;
+ break;
+ case MAJ_CMD_RSA_PUBLIC_KEY:
+ GNUNET_CRYPTO_rsa_public_key_free (*spec[i].details.rsa_public_key);
+ *spec[i].details.rsa_public_key = NULL;
+ break;
+ case MAJ_CMD_RSA_SIGNATURE:
+ GNUNET_CRYPTO_rsa_signature_free (*spec[i].details.rsa_signature);
+ *spec[i].details.rsa_signature = NULL;
+ break;
+ case MAJ_CMD_JSON_OBJECT:
+ json_decref (*spec[i].details.obj);
+ *spec[i].details.obj = NULL;
+ break;
+ default:
+ GNUNET_break (0);
+ break;
+ }
+ }
+}
+
+
+/**
+ * Navigate and parse data in a JSON tree.
+ *
+ * @param root the JSON node to start the navigation at.
+ * @param spec parse specification array
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+int
+MAJ_parse_json (const json_t *root,
+ struct MAJ_Specification *spec)
+{
+ int ret;
+
+ ret = parse_json ((json_t *) root,
+ spec);
+ if (-1 == ret)
+ return GNUNET_OK;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "JSON field `%s` (%d) had unexpected value\n",
+ spec[ret].field,
+ ret);
+ parse_free (spec, ret);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Free all elements allocated during a
+ * #MAJ_parse_json() operation.
+ *
+ * @param spec specification of the parse operation
+ */
+void
+MAJ_parse_free (struct MAJ_Specification *spec)
+{
+ int i;
+
+ for (i=0;MAJ_CMD_END != spec[i].cmd;i++) ;
+ parse_free (spec, i);
+}
+
+
+/**
+ * The expected field stores a string.
+ *
+ * @param name name of the JSON field
+ * @param strptr where to store a pointer to the field
+ */
+struct MAJ_Specification
+MAJ_spec_string (const char *name,
+ const char **strptr)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_STRING,
+ .field = name,
+ .details.strptr = strptr
+ };
+ return ret;
+}
+
+
+/**
+ * Specification for parsing an absolute time value.
+ *
+ * @param name name of the JSON field
+ * @param at where to store the absolute time found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_absolute_time (const char *name,
+ struct GNUNET_TIME_Absolute *at)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_TIME_ABSOLUTE,
+ .field = name,
+ .details.abs_time = at
+ };
+ return ret;
+}
+
+
+/**
+ * Specification for parsing an amount value.
+ *
+ * @param name name of the JSON field
+ * @param amount where to store the amount found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_amount (const char *name,
+ struct TALER_Amount *amount)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_AMOUNT,
+ .field = name,
+ .details.amount = amount
+ };
+ return ret;
+}
+
+
+/**
+ * 16-bit integer.
+ *
+ * @param name name of the JSON field
+ * @param[out] u16 where to store the integer found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_uint16 (const char *name,
+ uint16_t *u16)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_UINT16,
+ .field = name,
+ .details.u16 = u16
+ };
+ return ret;
+}
+
+
+/**
+ * JSON object.
+ *
+ * @param name name of the JSON field
+ * @param[out] jsonp where to store the JSON found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_json (const char *name,
+ json_t **jsonp)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_JSON_OBJECT,
+ .field = name,
+ .details.obj = jsonp
+ };
+ return ret;
+}
+
+
+/**
+ * Specification for parsing an RSA public key.
+ *
+ * @param name name of the JSON field
+ * @param pk where to store the RSA key found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_rsa_public_key (const char *name,
+ struct GNUNET_CRYPTO_rsa_PublicKey **pk)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_RSA_PUBLIC_KEY,
+ .field = name,
+ .details.rsa_public_key = pk
+ };
+ return ret;
+}
+
+
+/**
+ * Specification for parsing an RSA signature.
+ *
+ * @param name name of the JSON field
+ * @param sig where to store the RSA signature found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_rsa_signature (const char *name,
+ struct GNUNET_CRYPTO_rsa_Signature **sig)
+{
+ struct MAJ_Specification ret =
+ {
+ .cmd = MAJ_CMD_RSA_SIGNATURE,
+ .field = name,
+ .details.rsa_signature = sig
+ };
+ return ret;
+}
+
+
+/* end of merchant_api_json.c */
diff --git a/src/lib/merchant_api_json.h b/src/lib/merchant_api_json.h
new file mode 100644
index 00000000..5f5265cf
--- /dev/null
+++ b/src/lib/merchant_api_json.h
@@ -0,0 +1,331 @@
+/*
+ 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 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/>
+*/
+/**
+ * @file lib/merchant_api_json.h
+ * @brief functions to parse incoming requests (JSON snippets)
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <taler/taler_util.h>
+#include <jansson.h>
+
+
+/**
+ * Enumeration with the various commands for the
+ * #MAJ_parse_json interpreter.
+ */
+enum MAJ_Command
+{
+
+ /**
+ * End of command list.
+ */
+ MAJ_CMD_END,
+
+ /**
+ * Parse amount at current position.
+ */
+ MAJ_CMD_AMOUNT,
+
+ /**
+ * Parse absolute time at current position.
+ */
+ MAJ_CMD_TIME_ABSOLUTE,
+
+ /**
+ * Parse fixed binary value at current position.
+ */
+ MAJ_CMD_BINARY_FIXED,
+
+ /**
+ * Parse variable-size binary value at current position.
+ */
+ MAJ_CMD_BINARY_VARIABLE,
+
+ /**
+ * Parse RSA public key at current position.
+ */
+ MAJ_CMD_RSA_PUBLIC_KEY,
+
+ /**
+ * Parse RSA signature at current position.
+ */
+ MAJ_CMD_RSA_SIGNATURE,
+
+ /**
+ * Parse `const char *` JSON string at current position.
+ */
+ MAJ_CMD_STRING,
+
+ /**
+ * Parse `uint16_t` integer at the current position.
+ */
+ MAJ_CMD_UINT16,
+
+ /**
+ * Parse JSON object at the current position.
+ */
+ MAJ_CMD_JSON_OBJECT,
+
+ /**
+ * Parse ??? at current position.
+ */
+ MAJ_CMD_C
+
+};
+
+
+/**
+ * @brief Entry in parser specification for #MAJ_parse_json.
+ */
+struct MAJ_Specification
+{
+
+ /**
+ * Command to execute.
+ */
+ enum MAJ_Command cmd;
+
+ /**
+ * Name of the field to access.
+ */
+ const char *field;
+
+ /**
+ * Further details for the command.
+ */
+ union {
+
+ /**
+ * Where to store amount for #MAJ_CMD_AMOUNT.
+ */
+ struct TALER_Amount *amount;
+
+ /**
+ * Where to store time, for #MAJ_CMD_TIME_ABSOLUTE.
+ */
+ struct GNUNET_TIME_Absolute *abs_time;
+
+ /**
+ * Where to write binary data, for #MAJ_CMD_BINARY_FIXED.
+ */
+ struct {
+ /**
+ * Where to write the data.
+ */
+ void *dest;
+
+ /**
+ * How many bytes to write to @e dest.
+ */
+ size_t dest_size;
+
+ } fixed_data;
+
+ /**
+ * Where to write binary data, for #MAJ_CMD_BINARY_VARIABLE.
+ */
+ struct {
+ /**
+ * Where to store the pointer with the data (is allocated).
+ */
+ void **dest_p;
+
+ /**
+ * Where to store the number of bytes allocated at `*dest`.
+ */
+ size_t *dest_size_p;
+
+ } variable_data;
+
+ /**
+ * Where to store the RSA public key for #MAJ_CMD_RSA_PUBLIC_KEY
+ */
+ struct GNUNET_CRYPTO_rsa_PublicKey **rsa_public_key;
+
+ /**
+ * Where to store the RSA signature for #MAJ_CMD_RSA_SIGNATURE
+ */
+ struct GNUNET_CRYPTO_rsa_Signature **rsa_signature;
+
+ /**
+ * Details for #MAJ_CMD_EDDSA_SIGNATURE
+ */
+ struct {
+
+ /**
+ * Where to store the purpose.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose **purpose_p;
+
+ /**
+ * Key to verify the signature against.
+ */
+ const struct GNUNET_CRYPTO_EddsaPublicKey *pub_key;
+
+ } eddsa_signature;
+
+ /**
+ * Where to store a pointer to the string.
+ */
+ const char **strptr;
+
+ /**
+ * Where to store 16-bit integer.
+ */
+ uint16_t *u16;
+
+ /**
+ * Where to store a JSON object.
+ */
+ json_t **obj;
+
+ } details;
+
+};
+
+
+/**
+ * Navigate and parse data in a JSON tree.
+ *
+ * @param root the JSON node to start the navigation at.
+ * @param spec parse specification array
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+int
+MAJ_parse_json (const json_t *root,
+ struct MAJ_Specification *spec);
+
+
+/**
+ * Free all elements allocated during a
+ * #MAJ_parse_json() operation.
+ *
+ * @param spec specification of the parse operation
+ */
+void
+MAJ_parse_free (struct MAJ_Specification *spec);
+
+
+/**
+ * End of a parser specification.
+ */
+#define MAJ_spec_end { .cmd = MAJ_CMD_END }
+
+/**
+ * Fixed size object (in network byte order, encoded using Crockford
+ * Base32hex encoding).
+ *
+ * @param name name of the JSON field
+ * @param obj pointer where to write the data (type of `*obj` will determine size)
+ */
+#define MAJ_spec_fixed_auto(name,obj) { .cmd = MAJ_CMD_BINARY_FIXED, .field = name, .details.fixed_data.dest = obj, .details.fixed_data.dest_size = sizeof (*obj) }
+
+
+/**
+ * Variable size object (in network byte order, encoded using Crockford
+ * Base32hex encoding).
+ *
+ * @param name name of the JSON field
+ * @param obj pointer where to write the data (a `void **`)
+ * @param size where to store the number of bytes allocated for @a obj (of type `size_t *`
+ */
+#define MAJ_spec_varsize(name,obj,size) { .cmd = MAJ_CMD_BINARY_VARIABLE, .field = name, .details.variable_data.dest_p = obj, .details.variable_data.dest_size_p = size }
+
+
+/**
+ * The expected field stores a string.
+ *
+ * @param name name of the JSON field
+ * @param strptr where to store a pointer to the field
+ */
+struct MAJ_Specification
+MAJ_spec_string (const char *name,
+ const char **strptr);
+
+
+/**
+ * Absolute time.
+ *
+ * @param name name of the JSON field
+ * @param[out] at where to store the absolute time found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_absolute_time (const char *name,
+ struct GNUNET_TIME_Absolute *at);
+
+
+/**
+ * 16-bit integer.
+ *
+ * @param name name of the JSON field
+ * @param[out] u16 where to store the integer found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_uint16 (const char *name,
+ uint16_t *u16);
+
+
+/**
+ * JSON object.
+ *
+ * @param name name of the JSON field
+ * @param[out] jsonp where to store the JSON found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_json (const char *name,
+ json_t **jsonp);
+
+
+/**
+ * Specification for parsing an amount value.
+ *
+ * @param name name of the JSON field
+ * @param amount where to store the amount under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_amount (const char *name,
+ struct TALER_Amount *amount);
+
+
+/**
+ * Specification for parsing an RSA public key.
+ *
+ * @param name name of the JSON field
+ * @param pk where to store the RSA key found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_rsa_public_key (const char *name,
+ struct GNUNET_CRYPTO_rsa_PublicKey **pk);
+
+
+/**
+ * Specification for parsing an RSA signature.
+ *
+ * @param name name of the JSON field
+ * @param sig where to store the RSA signature found under @a name
+ */
+struct MAJ_Specification
+MAJ_spec_rsa_signature (const char *name,
+ struct GNUNET_CRYPTO_rsa_Signature **sig);
+
+
+
+
+/* end of merchant_api_json.h */
diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c
new file mode 100644
index 00000000..e33da423
--- /dev/null
+++ b/src/lib/merchant_api_pay.c
@@ -0,0 +1,372 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
+
+ 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, If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/merchant_api_pay.c
+ * @brief Implementation of the /pay request of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_json.h"
+#include "merchant_api_context.h"
+#include <taler/taler_signatures.h>
+
+
+/**
+ * @brief A Pay Handle
+ */
+struct TALER_MERCHANT_Pay
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * JSON encoding of the request to POST.
+ */
+ char *json_enc;
+
+ /**
+ * Handle for the request.
+ */
+ struct MAC_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_PayCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Download buffer
+ */
+ struct MAC_DownloadBuffer db;
+
+};
+
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /pay request.
+ *
+ * @param cls the `struct TALER_MERCHANT_Pay`
+ * @param eh the curl request handle
+ */
+static void
+handle_pay_finished (void *cls,
+ CURL *eh)
+{
+ struct TALER_MERCHANT_Pay *ph = cls;
+ long response_code;
+ json_t *json;
+
+ ph->job = NULL;
+ json = MAC_download_get_result (&ph->db,
+ eh,
+ &response_code);
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the merchant is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Nothing really to verify, merchant 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",
+ response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ ph->cb (ph->cb_cls,
+ response_code,
+ json);
+ json_decref (json);
+ TALER_MERCHANT_pay_cancel (ph);
+}
+
+
+/**
+ * Pay a merchant. API for wallets that have the coin's private keys.
+ *
+ * @param merchant the merchant context
+ * @param mint_uri URI of the mint that the coins belong to
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract hash of the contact of the merchant with the customer
+ * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant
+ * @param transaction_id transaction id for the transaction between merchant and customer
+ * @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 merchant (can be zero if refunds are not allowed)
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param pay_cb the callback to call when a reply for this request is available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_Pay *
+TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant,
+ const char *mint_uri,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract,
+ struct GNUNET_TIME_Absolute timestamp,
+ uint64_t transaction_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ unsigned int num_coins,
+ const struct TLAER_MERCHANT_PayCoin *coins,
+ TALER_MERCHANT_PayCallback pay_cb,
+ void *pay_cb_cls)
+{
+ struct TALER_MERCHANT_Pay *ph;
+ json_t *pay_obj;
+ json_t *j_coins;
+ CURL *eh;
+ struct GNUNET_HashCode h_wire;
+ struct TALER_Amount amount_without_fee;
+ unsigned int i;
+ struct TALER_DepositRequestPS dr;
+
+ dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
+ dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS));
+ dr.h_contract = *h_contract;
+ dr.h_wire = *h_wire;
+ dr.timestamp = GNUNET_TIME_absolute_hton (timestamp);
+ dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
+ dr.transaction_id = GNUNET_htonll (transaction_id);
+ dr.merchant = *merchant_pub;
+ j_coins = json_array ();
+ for (i=0;i<num_coins;i++)
+ {
+ json_t *j_coin;
+ const struct TALER_MERCHANT_PayCoin *pc = &coins[i];
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_Amount fee;
+
+ /* prepare 'dr' for this coin to generate coin signature */
+ GNUNET_CRYPTO_ecdhe_key_get_public (&pc->coin_priv.edche_priv,
+ &dr.coin_pub.ecdhe_pub);
+ TALER_amount_hton (&dr.amount_with_fee,
+ &pc->amount_with_fee);
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&fee,
+ &pc->amount_with_fee,
+ &pc->fee))
+ {
+ /* Integer underflow, fee larger than total amount?
+ This should not happen (client violated API!) */
+ GNUNET_break (0);
+ json_decref (j_coins);
+ return NULL;
+ }
+ TALER_amount_hton (&dr.deposit_fee,
+ &fee);
+ GNUNET_CRYPTO_eddsa_sign (&pc->coin_priv.eddsa_priv,
+ &dr.purpose,
+ &coin_sig.eddsa_sig);
+
+ /* create JSON for this coin */
+ j_coin = json_pack ("{s:o, s:o," /* f/coin_pub */
+ " s:o, s:o," /* denom_pub / ub_sig */
+ " s:o}", /* coin_sig */
+ "f", TALER_json_from_amount (&pc->amount_with_fee),
+ "coin_pub", TALER_json_from_data (&dr.coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP)),
+ "denom_pub", TALER_json_from_rsa_public_key (pc->denom_pub.rsa_public_key),
+ "ub_sig", TALER_json_from_rsa_signature (pc->denom_sig.rsa_signature),
+ "coin_sig", TALER_json_from_data (&coin_sig,
+ sizeof (coin_sig))
+ );
+ json_array_append (j_coins,
+ j_coin);
+ }
+
+
+ pay_obj = json_pack ("{s:o, s:o," /* H_wire/H_contract */
+ " s:I, s:o," /* transaction id, timestamp */
+ " s:o, s:s," /* refund_deadline, mint */
+ " s:o, s:o," /* coins, max_fee */
+ " s:o}", /* amount */
+ "H_wire", TALER_json_from_data (&h_wire,
+ sizeof (h_wire)),
+ "H_contract", TALER_json_from_data (h_contract,
+ sizeof (struct GNUNET_HashCode)),
+ "transaction_id", (json_int_t) transaction_id,
+ "timestamp", TALER_json_from_abs (timestamp),
+ "refund_deadline", TALER_json_from_abs (refund_deadline),
+ "mint", mint_uri,
+ "coins", j_coins,
+ "max_fee", TALER_json_from_amount (max_fee),
+ "amount", TALER_json_from_amount (amount)
+ );
+
+ // optionally: add edate! "edate", TALER_json_from_abs (wire_deadline),
+
+ ph = GNUNET_new (struct TALER_MERCHANT_Pay);
+#if 0
+ ph->merchant = merchant;
+ ph->cb = cb;
+ ph->cb_cls = cb_cls;
+ ph->url = MAH_path_to_url (merchant, "/pay");
+ ph->depconf.purpose.size = htonl (sizeof (struct TALER_PayConfirmationPS));
+ ph->depconf.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONFIRM_PAY);
+ ph->depconf.h_contract = *h_contract;
+ ph->depconf.h_wire = h_wire;
+ ph->depconf.transaction_id = GNUNET_htonll (transaction_id);
+ ph->depconf.timestamp = GNUNET_TIME_absolute_hton (timestamp);
+ ph->depconf.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
+ TALER_amount_subtract (&amount_without_fee,
+ amount,
+ &dki->fee_pay);
+ TALER_amount_hton (&ph->depconf.amount_without_fee,
+ &amount_without_fee);
+ ph->depconf.coin_pub = *coin_pub;
+ ph->depconf.merchant = *merchant_pub;
+ ph->amount_with_fee = *amount;
+ ph->coin_value = dki->value;
+
+ eh = curl_easy_init ();
+ GNUNET_assert (NULL != (ph->json_enc =
+ json_dumps (pay_obj,
+ JSON_COMPACT)));
+ json_decref (pay_obj);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ 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)));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_WRITEFUNCTION,
+ &MAC_download_cb));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_WRITEDATA,
+ &ph->db));
+ ctx = MAH_handle_to_context (merchant);
+ ph->job = MAC_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_pay_finished,
+ ph);
+ return ph;
+#endif
+ return NULL;
+}
+
+
+
+/**
+ * Pay a merchant. API for frontends talking to backends. Here,
+ * the frontend does not have the coin's private keys, but just
+ * the public keys and signatures. Note the sublte difference
+ * in the type of @a coins compared to #TALER_MERCHANT_pay().
+ *
+ * @param merchant the merchant context
+ * @param mint_uri URI of the mint that the coins belong to
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract hash of the contact of the merchant with the customer
+ * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant
+ * @param transaction_id transaction id for the transaction between merchant and customer
+ * @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 merchant (can be zero if refunds are not allowed)
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param pay_cb the callback to call when a reply for this request is available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_Pay *
+TALER_MERCHANT_pay_frontend (struct TALER_MERCHANT_Context *merchant,
+ const char *mint_uri,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract,
+ struct GNUNET_TIME_Absolute timestamp,
+ uint64_t transaction_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_PaidCoin *coins,
+ TALER_MERCHANT_PayCallback pay_cb,
+ void *pay_cb_cls)
+{
+ GNUNET_break (0); // FIXME: not implemented!
+ return NULL;
+}
+
+
+/**
+ * Cancel a pay permission request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param pay the pay permission request handle
+ */
+void
+TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *pay)
+{
+ if (NULL != pay->job)
+ {
+ MAC_job_cancel (pay->job);
+ pay->job = NULL;
+ }
+ GNUNET_free_non_null (pay->db.buf);
+ GNUNET_free (pay->url);
+ GNUNET_free (pay->json_enc);
+ GNUNET_free (pay);
+}
+
+
+/* end of merchant_api_pay.c */
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
new file mode 100644
index 00000000..c264f9a3
--- /dev/null
+++ b/src/lib/test_merchant_api.c
@@ -0,0 +1,1345 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
+
+ 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, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/test_merchant_api.c
+ * @brief testcase to test merchant's HTTP API interface
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_mint_service.h"
+#include "taler_merchant_service.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+
+/**
+ * Main execution context for the main loop of the mint.
+ */
+static struct TALER_MINT_Context *ctx;
+
+/**
+ * Handle to access the mint.
+ */
+static struct TALER_MINT_Handle *mint;
+
+/**
+ * Main execution context for the main loop of the mint.
+ */
+static struct TALER_MERCHANT_Context *merchant;
+
+/**
+ * Task run on shutdown.
+ */
+static struct GNUNET_SCHEDULER_Task *shutdown_task;
+
+/**
+ * Task that runs the main event loop.
+ */
+static struct GNUNET_SCHEDULER_Task *ctx_task;
+
+/**
+ * Result of the testcases, #GNUNET_OK on success
+ */
+static int result;
+
+
+/**
+ * Opcodes for the interpreter.
+ */
+enum OpCode
+{
+ /**
+ * Termination code, stops the interpreter loop (with success).
+ */
+ OC_END = 0,
+
+ /**
+ * Add funds to a reserve by (faking) incoming wire transfer.
+ */
+ OC_ADMIN_ADD_INCOMING,
+
+ /**
+ * Check status of a reserve.
+ */
+ OC_WITHDRAW_STATUS,
+
+ /**
+ * Withdraw a coin from a reserve.
+ */
+ OC_WITHDRAW_SIGN,
+
+ /**
+ * Pay with coins.
+ */
+ OC_PAY
+
+};
+
+
+/**
+ * Structure specifying details about a coin to be melted.
+ * Used in a NULL-terminated array as part of command
+ * specification.
+ */
+struct MeltDetails
+{
+
+ /**
+ * Amount to melt (including fee).
+ */
+ const char *amount;
+
+ /**
+ * Reference to reserve_withdraw operations for coin to
+ * be used for the /refresh/melt operation.
+ */
+ const char *coin_ref;
+
+};
+
+
+/**
+ * Information about a fresh coin generated by the refresh operation.
+ */
+struct FreshCoin
+{
+
+ /**
+ * If @e amount is NULL, this specifies the denomination key to
+ * use. Otherwise, this will be set (by the interpreter) to the
+ * denomination PK matching @e amount.
+ */
+ const struct TALER_MINT_DenomPublicKey *pk;
+
+ /**
+ * Set (by the interpreter) to the mint's signature over the
+ * coin's public key.
+ */
+ struct TALER_DenominationSignature sig;
+
+ /**
+ * Set (by the interpreter) to the coin's private key.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+};
+
+
+/**
+ * Details for a mint operation to execute.
+ */
+struct Command
+{
+ /**
+ * Opcode of the command.
+ */
+ enum OpCode oc;
+
+ /**
+ * Label for the command, can be NULL.
+ */
+ const char *label;
+
+ /**
+ * Which response code do we expect for this command?
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Details about the command.
+ */
+ union
+ {
+
+ /**
+ * Information for a #OC_ADMIN_ADD_INCOMING command.
+ */
+ struct
+ {
+
+ /**
+ * Label to another admin_add_incoming command if we
+ * should deposit into an existing reserve, NULL if
+ * a fresh reserve should be created.
+ */
+ const char *reserve_reference;
+
+ /**
+ * String describing the amount to add to the reserve.
+ */
+ const char *amount;
+
+ /**
+ * Wire details (JSON).
+ */
+ const char *wire;
+
+ /**
+ * Set (by the interpreter) to the reserve's private key
+ * we used to fill the reserve.
+ */
+ struct TALER_ReservePrivateKeyP reserve_priv;
+
+ /**
+ * Set to the API's handle during the operation.
+ */
+ struct TALER_MINT_AdminAddIncomingHandle *aih;
+
+ } admin_add_incoming;
+
+ /**
+ * Information for a #OC_WITHDRAW_STATUS command.
+ */
+ struct
+ {
+
+ /**
+ * Label to the #OC_ADMIN_ADD_INCOMING command which
+ * created the reserve.
+ */
+ const char *reserve_reference;
+
+ /**
+ * Set to the API's handle during the operation.
+ */
+ struct TALER_MINT_ReserveStatusHandle *wsh;
+
+ /**
+ * Expected reserve balance.
+ */
+ const char *expected_balance;
+
+ } reserve_status;
+
+ /**
+ * Information for a #OC_WITHDRAW_SIGN command.
+ */
+ struct
+ {
+
+ /**
+ * Which reserve should we withdraw from?
+ */
+ const char *reserve_reference;
+
+ /**
+ * String describing the denomination value we should withdraw.
+ * A corresponding denomination key must exist in the mint's
+ * offerings. Can be NULL if @e pk is set instead.
+ */
+ const char *amount;
+
+ /**
+ * If @e amount is NULL, this specifies the denomination key to
+ * use. Otherwise, this will be set (by the interpreter) to the
+ * denomination PK matching @e amount.
+ */
+ const struct TALER_MINT_DenomPublicKey *pk;
+
+ /**
+ * Set (by the interpreter) to the mint's signature over the
+ * coin's public key.
+ */
+ struct TALER_DenominationSignature sig;
+
+ /**
+ * Set (by the interpreter) to the coin's private key.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Blinding key used for the operation.
+ */
+ struct TALER_DenominationBlindingKey blinding_key;
+
+ /**
+ * Withdraw handle (while operation is running).
+ */
+ struct TALER_MINT_ReserveWithdrawHandle *wsh;
+
+ } reserve_withdraw;
+
+ /**
+ * Information for a #OC_DEPOSIT command.
+ */
+ struct
+ {
+
+ /**
+ * Amount to pay.
+ */
+ const char *amount;
+
+ /**
+ * Reference to a reserve_withdraw operation for a coin to
+ * be used for the /deposit operation.
+ */
+ const char *coin_ref;
+
+ /**
+ * If this @e coin_ref refers to an operation that generated
+ * an array of coins, this value determines which coin to use.
+ */
+ unsigned int coin_idx;
+
+ /**
+ * JSON string describing the merchant's "wire details".
+ */
+ const char *wire_details;
+
+ /**
+ * JSON string describing the contract between the two parties.
+ */
+ const char *contract;
+
+ /**
+ * Transaction ID to use.
+ */
+ uint64_t transaction_id;
+
+ /**
+ * 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 of the merchant,
+ * if @e refund_deadline is non-zero.
+ */
+ struct TALER_MerchantPrivateKeyP merchant_priv;
+
+ /**
+ * Deposit handle while operation is running.
+ */
+ struct TALER_MINT_DepositHandle *dh;
+
+ } pay;
+
+ } details;
+
+};
+
+
+/**
+ * State of the interpreter loop.
+ */
+struct InterpreterState
+{
+ /**
+ * Keys from the mint.
+ */
+ const struct TALER_MINT_Keys *keys;
+
+ /**
+ * Commands the interpreter will run.
+ */
+ struct Command *commands;
+
+ /**
+ * Interpreter task (if one is scheduled).
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Instruction pointer. Tells #interpreter_run() which
+ * instruction to run next.
+ */
+ unsigned int ip;
+
+};
+
+
+/**
+ * Task that runs the context's event loop with the GNUnet scheduler.
+ *
+ * @param cls unused
+ * @param tc scheduler context (unused)
+ */
+static void
+context_task (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc);
+
+
+/**
+ * Run the context task, the working set has changed.
+ */
+static void
+trigger_context_task ()
+{
+ GNUNET_SCHEDULER_cancel (ctx_task);
+ ctx_task = GNUNET_SCHEDULER_add_now (&context_task,
+ NULL);
+}
+
+
+/**
+ * The testcase failed, return with an error code.
+ *
+ * @param is interpreter state to clean up
+ */
+static void
+fail (struct InterpreterState *is)
+{
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * Find a command by label.
+ *
+ * @param is interpreter state to search
+ * @param label label to look for
+ * @return NULL if command was not found
+ */
+static const struct Command *
+find_command (const struct InterpreterState *is,
+ const char *label)
+{
+ unsigned int i;
+ const struct Command *cmd;
+
+ if (NULL == label)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Attempt to lookup command for empty label\n");
+ return NULL;
+ }
+ for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++)
+ if ( (NULL != cmd->label) &&
+ (0 == strcmp (cmd->label,
+ label)) )
+ return cmd;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command not found: %s\n",
+ label);
+ return NULL;
+}
+
+
+/**
+ * Run the main interpreter loop that performs mint operations.
+ *
+ * @param cls contains the `struct InterpreterState`
+ * @param tc scheduler context
+ */
+static void
+interpreter_run (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc);
+
+
+/**
+ * Function called upon completion of our /admin/add/incoming request.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
+ * 0 if the mint's reply is bogus (fails to follow the protocol)
+ * @param full_response full response from the mint (for logging, in case of errors)
+ */
+static void
+add_incoming_cb (void *cls,
+ unsigned int http_status,
+ json_t *full_response)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+
+ cmd->details.admin_add_incoming.aih = NULL;
+ if (MHD_HTTP_OK != http_status)
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ is->ip++;
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Check if the given historic event @a h corresponds to the given
+ * command @a cmd.
+ *
+ * @param h event in history
+ * @param cmd an #OC_ADMIN_ADD_INCOMING command
+ * @return #GNUNET_OK if they match, #GNUNET_SYSERR if not
+ */
+static int
+compare_admin_add_incoming_history (const struct TALER_MINT_ReserveHistory *h,
+ const struct Command *cmd)
+{
+ struct TALER_Amount amount;
+
+ if (TALER_MINT_RTT_DEPOSIT != h->type)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (cmd->details.admin_add_incoming.amount,
+ &amount));
+ if (0 != TALER_amount_cmp (&amount,
+ &h->amount))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check if the given historic event @a h corresponds to the given
+ * command @a cmd.
+ *
+ * @param h event in history
+ * @param cmd an #OC_WITHDRAW_SIGN command
+ * @return #GNUNET_OK if they match, #GNUNET_SYSERR if not
+ */
+static int
+compare_reserve_withdraw_history (const struct TALER_MINT_ReserveHistory *h,
+ const struct Command *cmd)
+{
+ struct TALER_Amount amount;
+ struct TALER_Amount amount_with_fee;
+
+ if (TALER_MINT_RTT_WITHDRAWAL != h->type)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (cmd->details.reserve_withdraw.amount,
+ &amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&amount_with_fee,
+ &amount,
+ &cmd->details.reserve_withdraw.pk->fee_withdraw));
+ if (0 != TALER_amount_cmp (&amount_with_fee,
+ &h->amount))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with the result of a /reserve/status request.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
+ * 0 if the mint's reply is bogus (fails to follow the protocol)
+ * @param[in] json original response in JSON format (useful only for diagnostics)
+ * @param balance current balance in the reserve, NULL on error
+ * @param history_length number of entries in the transaction history, 0 on error
+ * @param history detailed transaction history, NULL on error
+ */
+static void
+reserve_status_cb (void *cls,
+ unsigned int http_status,
+ json_t *json,
+ const struct TALER_Amount *balance,
+ unsigned int history_length,
+ const struct TALER_MINT_ReserveHistory *history)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+ struct Command *rel;
+ unsigned int i;
+ unsigned int j;
+ struct TALER_Amount amount;
+
+ cmd->details.reserve_status.wsh = NULL;
+ if (cmd->expected_response_code != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s\n",
+ http_status,
+ cmd->label);
+ GNUNET_break (0);
+ json_dumpf (json, stderr, 0);
+ fail (is);
+ return;
+ }
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ /* FIXME: note that history events may come in a different
+ order than the commands. However, for now this works... */
+ j = 0;
+ for (i=0;i<is->ip;i++)
+ {
+ switch ((rel = &is->commands[i])->oc)
+ {
+ case OC_ADMIN_ADD_INCOMING:
+ if ( ( (NULL != rel->label) &&
+ (0 == strcmp (cmd->details.reserve_status.reserve_reference,
+ rel->label) ) ) ||
+ ( (NULL != rel->details.admin_add_incoming.reserve_reference) &&
+ (0 == strcmp (cmd->details.reserve_status.reserve_reference,
+ rel->details.admin_add_incoming.reserve_reference) ) ) )
+ {
+ if (GNUNET_OK !=
+ compare_admin_add_incoming_history (&history[j],
+ rel))
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ j++;
+ }
+ break;
+ case OC_WITHDRAW_SIGN:
+ if (0 == strcmp (cmd->details.reserve_status.reserve_reference,
+ rel->details.reserve_withdraw.reserve_reference))
+ {
+ if (GNUNET_OK !=
+ compare_reserve_withdraw_history (&history[j],
+ rel))
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ j++;
+ }
+ break;
+ default:
+ /* unreleated, just skip */
+ break;
+ }
+ }
+ if (j != history_length)
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ if (NULL != cmd->details.reserve_status.expected_balance)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (cmd->details.reserve_status.expected_balance,
+ &amount));
+ if (0 != TALER_amount_cmp (&amount,
+ balance))
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ }
+ break;
+ default:
+ /* Unsupported status code (by test harness) */
+ GNUNET_break (0);
+ break;
+ }
+ is->ip++;
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Function called upon completion of our /reserve/withdraw request.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
+ * 0 if the mint's reply is bogus (fails to follow the protocol)
+ * @param sig signature over the coin, NULL on error
+ * @param full_response full response from the mint (for logging, in case of errors)
+ */
+static void
+reserve_withdraw_cb (void *cls,
+ unsigned int http_status,
+ const struct TALER_DenominationSignature *sig,
+ json_t *full_response)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+
+ cmd->details.reserve_withdraw.wsh = NULL;
+ if (cmd->expected_response_code != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s\n",
+ http_status,
+ cmd->label);
+ json_dumpf (full_response, stderr, 0);
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ if (NULL == sig)
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ cmd->details.reserve_withdraw.sig.rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature);
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ /* nothing to check */
+ break;
+ default:
+ /* Unsupported status code (by test harness) */
+ GNUNET_break (0);
+ break;
+ }
+ is->ip++;
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Function called with the result of a /pay operation.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit;
+ * 0 if the mint's reply is bogus (fails to follow the protocol)
+ * @param obj the received JSON reply, should be kept as proof (and, in case of errors,
+ * be forwarded to the customer)
+ */
+static void
+pay_cb (void *cls,
+ unsigned int http_status,
+ const char *redirect_uri,
+ json_t *obj)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+
+ cmd->details.deposit.dh = NULL;
+ if (cmd->expected_response_code != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s\n",
+ http_status,
+ cmd->label);
+ json_dumpf (obj, stderr, 0);
+ fail (is);
+ return;
+ }
+ is->ip++;
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Find denomination key matching the given amount.
+ *
+ * @param keys array of keys to search
+ * @param amount coin value to look for
+ * @return NULL if no matching key was found
+ */
+static const struct TALER_MINT_DenomPublicKey *
+find_pk (const struct TALER_MINT_Keys *keys,
+ const struct TALER_Amount *amount)
+{
+ unsigned int i;
+ struct GNUNET_TIME_Absolute now;
+ struct TALER_MINT_DenomPublicKey *pk;
+ char *str;
+
+ now = GNUNET_TIME_absolute_get ();
+ for (i=0;i<keys->num_denom_keys;i++)
+ {
+ pk = &keys->denom_keys[i];
+ if ( (0 == TALER_amount_cmp (amount,
+ &pk->value)) &&
+ (now.abs_value_us >= pk->valid_from.abs_value_us) &&
+ (now.abs_value_us < pk->withdraw_valid_until.abs_value_us) )
+ return pk;
+ }
+ /* do 2nd pass to check if expiration times are to blame for failure */
+ str = TALER_amount_to_string (amount);
+ for (i=0;i<keys->num_denom_keys;i++)
+ {
+ pk = &keys->denom_keys[i];
+ if ( (0 == TALER_amount_cmp (amount,
+ &pk->value)) &&
+ ( (now.abs_value_us < pk->valid_from.abs_value_us) ||
+ (now.abs_value_us > pk->withdraw_valid_until.abs_value_us) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Have denomination key for `%s', but with wrong expiration range %llu vs [%llu,%llu)\n",
+ str,
+ now.abs_value_us,
+ pk->valid_from.abs_value_us,
+ pk->withdraw_valid_until.abs_value_us);
+ GNUNET_free (str);
+ return NULL;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No denomination key for amount %s found\n",
+ str);
+ GNUNET_free (str);
+ return NULL;
+}
+
+
+/**
+ * Run the main interpreter loop that performs mint operations.
+ *
+ * @param cls contains the `struct InterpreterState`
+ * @param tc scheduler context
+ */
+static void
+interpreter_run (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+ const struct Command *ref;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_Amount amount;
+ struct GNUNET_TIME_Absolute execution_date;
+ json_t *wire;
+
+ is->task = NULL;
+ if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
+ {
+ fprintf (stderr,
+ "Test aborted by shutdown request\n");
+ fail (is);
+ return;
+ }
+ switch (cmd->oc)
+ {
+ case OC_END:
+ result = GNUNET_OK;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case OC_ADMIN_ADD_INCOMING:
+ if (NULL !=
+ cmd->details.admin_add_incoming.reserve_reference)
+ {
+ ref = find_command (is,
+ cmd->details.admin_add_incoming.reserve_reference);
+ GNUNET_assert (NULL != ref);
+ GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
+ cmd->details.admin_add_incoming.reserve_priv
+ = ref->details.admin_add_incoming.reserve_priv;
+ }
+ else
+ {
+ struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
+
+ priv = GNUNET_CRYPTO_eddsa_key_create ();
+ cmd->details.admin_add_incoming.reserve_priv.eddsa_priv = *priv;
+ GNUNET_free (priv);
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.admin_add_incoming.reserve_priv.eddsa_priv,
+ &reserve_pub.eddsa_pub);
+ if (GNUNET_OK !=
+ TALER_string_to_amount (cmd->details.admin_add_incoming.amount,
+ &amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %u\n",
+ cmd->details.admin_add_incoming.amount,
+ is->ip);
+ fail (is);
+ return;
+ }
+ wire = json_loads (cmd->details.admin_add_incoming.wire,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == wire)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse wire details `%s' at %u\n",
+ cmd->details.admin_add_incoming.wire,
+ is->ip);
+ fail (is);
+ return;
+ }
+ execution_date = GNUNET_TIME_absolute_get ();
+ TALER_round_abs_time (&execution_date);
+ cmd->details.admin_add_incoming.aih
+ = TALER_MINT_admin_add_incoming (mint,
+ &reserve_pub,
+ &amount,
+ execution_date,
+ wire,
+ &add_incoming_cb,
+ is);
+ if (NULL == cmd->details.admin_add_incoming.aih)
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ trigger_context_task ();
+ return;
+ case OC_WITHDRAW_STATUS:
+ GNUNET_assert (NULL !=
+ cmd->details.reserve_status.reserve_reference);
+ ref = find_command (is,
+ cmd->details.reserve_status.reserve_reference);
+ GNUNET_assert (NULL != ref);
+ GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
+ GNUNET_CRYPTO_eddsa_key_get_public (&ref->details.admin_add_incoming.reserve_priv.eddsa_priv,
+ &reserve_pub.eddsa_pub);
+ cmd->details.reserve_status.wsh
+ = TALER_MINT_reserve_status (mint,
+ &reserve_pub,
+ &reserve_status_cb,
+ is);
+ trigger_context_task ();
+ return;
+ case OC_WITHDRAW_SIGN:
+ GNUNET_assert (NULL !=
+ cmd->details.reserve_withdraw.reserve_reference);
+ ref = find_command (is,
+ cmd->details.reserve_withdraw.reserve_reference);
+ GNUNET_assert (NULL != ref);
+ GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc);
+ if (NULL != cmd->details.reserve_withdraw.amount)
+ {
+ if (GNUNET_OK !=
+ TALER_string_to_amount (cmd->details.reserve_withdraw.amount,
+ &amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %u\n",
+ cmd->details.reserve_withdraw.amount,
+ is->ip);
+ fail (is);
+ return;
+ }
+ cmd->details.reserve_withdraw.pk = find_pk (is->keys,
+ &amount);
+ }
+ if (NULL == cmd->details.reserve_withdraw.pk)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to determine denomination key at %u\n",
+ is->ip);
+ fail (is);
+ return;
+ }
+
+ /* create coin's private key */
+ {
+ struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
+
+ priv = GNUNET_CRYPTO_eddsa_key_create ();
+ cmd->details.reserve_withdraw.coin_priv.eddsa_priv = *priv;
+ GNUNET_free (priv);
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.reserve_withdraw.coin_priv.eddsa_priv,
+ &coin_pub.eddsa_pub);
+ cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key
+ = GNUNET_CRYPTO_rsa_blinding_key_create (GNUNET_CRYPTO_rsa_public_key_len (cmd->details.reserve_withdraw.pk->key.rsa_public_key));
+ cmd->details.reserve_withdraw.wsh
+ = TALER_MINT_reserve_withdraw (mint,
+ cmd->details.reserve_withdraw.pk,
+ &ref->details.admin_add_incoming.reserve_priv,
+ &cmd->details.reserve_withdraw.coin_priv,
+ &cmd->details.reserve_withdraw.blinding_key,
+ &reserve_withdraw_cb,
+ is);
+ if (NULL == cmd->details.reserve_withdraw.wsh)
+ {
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ trigger_context_task ();
+ return;
+ case OC_PAY:
+ {
+ GNUNET_break (0); // FIXME: not implemented!
+ trigger_context_task ();
+ return;
+ }
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unknown instruction %d at %u (%s)\n",
+ cmd->oc,
+ is->ip,
+ cmd->label);
+ fail (is);
+ return;
+ }
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Function run when the test terminates (good or bad).
+ * Cleans up our state.
+ *
+ * @param cls the interpreter state.
+ * @param tc unused
+ */
+static void
+do_shutdown (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd;
+ unsigned int i;
+
+ shutdown_task = NULL;
+ for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++)
+ {
+ switch (cmd->oc)
+ {
+ case OC_END:
+ GNUNET_assert (0);
+ break;
+ case OC_ADMIN_ADD_INCOMING:
+ if (NULL != cmd->details.admin_add_incoming.aih)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ i,
+ cmd->label);
+ TALER_MINT_admin_add_incoming_cancel (cmd->details.admin_add_incoming.aih);
+ cmd->details.admin_add_incoming.aih = NULL;
+ }
+ break;
+ case OC_WITHDRAW_STATUS:
+ if (NULL != cmd->details.reserve_status.wsh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ i,
+ cmd->label);
+ TALER_MINT_reserve_status_cancel (cmd->details.reserve_status.wsh);
+ cmd->details.reserve_status.wsh = NULL;
+ }
+ break;
+ case OC_WITHDRAW_SIGN:
+ if (NULL != cmd->details.reserve_withdraw.wsh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ i,
+ cmd->label);
+ TALER_MINT_reserve_withdraw_cancel (cmd->details.reserve_withdraw.wsh);
+ cmd->details.reserve_withdraw.wsh = NULL;
+ }
+ if (NULL != cmd->details.reserve_withdraw.sig.rsa_signature)
+ {
+ GNUNET_CRYPTO_rsa_signature_free (cmd->details.reserve_withdraw.sig.rsa_signature);
+ cmd->details.reserve_withdraw.sig.rsa_signature = NULL;
+ }
+ if (NULL != cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key)
+ {
+ GNUNET_CRYPTO_rsa_blinding_key_free (cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key);
+ cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key = NULL;
+ }
+ break;
+ case OC_PAY:
+ GNUNET_break (0); // FIXME: not implemented
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unknown instruction %d at %u (%s)\n",
+ cmd->oc,
+ i,
+ cmd->label);
+ break;
+ }
+ }
+ if (NULL != is->task)
+ {
+ GNUNET_SCHEDULER_cancel (is->task);
+ is->task = NULL;
+ }
+ GNUNET_free (is);
+ if (NULL != ctx_task)
+ {
+ GNUNET_SCHEDULER_cancel (ctx_task);
+ ctx_task = NULL;
+ }
+ if (NULL != mint)
+ {
+ TALER_MINT_disconnect (mint);
+ mint = NULL;
+ }
+ if (NULL != ctx)
+ {
+ TALER_MINT_fini (ctx);
+ ctx = NULL;
+ }
+}
+
+
+/**
+ * Functions of this type are called to provide the retrieved signing and
+ * denomination keys of the mint. No TALER_MINT_*() functions should be called
+ * in this callback.
+ *
+ * @param cls closure
+ * @param keys information about keys of the mint
+ */
+static void
+cert_cb (void *cls,
+ const struct TALER_MINT_Keys *keys)
+{
+ struct InterpreterState *is = cls;
+
+ /* check that keys is OK */
+#define ERR(cond) do { if(!(cond)) break; GNUNET_break (0); GNUNET_SCHEDULER_shutdown(); return; } while (0)
+ ERR (NULL == keys);
+ ERR (0 == keys->num_sign_keys);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Read %u signing keys\n",
+ keys->num_sign_keys);
+ ERR (0 == keys->num_denom_keys);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Read %u denomination keys\n",
+ keys->num_denom_keys);
+#undef ERR
+
+ /* run actual tests via interpreter-loop */
+ is->keys = keys;
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
+ * Task that runs the context's event loop with the GNUnet scheduler.
+ *
+ * @param cls unused
+ * @param tc scheduler context (unused)
+ */
+static void
+context_task (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ long timeout;
+ int max_fd;
+ fd_set read_fd_set;
+ fd_set write_fd_set;
+ fd_set except_fd_set;
+ struct GNUNET_NETWORK_FDSet *rs;
+ struct GNUNET_NETWORK_FDSet *ws;
+ struct GNUNET_TIME_Relative delay;
+
+ ctx_task = NULL;
+ TALER_MINT_perform (ctx);
+ max_fd = -1;
+ timeout = -1;
+ FD_ZERO (&read_fd_set);
+ FD_ZERO (&write_fd_set);
+ FD_ZERO (&except_fd_set);
+ TALER_MINT_get_select_info (ctx,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set,
+ &max_fd,
+ &timeout);
+ if (timeout >= 0)
+ delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout);
+ else
+ delay = GNUNET_TIME_UNIT_FOREVER_REL;
+ rs = GNUNET_NETWORK_fdset_create ();
+ GNUNET_NETWORK_fdset_copy_native (rs,
+ &read_fd_set,
+ max_fd + 1);
+ ws = GNUNET_NETWORK_fdset_create ();
+ GNUNET_NETWORK_fdset_copy_native (ws,
+ &write_fd_set,
+ max_fd + 1);
+ ctx_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
+ delay,
+ rs,
+ ws,
+ &context_task,
+ cls);
+ GNUNET_NETWORK_fdset_destroy (rs);
+ GNUNET_NETWORK_fdset_destroy (ws);
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param config configuration
+ */
+static void
+run (void *cls,
+ const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ struct InterpreterState *is;
+ static struct Command commands[] =
+ {
+ /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config */
+ { .oc = OC_ADMIN_ADD_INCOMING,
+ .label = "create-reserve-1",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.admin_add_incoming.wire = "{ \"type\":\"TEST\", \"bank\":\"source bank\", \"account\":42 }",
+ .details.admin_add_incoming.amount = "EUR:5.01" },
+ /* Withdraw a 5 EUR coin, at fee of 1 ct */
+ { .oc = OC_WITHDRAW_SIGN,
+ .label = "withdraw-coin-1",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.reserve_withdraw.reserve_reference = "create-reserve-1",
+ .details.reserve_withdraw.amount = "EUR:5" },
+ /* Check that deposit and withdraw operation are in history, and
+ that the balance is now at zero */
+ { .oc = OC_WITHDRAW_STATUS,
+ .label = "withdraw-status-1",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.reserve_status.reserve_reference = "create-reserve-1",
+ .details.reserve_status.expected_balance = "EUR:0" },
+ /* Try to pay with the 5 EUR coin (in full) */
+ { .oc = OC_PAY,
+ .label = "deposit-simple",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.deposit.amount = "EUR:5",
+ .details.deposit.coin_ref = "withdraw-coin-1",
+ .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
+ .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }",
+ .details.deposit.transaction_id = 1 },
+
+ /* Try to double-spend the 5 EUR coin with different wire details */
+ { .oc = OC_PAY,
+ .label = "deposit-double-1",
+ .expected_response_code = MHD_HTTP_FORBIDDEN,
+ .details.deposit.amount = "EUR:5",
+ .details.deposit.coin_ref = "withdraw-coin-1",
+ .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":43 }",
+ .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }",
+ .details.deposit.transaction_id = 1 },
+ /* Try to double-spend the 5 EUR coin at the same merchant (but different
+ transaction ID) */
+ { .oc = OC_PAY,
+ .label = "deposit-double-2",
+ .expected_response_code = MHD_HTTP_FORBIDDEN,
+ .details.deposit.amount = "EUR:5",
+ .details.deposit.coin_ref = "withdraw-coin-1",
+ .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
+ .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }",
+ .details.deposit.transaction_id = 2 },
+ /* Try to double-spend the 5 EUR coin at the same merchant (but different
+ contract) */
+ { .oc = OC_PAY,
+ .label = "deposit-double-3",
+ .expected_response_code = MHD_HTTP_FORBIDDEN,
+ .details.deposit.amount = "EUR:5",
+ .details.deposit.coin_ref = "withdraw-coin-1",
+ .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
+ .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":2 } }",
+ .details.deposit.transaction_id = 1 },
+
+ { .oc = OC_END }
+ };
+
+ is = GNUNET_new (struct InterpreterState);
+ is->commands = commands;
+
+ ctx = TALER_MINT_init ();
+ GNUNET_assert (NULL != ctx);
+ ctx_task = GNUNET_SCHEDULER_add_now (&context_task,
+ ctx);
+ mint = TALER_MINT_connect (ctx,
+ "http://localhost:8081",
+ &cert_cb, is,
+ TALER_MINT_OPTION_END);
+ GNUNET_assert (NULL != mint);
+ shutdown_task
+ = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
+ (GNUNET_TIME_UNIT_SECONDS, 150),
+ &do_shutdown, is);
+}
+
+
+/**
+ * Main function for the testcase for the mint API.
+ *
+ * @param argc expected to be 1
+ * @param argv expected to only contain the program name
+ */
+int
+main (int argc,
+ char * const *argv)
+{
+ struct GNUNET_OS_Process *proc;
+ struct GNUNET_OS_Process *mintd;
+ struct GNUNET_OS_Process *merchantd;
+
+ GNUNET_log_setup ("test-mint-api",
+ "WARNING",
+ NULL);
+ proc = GNUNET_OS_start_process (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-mint-keyup",
+ "taler-mint-keyup",
+ "-d", "test-mint-home",
+ "-m", "test-mint-home/master.priv",
+ NULL);
+ GNUNET_OS_process_wait (proc);
+ GNUNET_OS_process_destroy (proc);
+ mintd = GNUNET_OS_start_process (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-mint-httpd",
+ "taler-mint-httpd",
+ "-d", "test-mint-home",
+ NULL);
+ merchantd = GNUNET_OS_start_process (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-merchant-httpd",
+ "taler-merchant-httpd",
+ "-c", "test-merchant-home",
+ NULL);
+ /* give child time to start and bind against the socket */
+ fprintf (stderr, "Waiting for taler-mint-httpd to be ready");
+ do
+ {
+ fprintf (stderr, ".");
+ sleep (1);
+ }
+ while (0 != system ("wget -q -t 1 -T 1 http://127.0.0.1:8081/keys -o /dev/null -O /dev/null"));
+ fprintf (stderr, "\n");
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_run (&run, NULL);
+ GNUNET_OS_process_kill (mintd,
+ SIGTERM);
+ GNUNET_OS_process_wait (mintd);
+ GNUNET_OS_process_destroy (mintd);
+ return (GNUNET_OK == result) ? 0 : 1;
+}
+
+/* end of test_merchant_api.c */