diff options
| -rw-r--r-- | src/bank-lib/Makefile.am | 47 | ||||
| -rw-r--r-- | src/bank-lib/bank_api_admin.c | 240 | ||||
| -rw-r--r-- | src/bank-lib/bank_api_context.c | 570 | ||||
| -rw-r--r-- | src/bank-lib/bank_api_context.h | 181 | ||||
| -rw-r--r-- | src/bank-lib/bank_api_json.c | 525 | ||||
| -rw-r--r-- | src/bank-lib/bank_api_json.h | 352 | ||||
| -rw-r--r-- | src/bank-lib/test_bank_api.c | 2610 | ||||
| -rw-r--r-- | src/include/taler_bank_service.h | 160 | 
8 files changed, 4685 insertions, 0 deletions
| diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am new file mode 100644 index 00000000..5b3b4d25 --- /dev/null +++ b/src/bank-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 = \ +  libtalerbank.la + +libtalerbank_la_LDFLAGS = \ +  -version-info 0:0:0 \ +  -no-undefined + +libtalerbank_la_SOURCES = \ +  bank_api_context.c bank_api_context.h \ +  bank_api_json.c bank_api_json.h \ +  bank_api_admin.c + +libtalerbank_la_LIBADD = \ +  -lgnunetutil \ +  -ljansson \ +  $(XLIB) + +if HAVE_LIBCURL +libtalerbank_la_LIBADD += -lcurl +else +if HAVE_LIBGNURL +libtalerbank_la_LIBADD += -lgnurl +endif +endif + +#check_PROGRAMS = \ +#  test_bank_api + +#TESTS = \ +#  $(check_PROGRAMS) + +#test_bank_api_SOURCES = \ +#  test_bank_api.c +#test_bank_api_LDADD = \ +#  libtalerbank.la \ +#  $(LIBGCRYPT_LIBS) \ +#  $(top_builddir)/src/util/libtalerutil.la \ +#  -lgnunetutil \ +#  -ljansson diff --git a/src/bank-lib/bank_api_admin.c b/src/bank-lib/bank_api_admin.c new file mode 100644 index 00000000..ed205eeb --- /dev/null +++ b/src/bank-lib/bank_api_admin.c @@ -0,0 +1,240 @@ +/* +  This file is part of TALER +  Copyright (C) 2015, 2016 GNUnet e.V. + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file bank-lib/bank_api_admin.c + * @brief Implementation of the /admin/ requests of the bank'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_bank_service.h" +#include "bank_api_json.h" +#include "bank_api_context.h" +#include "taler_signatures.h" + + +/** + * @brief An admin/add/incoming Handle + */ +struct TALER_BANK_AdminAddIncomingHandle +{ + +  /** +   * The connection to bank this request handle will use +   */ +  struct TALER_BANK_Context *bank; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * JSON encoding of the request to POST. +   */ +  char *json_enc; + +  /** +   * Handle for the request. +   */ +  struct BAC_Job *job; + +  /** +   * HTTP headers for the request. +   */ +  struct curl_slist *headers; + +  /** +   * Function to call with the result. +   */ +  TALER_BANK_AdminAddIncomingResultCallback cb; + +  /** +   * Closure for @a cb. +   */ +  void *cb_cls; + +  /** +   * Download buffer +   */ +  struct BAC_DownloadBuffer db; + +}; + + +/** + * Function called when we're done processing the + * HTTP /admin/add/incoming request. + * + * @param cls the `struct TALER_BANK_AdminAddIncomingHandle` + * @param eh the curl request handle + */ +static void +handle_admin_add_incoming_finished (void *cls, +                                    CURL *eh) +{ +  struct TALER_BANK_AdminAddIncomingHandle *aai = cls; +  long response_code; +  json_t *json; + +  aai->job = NULL; +  json = BAC_download_get_result (&aai->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 bank is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_FORBIDDEN: +    /* Access denied */ +    break; +  case MHD_HTTP_UNAUTHORIZED: +    /* Nothing really to verify, bank 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; +  } +  aai->cb (aai->cb_cls, +           response_code); +  json_decref (json); +  TALER_BANK_admin_add_incoming_cancel (aai); +} + + +/** + * Notify the bank that we have received an incoming transaction + * which fills a reserve.  Note that this API is an administrative + * API and thus not accessible to typical bank clients, but only + * to the operators of the bank. + * + * @param bank the bank handle; the bank must be ready to operate + * @param reserve_pub public key of the reserve + * @param amount amount that was deposited + * @param execution_date when did we receive the amount + * @param wire wire details + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for the above callback + * @return NULL + *         if the inputs are invalid (i.e. invalid amount). + *         In this case, the callback is not called. + */ +struct TALER_BANK_AdminAddIncomingHandle * +TALER_BANK_admin_add_incoming (struct TALER_BANK_Context *bank, +                               const struct TALER_WireTransferIdentifierRawP *wtid, +                               const struct TALER_Amount *amount, +                               const json_t *wire, +                               TALER_BANK_AdminAddIncomingResultCallback res_cb, +                               void *res_cb_cls) +{ +  struct TALER_BANK_AdminAddIncomingHandle *aai; +  json_t *admin_obj; +  CURL *eh; + +  admin_obj = json_pack ("{s:o, s:o," /* reserve_pub/amount */ +                         " s:O}", /* execution_Date/wire */ +                         "wtid", TALER_json_from_data (wtid, +                                                       sizeof (*wtid)), +                         "amount", TALER_json_from_amount (amount), +                         "wire", wire); +  aai = GNUNET_new (struct TALER_BANK_AdminAddIncomingHandle); +  aai->bank = bank; +  aai->cb = res_cb; +  aai->cb_cls = res_cb_cls; +  aai->url = BAC_path_to_url (bank, "/admin/add/incoming"); + +  eh = curl_easy_init (); +  GNUNET_assert (NULL != (aai->json_enc = +                          json_dumps (admin_obj, +                                      JSON_COMPACT))); +  json_decref (admin_obj); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   aai->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDS, +                                   aai->json_enc)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDSIZE, +                                   strlen (aai->json_enc))); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &BAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &aai->db)); +  aai->job = BAC_job_add (bank, +                          eh, +                          GNUNET_YES, +                          &handle_admin_add_incoming_finished, +                          aai); +  return aai; +} + + +/** + * Cancel an add incoming.  This function cannot be used on a request + * handle if a response is already served for it. + * + * @param aai the admin add incoming request handle + */ +void +TALER_BANK_admin_add_incoming_cancel (struct TALER_BANK_AdminAddIncomingHandle *aai) +{ +  if (NULL != aai->job) +  { +    BAC_job_cancel (aai->job); +    aai->job = NULL; +  } +  curl_slist_free_all (aai->headers); +  GNUNET_free_non_null (aai->db.buf); +  GNUNET_free (aai->url); +  GNUNET_free (aai->json_enc); +  GNUNET_free (aai); +} + + +/* end of bank_api_admin.c */ diff --git a/src/bank-lib/bank_api_context.c b/src/bank-lib/bank_api_context.c new file mode 100644 index 00000000..f54e9e70 --- /dev/null +++ b/src/bank-lib/bank_api_context.c @@ -0,0 +1,570 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file bank-lib/bank_api_context.c + * @brief Implementation of the context part of the bank's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include "taler_bank_service.h" +#include "bank_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_BANK_curl_fail; + + +/** + * Jobs are CURL requests running within a `struct TALER_BANK_Context`. + */ +struct BAC_Job +{ + +  /** +   * We keep jobs in a DLL. +   */ +  struct BAC_Job *next; + +  /** +   * We keep jobs in a DLL. +   */ +  struct BAC_Job *prev; + +  /** +   * Easy handle of the job. +   */ +  CURL *easy_handle; + +  /** +   * Context this job runs in. +   */ +  struct TALER_BANK_Context *ctx; + +  /** +   * Function to call upon completion. +   */ +  BAC_JobCompletionCallback jcc; + +  /** +   * Closure for @e jcc. +   */ +  void *jcc_cls; + +}; + + +/** + * Context + */ +struct TALER_BANK_Context +{ +  /** +   * Curl multi handle +   */ +  CURLM *multi; + +  /** +   * Curl share handle +   */ +  CURLSH *share; + +  /** +   * We keep jobs in a DLL. +   */ +  struct BAC_Job *jobs_head; + +  /** +   * We keep jobs in a DLL. +   */ +  struct BAC_Job *jobs_tail; + +  /** +   * HTTP header "application/json", created once and used +   * for all requests that need it. +   */ +  struct curl_slist *json_header; + +  /** +   * Base URL of the bank. +   */ +  char *url; + +}; + + +/** + * Initialise this library.  This function should be called before using any of + * the following functions. + * + * @param url HTTP base URL for the bank + * @return library context + */ +struct TALER_BANK_Context * +TALER_BANK_init (const char *url) +{ +  struct TALER_BANK_Context *ctx; +  CURLM *multi; +  CURLSH *share; + +  if (TALER_BANK_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_BANK_Context); +  ctx->multi = multi; +  ctx->share = share; +  ctx->url = GNUNET_strdup (url); +  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 #BAC_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 BAC_Job * +BAC_job_add (struct TALER_BANK_Context *ctx, +             CURL *eh, +             int add_json, +             BAC_JobCompletionCallback jcc, +             void *jcc_cls) +{ +  struct BAC_Job *job; + +  if (GNUNET_YES == add_json) +    GNUNET_assert (CURLE_OK == +                   curl_easy_setopt (eh, +                                     CURLOPT_HTTPHEADER, +                                     ctx->json_header)); + +  job = GNUNET_new (struct BAC_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 #BAC_job_add(). + * + * @param eh easy handle that was used + * @return the `jcc_cls` that was given to #BAC_job_add(). + */ +void * +BAC_easy_to_closure (CURL *eh) +{ +  struct BAC_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 +BAC_job_cancel (struct BAC_Job *job) +{ +  struct TALER_BANK_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_BANK_perform (struct TALER_BANK_Context *ctx) +{ +  CURLMsg *cmsg; +  struct BAC_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); +    BAC_job_cancel (job); +  } +} + + +/** + * Obtain the information for a select() call to wait until + * #TALER_BANK_perform() is ready again.  Note that calling + * any other TALER_BANK-API may also imply that the library + * is again ready for #TALER_BANK_perform(). + * + * Basically, a client should use this API to prepare for select(), + * then block on select(), then call #TALER_BANK_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_BANK_perform(). + */ +void +TALER_BANK_get_select_info (struct TALER_BANK_Context *ctx, +                            fd_set *read_fd_set, +                            fd_set *write_fd_set, +                            fd_set *except_fd_set, +                            int *max_fd, +                            long *timeout) +{ +  long to; +  int m; + +  m = -1; +  GNUNET_assert (CURLM_OK == +                 curl_multi_fdset (ctx->multi, +                                   read_fd_set, +                                   write_fd_set, +                                   except_fd_set, +                                   &m)); +  to = *timeout; +  *max_fd = GNUNET_MAX (m, *max_fd); +  GNUNET_assert (CURLM_OK == +                 curl_multi_timeout (ctx->multi, +                                     &to)); + +  /* Only if what we got back from curl is smaller than what we +     already had (-1 == infinity!), then update timeout */ +  if ( (to < *timeout) && +       (-1 != to) ) +    *timeout = to; +  if ( (-1 == (*timeout)) && +       (NULL != ctx->jobs_head) ) +    *timeout = to; +} + + +/** + * 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_BANK_fini (struct TALER_BANK_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->url); +  GNUNET_free (ctx); +} + + +/** + * Obtain the URL to use for an API request. + * + * @param h the mint handle to query + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URI to use with cURL + */ +char * +MAH_path_to_url (struct TALER_BANK_Context *h, +                 const char *path) +{ +  char *url; + +  if ( ('/' == path[0]) && +       (0 < strlen (h->url)) && +       ('/' == h->url[strlen (h->url) - 1]) ) +    path++; /* avoid generating URL with "//" from concat */ +  GNUNET_asprintf (&url, +                   "%s%s", +                   h->url, +                   path); +  return url; +} + + +/** + * Callback used when downloading the reply to an HTTP request. + * Just appends all of the data to the `buf` in the + * `struct BAC_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 +BAC_download_cb (char *bufptr, +                 size_t size, +                 size_t nitems, +                 void *cls) +{ +  struct BAC_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 * +BAC_download_get_result (struct BAC_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_BANK_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_BANK_curl_fail = 1; +  } +} + + +/** + * Cleans up after us, specifically runs the Curl cleanup. + */ +__attribute__ ((destructor)) +void +TALER_BANK_destructor__ (void) +{ +  if (TALER_BANK_curl_fail) +    return; +  curl_global_cleanup (); +} + +/* end of bank_api_context.c */ diff --git a/src/bank-lib/bank_api_context.h b/src/bank-lib/bank_api_context.h new file mode 100644 index 00000000..552cbe44 --- /dev/null +++ b/src/bank-lib/bank_api_context.h @@ -0,0 +1,181 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file bank-lib/bank_api_context.h + * @brief Internal interface to the context part of the bank'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_bank_service.h" +#include "taler_signatures.h" + + +/** + * Entry in the context's job queue. + */ +struct BAC_Job; + +/** + * Function to call upon completion of a job. + * + * @param cls closure + * @param eh original easy handle (for inspection) + */ +typedef void +(*BAC_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 #BAC_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 BAC_Job * +BAC_job_add (struct TALER_BANK_Context *ctx, +             CURL *eh, +             int add_json, +             BAC_JobCompletionCallback jcc, +             void *jcc_cls); + + +/** + * Obtain the `jcc_cls` argument from an `eh` that was + * given to #BAC_job_add(). + * + * @param eh easy handle that was used + * @return the `jcc_cls` that was given to #BAC_job_add(). + */ +void * +BAC_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 +BAC_job_cancel (struct BAC_Job *job); + + +/** + * @brief Buffer data structure we use to buffer the HTTP download + * before giving it to the JSON parser. + */ +struct BAC_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 BAC_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 BAC_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 +BAC_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 * +BAC_download_get_result (struct BAC_DownloadBuffer *db, +                         CURL *eh, +                         long *response_code); + + +/** + * Obtain the URL to use for an API request. + * + * @param h the bank handle to query + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URI to use with cURL + */ +char * +BAC_path_to_url (struct TALER_BANK_Context *h, +                 const char *path); + + +/* end of bank_api_context.h */ diff --git a/src/bank-lib/bank_api_json.c b/src/bank-lib/bank_api_json.c new file mode 100644 index 00000000..2a09e527 --- /dev/null +++ b/src/bank-lib/bank_api_json.c @@ -0,0 +1,525 @@ +/* +  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 bank-lib/bank_api_json.c + * @brief functions to parse incoming requests (JSON snippets) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include "bank_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 BAJ_Specification *spec) +{ +  int i; +  json_t *pos; /* what's our current position? */ + +  pos = root; +  for (i=0;BAJ_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 BAJ_CMD_END: +      GNUNET_assert (0); +      return i; +    case BAJ_CMD_AMOUNT: +      if (GNUNET_OK != +          TALER_json_to_amount (pos, +                                spec[i].details.amount)) +      { +        GNUNET_break_op (0); +        return i; +      } +      break; +    case BAJ_CMD_TIME_ABSOLUTE: +      if (GNUNET_OK != +          TALER_json_to_abs (pos, +                             spec[i].details.abs_time)) +      { +        GNUNET_break_op (0); +        return i; +      } +      break; + +    case BAJ_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 BAJ_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 BAJ_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 BAJ_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 BAJ_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 BAJ_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 BAJ_CMD_UINT64: +      { +        json_int_t val; + +        if (! json_is_integer (pos)) +        { +          GNUNET_break_op (0); +          return i; +        } +        val = json_integer_value (pos); +        *spec[i].details.u64 = (uint64_t) val; +      } +      break; + +    case BAJ_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 + * #BAJ_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 BAJ_Specification *spec, +            int end) +{ +  int i; + +  for (i=0;i<end;i++) +  { +    switch (spec[i].cmd) +    { +    case BAJ_CMD_END: +      GNUNET_assert (0); +      return; +    case BAJ_CMD_AMOUNT: +      break; +    case BAJ_CMD_TIME_ABSOLUTE: +      break; +    case BAJ_CMD_BINARY_FIXED: +      break; +    case BAJ_CMD_STRING: +      break; +    case BAJ_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 BAJ_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 BAJ_CMD_RSA_SIGNATURE: +      GNUNET_CRYPTO_rsa_signature_free (*spec[i].details.rsa_signature); +      *spec[i].details.rsa_signature = NULL; +      break; +    case BAJ_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 +BAJ_parse_json (const json_t *root, +                struct BAJ_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 + * #BAJ_parse_json() operation. + * + * @param spec specification of the parse operation + */ +void +BAJ_parse_free (struct BAJ_Specification *spec) +{ +  int i; + +  for (i=0;BAJ_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 BAJ_Specification +BAJ_spec_string (const char *name, +                 const char **strptr) +{ +  struct BAJ_Specification ret = +    { +      .cmd = BAJ_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 BAJ_Specification +BAJ_spec_absolute_time (const char *name, +                        struct GNUNET_TIME_Absolute *at) +{ +  struct BAJ_Specification ret = +    { +      .cmd = BAJ_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 BAJ_Specification +BAJ_spec_amount (const char *name, +                 struct TALER_Amount *amount) +{ +  struct BAJ_Specification ret = +    { +      .cmd = BAJ_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 BAJ_Specification +BAJ_spec_uint16 (const char *name, +                 uint16_t *u16) +{ +  struct BAJ_Specification ret = +    { +      .cmd = BAJ_CMD_UINT16, +      .field = name, +      .details.u16 = u16 +    }; +  return ret; +} + + +/** + * 64-bit integer. + * + * @param name name of the JSON field + * @param[out] u64 where to store the integer found under @a name + */ +struct BAJ_Specification +BAJ_spec_uint64 (const char *name, +                 uint64_t *u64) +{ +  struct BAJ_Specification ret = +    { +      .cmd = BAJ_CMD_UINT64, +      .field = name, +      .details.u64 = u64 +    }; +  return ret; +} + + +/** + * JSON object. + * + * @param name name of the JSON field + * @param[out] jsonp where to store the JSON found under @a name + */ +struct BAJ_Specification +BAJ_spec_json (const char *name, +               json_t **jsonp) +{ +  struct BAJ_Specification ret = +    { +      .cmd = BAJ_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 BAJ_Specification +BAJ_spec_rsa_public_key (const char *name, +                         struct GNUNET_CRYPTO_rsa_PublicKey **pk) +{ +  struct BAJ_Specification ret = +    { +      .cmd = BAJ_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 BAJ_Specification +BAJ_spec_rsa_signature (const char *name, +                        struct GNUNET_CRYPTO_rsa_Signature **sig) +{ +  struct BAJ_Specification ret = +    { +      .cmd = BAJ_CMD_RSA_SIGNATURE, +      .field = name, +      .details.rsa_signature = sig +    }; +  return ret; +} + + +/* end of bank_api_json.c */ diff --git a/src/bank-lib/bank_api_json.h b/src/bank-lib/bank_api_json.h new file mode 100644 index 00000000..2ecaf8ef --- /dev/null +++ b/src/bank-lib/bank_api_json.h @@ -0,0 +1,352 @@ +/* +  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 mint-lib/mint_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_util.h" +#include <jansson.h> + + +/** + * Enumeration with the various commands for the + * #BAJ_parse_json interpreter. + */ +enum BAJ_Command +{ + +  /** +   * End of command list. +   */ +  BAJ_CMD_END, + +  /** +   * Parse amount at current position. +   */ +  BAJ_CMD_AMOUNT, + +  /** +   * Parse absolute time at current position. +   */ +  BAJ_CMD_TIME_ABSOLUTE, + +  /** +   * Parse fixed binary value at current position. +   */ +  BAJ_CMD_BINARY_FIXED, + +  /** +   * Parse variable-size binary value at current position. +   */ +  BAJ_CMD_BINARY_VARIABLE, + +  /** +   * Parse RSA public key at current position. +   */ +  BAJ_CMD_RSA_PUBLIC_KEY, + +  /** +   * Parse RSA signature at current position. +   */ +  BAJ_CMD_RSA_SIGNATURE, + +  /** +   * Parse `const char *` JSON string at current position. +   */ +  BAJ_CMD_STRING, + +  /** +   * Parse `uint16_t` integer at the current position. +   */ +  BAJ_CMD_UINT16, + +  /** +   * Parse `uint64_t` integer at the current position. +   */ +  BAJ_CMD_UINT64, + +  /** +   * Parse JSON object at the current position. +   */ +  BAJ_CMD_JSON_OBJECT, + +  /** +   * Parse ??? at current position. +   */ +  BAJ_CMD_C + +}; + + +/** + * @brief Entry in parser specification for #BAJ_parse_json. + */ +struct BAJ_Specification +{ + +  /** +   * Command to execute. +   */ +  enum BAJ_Command cmd; + +  /** +   * Name of the field to access. +   */ +  const char *field; + +  /** +   * Further details for the command. +   */ +  union { + +    /** +     * Where to store amount for #BAJ_CMD_AMOUNT. +     */ +    struct TALER_Amount *amount; + +    /** +     * Where to store time, for #BAJ_CMD_TIME_ABSOLUTE. +     */ +    struct GNUNET_TIME_Absolute *abs_time; + +    /** +     * Where to write binary data, for #BAJ_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 #BAJ_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 #BAJ_CMD_RSA_PUBLIC_KEY +     */ +    struct GNUNET_CRYPTO_rsa_PublicKey **rsa_public_key; + +    /** +     * Where to store the RSA signature for #BAJ_CMD_RSA_SIGNATURE +     */ +    struct GNUNET_CRYPTO_rsa_Signature **rsa_signature; + +    /** +     * Details for #BAJ_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 64-bit integer. +     */ +    uint64_t *u64; + +    /** +     * 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 +BAJ_parse_json (const json_t *root, +                struct BAJ_Specification *spec); + + +/** + * Free all elements allocated during a + * #BAJ_parse_json() operation. + * + * @param spec specification of the parse operation + */ +void +BAJ_parse_free (struct BAJ_Specification *spec); + + +/** + * End of a parser specification. + */ +#define BAJ_spec_end { .cmd = BAJ_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 BAJ_spec_fixed_auto(name,obj) { .cmd = BAJ_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 BAJ_spec_varsize(name,obj,size) { .cmd = BAJ_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 BAJ_Specification +BAJ_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 BAJ_Specification +BAJ_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 BAJ_Specification +BAJ_spec_uint16 (const char *name, +                 uint16_t *u16); + + +/** + * 64-bit integer. + * + * @param name name of the JSON field + * @param[out] u64 where to store the integer found under @a name + */ +struct BAJ_Specification +BAJ_spec_uint64 (const char *name, +                 uint64_t *u64); + + +/** + * JSON object. + * + * @param name name of the JSON field + * @param[out] jsonp where to store the JSON found under @a name + */ +struct BAJ_Specification +BAJ_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 BAJ_Specification +BAJ_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 BAJ_Specification +BAJ_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 BAJ_Specification +BAJ_spec_rsa_signature (const char *name, +                        struct GNUNET_CRYPTO_rsa_Signature **sig); + + + + +/* end of mint_api_json.h */ diff --git a/src/bank-lib/test_bank_api.c b/src/bank-lib/test_bank_api.c new file mode 100644 index 00000000..3c1747a1 --- /dev/null +++ b/src/bank-lib/test_bank_api.c @@ -0,0 +1,2610 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015, 2016 GNUnet e.V. + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file bank/test_bank_api.c + * @brief testcase to test bank's HTTP API interface + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_bank_service.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> + +/** + * Is the configuration file is set to include wire format 'test'? + */ +#define WIRE_TEST 1 + +/** + * Is the configuration file is set to include wire format 'sepa'? + */ +#define WIRE_SEPA 1 + +/** + * Main execution context for the main loop. + */ +static struct TALER_BANK_Context *ctx; + +/** + * Handle to access the bank. + */ +static struct TALER_BANK_Handle *bank; + +/** + * 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, + +  /** +   * Deposit a coin (pay with it). +   */ +  OC_DEPOSIT, + +  /** +   * Melt a (set of) coins. +   */ +  OC_REFRESH_MELT, + +  /** +   * Complete melting session by withdrawing melted coins. +   */ +  OC_REFRESH_REVEAL, + +  /** +   * Verify bank's /refresh/link by linking original private key to +   * results from #OC_REFRESH_REVEAL step. +   */ +  OC_REFRESH_LINK, + +  /** +   * Verify the bank's /wire-method. +   */ +  OC_WIRE, + +  /** +   * Verify bank's /wire/deposits method. +   */ +  OC_WIRE_DEPOSITS, + +  /** +   * Verify bank's /deposit/wtid method. +   */ +  OC_DEPOSIT_WTID + +}; + + +/** + * 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_BANK_DenomPublicKey *pk; + +  /** +   * Set (by the interpreter) to the bank'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 bank 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_BANK_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_BANK_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 bank'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_BANK_DenomPublicKey *pk; + +      /** +       * Set (by the interpreter) to the bank'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_BANK_ReserveWithdrawHandle *wsh; + +    } reserve_withdraw; + +    /** +     * Information for a #OC_DEPOSIT command. +     */ +    struct +    { + +      /** +       * Amount to deposit. +       */ +      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_BANK_DepositHandle *dh; + +    } deposit; + +    /** +     * Information for a #OC_REFRESH_MELT command. +     */ +    struct +    { + +      /** +       * Information about coins to be melted. +       */ +      struct MeltDetails *melted_coins; + +      /** +       * Denominations of the fresh coins to withdraw. +       */ +      const char **fresh_amounts; + +      /** +       * Array of the public keys corresponding to +       * the @e fresh_amounts, set by the interpreter. +       */ +      const struct TALER_BANK_DenomPublicKey **fresh_pks; + +      /** +       * Melt handle while operation is running. +       */ +      struct TALER_BANK_RefreshMeltHandle *rmh; + +      /** +       * Data used in the refresh operation, set by the interpreter. +       */ +      char *refresh_data; + +      /** +       * Number of bytes in @e refresh_data, set by the interpreter. +       */ +      size_t refresh_data_length; + +      /** +       * Set by the interpreter (upon completion) to the noreveal +       * index selected by the bank. +       */ +      uint16_t noreveal_index; + +    } refresh_melt; + +    /** +     * Information for a #OC_REFRESH_REVEAL command. +     */ +    struct +    { + +      /** +       * Melt operation this is the matching reveal for. +       */ +      const char *melt_ref; + +      /** +       * Reveal handle while operation is running. +       */ +      struct TALER_BANK_RefreshRevealHandle *rrh; + +      /** +       * Number of fresh coins withdrawn, set by the interpreter. +       * Length of the @e fresh_coins array. +       */ +      unsigned int num_fresh_coins; + +      /** +       * Information about coins withdrawn, set by the interpreter. +       */ +      struct FreshCoin *fresh_coins; + +    } refresh_reveal; + +    /** +     * Information for a #OC_REFRESH_LINK command. +     */ +    struct +    { + +      /** +       * Reveal operation this is the matching link for. +       */ +      const char *reveal_ref; + +      /** +       * Link handle while operation is running. +       */ +      struct TALER_BANK_RefreshLinkHandle *rlh; + +      /** +       * Which of the melted coins should be used for the linkage? +       */ +      unsigned int coin_idx; + +    } refresh_link; + +    /** +     * Information for the /wire command. +     */ +    struct { + +      /** +       * Handle to the wire request. +       */ +      struct TALER_BANK_WireHandle *wh; + +      /** +       * Format we expect to see, others will be *ignored*. +       */ +      const char *format; + +    } wire; + +    /** +     * Information for the /wire/deposits's command. +     */ +    struct { + +      /** +       * Handle to the wire deposits request. +       */ +      struct TALER_BANK_WireDepositsHandle *wdh; + +      /** +       * Reference to a /deposit/wtid command. If set, we use the +       * WTID from that command. +       */ +      const char *wtid_ref; + +      /** +       * WTID to use (used if @e wtid_ref is NULL). +       */ +      struct TALER_WireTransferIdentifierRawP wtid; + +      /* TODO: may want to add list of deposits we expected +         to see aggregated here in the future. */ + +    } wire_deposits; + +    /** +     * Information for the /deposit/wtid command. +     */ +    struct { + +      /** +       * Handle to the deposit wtid request. +       */ +      struct TALER_BANK_DepositWtidHandle *dwh; + +      /** +       * Which /deposit operation should we obtain WTID data for? +       */ +      const char *deposit_ref; + +      /** +       * What is the expected total amount? Only used if +       * @e expected_response_code was #MHD_HTTP_OK. +       */ +      struct TALER_Amount total_amount_expected; + +      /** +       * Wire transfer identifier, set if #MHD_HTTP_OK was the response code. +       */ +      struct TALER_WireTransferIdentifierRawP wtid; + +    } deposit_wtid; + +  } details; + +}; + + +/** + * State of the interpreter loop. + */ +struct InterpreterState +{ +  /** +   * Keys from the bank. +   */ +  const struct TALER_BANK_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 bank 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 bank's reply is bogus (fails to follow the protocol) + * @param full_response full response from the bank (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_BANK_ReserveHistory *h, +                                    const struct Command *cmd) +{ +  struct TALER_Amount amount; + +  if (TALER_BANK_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_BANK_ReserveHistory *h, +                                  const struct Command *cmd) +{ +  struct TALER_Amount amount; +  struct TALER_Amount amount_with_fee; + +  if (TALER_BANK_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 bank'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_BANK_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 bank'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 bank (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 /deposit operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit; + *                    0 if the bank'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 +deposit_cb (void *cls, +            unsigned int http_status, +            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); +} + + +/** + * Function called with the result of the /refresh/melt operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, never #MHD_HTTP_OK (200) as for successful intermediate response this callback is skipped. + *                    0 if the bank's reply is bogus (fails to follow the protocol) + * @param noreveal_index choice by the bank in the cut-and-choose protocol, + *                    UINT16_MAX on error + * @param full_response full response from the bank (for logging, in case of errors) + */ +static void +melt_cb (void *cls, +         unsigned int http_status, +         uint16_t noreveal_index, +         json_t *full_response) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; + +  cmd->details.refresh_melt.rmh = 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); +    fail (is); +    return; +  } +  cmd->details.refresh_melt.noreveal_index = noreveal_index; +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Function called with the result of the /refresh/reveal operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + *                    0 if the bank's reply is bogus (fails to follow the protocol) + * @param num_coins number of fresh coins created, length of the @a sigs and @a coin_privs arrays, 0 if the operation failed + * @param coin_privs array of @a num_coins private keys for the coins that were created, NULL on error + * @param sigs array of signature over @a num_coins coins, NULL on error + * @param full_response full response from the bank (for logging, in case of errors) + */ +static void +reveal_cb (void *cls, +           unsigned int http_status, +           unsigned int num_coins, +           const struct TALER_CoinSpendPrivateKeyP *coin_privs, +           const struct TALER_DenominationSignature *sigs, +           json_t *full_response) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; +  const struct Command *ref; +  unsigned int i; + +  cmd->details.refresh_reveal.rrh = 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); +    fail (is); +    return; +  } +  ref = find_command (is, +                      cmd->details.refresh_reveal.melt_ref); +  cmd->details.refresh_reveal.num_fresh_coins = num_coins; +  switch (http_status) +  { +  case MHD_HTTP_OK: +    cmd->details.refresh_reveal.fresh_coins +      = GNUNET_new_array (num_coins, +                          struct FreshCoin); +    for (i=0;i<num_coins;i++) +    { +      struct FreshCoin *fc = &cmd->details.refresh_reveal.fresh_coins[i]; + +      fc->pk = ref->details.refresh_melt.fresh_pks[i]; +      fc->coin_priv = coin_privs[i]; +      fc->sig.rsa_signature +        = GNUNET_CRYPTO_rsa_signature_dup (sigs[i].rsa_signature); +    } +    break; +  default: +    break; +  } + +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Function called with the result of a /refresh/link operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + *                    0 if the bank's reply is bogus (fails to follow the protocol) + * @param num_coins number of fresh coins created, length of the @a sigs and @a coin_privs arrays, 0 if the operation failed + * @param coin_privs array of @a num_coins private keys for the coins that were created, NULL on error + * @param sigs array of signature over @a num_coins coins, NULL on error + * @param pubs array of public keys for the @a sigs, NULL on error + * @param full_response full response from the bank (for logging, in case of errors) + */ +static void +link_cb (void *cls, +         unsigned int http_status, +         unsigned int num_coins, +         const struct TALER_CoinSpendPrivateKeyP *coin_privs, +         const struct TALER_DenominationSignature *sigs, +         const struct TALER_DenominationPublicKey *pubs, +         json_t *full_response) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; +  const struct Command *ref; +  unsigned int i; +  unsigned int j; +  unsigned int found; + +  cmd->details.refresh_link.rlh = 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); +    fail (is); +    return; +  } +  ref = find_command (is, +                      cmd->details.refresh_link.reveal_ref); +  switch (http_status) +  { +  case MHD_HTTP_OK: +    /* check that number of coins returned matches */ +    if (num_coins != ref->details.refresh_reveal.num_fresh_coins) +    { +      GNUNET_break (0); +      fail (is); +      return; +    } +    /* check that the coins match */ +    for (i=0;i<num_coins;i++) +      for (j=i+1;j<num_coins;j++) +	if (0 == memcmp (&coin_privs[i], +			 &coin_privs[j], +			 sizeof (struct TALER_CoinSpendPrivateKeyP))) +	  GNUNET_break (0); +    /* Note: coins might be legitimately permutated in here... */ +    found = 0; +    for (i=0;i<num_coins;i++) +      for (j=0;j<num_coins;j++) +      { +	const struct FreshCoin *fc; + +	fc = &ref->details.refresh_reveal.fresh_coins[j]; +	if ( (0 == memcmp (&coin_privs[i], +			   &fc->coin_priv, +			   sizeof (struct TALER_CoinSpendPrivateKeyP))) && +	     (0 == GNUNET_CRYPTO_rsa_signature_cmp (fc->sig.rsa_signature, +						    sigs[i].rsa_signature)) && +	     (0 == GNUNET_CRYPTO_rsa_public_key_cmp (fc->pk->key.rsa_public_key, +						     pubs[i].rsa_public_key)) ) +	{ +	  found++; +	  break; +	} +      } +    if (found != num_coins) +    { +      fprintf (stderr, +	       "Only %u/%u coins match expectations\n", +	       found, +	       num_coins); +      GNUNET_break (0); +      fail (is); +      return; +    } +    break; +  default: +    break; +  } +  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_BANK_DenomPublicKey * +find_pk (const struct TALER_BANK_Keys *keys, +         const struct TALER_Amount *amount) +{ +  unsigned int i; +  struct GNUNET_TIME_Absolute now; +  struct TALER_BANK_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; +} + + +/** + * Callbacks called with the result(s) of a + * wire format inquiry request to the bank. + * + * The callback is invoked multiple times, once for each supported @a + * method.  Finally, it is invoked one more time with cls/0/NULL/NULL + * to indicate the end of the iteration.  If any request fails to + * generate a valid response from the bank, @a http_status will also + * be zero and the iteration will also end.  Thus, the iteration + * always ends with a final call with an @a http_status of 0. If the + * @a http_status is already 0 on the first call, then the response to + * the /wire request was invalid.  Later, clients can tell the + * difference between @a http_status of 0 indicating a failed + * /wire/method request and a regular end of the iteration by @a + * method being non-NULL.  If the bank simply correctly asserts that + * it does not support any methods, @a method will be NULL but the @a + * http_status will be #MHD_HTTP_OK for the first call (followed by a + * cls/0/NULL/NULL call to signal the end of the iteration). + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful request; + *                    0 if the bank's reply is bogus (fails to follow the protocol) + * @param method wire format method supported, i.e. "test" or "sepa", or NULL + *            if already the /wire request failed. + * @param obj the received JSON reply, if successful this should be the wire + *            format details as provided by /wire/METHOD/, or NULL if the + *            reply was not in JSON format (in this case, the client might + *            want to do an HTTP request to /wire/METHOD/ with a browser to + *            provide more information to the user about the @a method). + */ +static void +wire_cb (void *cls, +         unsigned int http_status, +         const char *method, +         json_t *obj) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; + +  if (0 == http_status) +  { +    /* 0 always signals the end of the iteration */ +    cmd->details.wire.wh = NULL; +  } +  else if ( (NULL != method) && +            (0 != strcasecmp (method, +                              cmd->details.wire.format)) ) +  { +    /* not the method we care about, skip */ +    return; +  } +  if (cmd->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s/%s\n", +                http_status, +                cmd->label, +                method); +    json_dumpf (obj, stderr, 0); +    fail (is); +    return; +  } +  if (0 == http_status) +  { +    /* end of iteration, move to next command */ +    is->ip++; +    is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                         is); +    return; +  } +  /* For now, we only support to be called only once +     with a "positive" result; so we switch to an +     expected value of 0 for the 2nd iteration */ +  cmd->expected_response_code = 0; +} + + +/** + * Function called with detailed wire transfer data, including all + * of the coin transactions that were combined into the wire transfer. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on bank protocol violation + * @param json original json reply (may include signatures, those have then been + *        validated already) + * @param wtid extracted wire transfer identifier, or NULL if the bank could + *             not provide any (set only if @a http_status is #MHD_HTTP_OK) + * @param total_amount total amount of the wire transfer, or NULL if the bank could + *             not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + * @param details_length length of the @a details array + * @param details array with details about the combined transactions + */ +static void +wire_deposits_cb (void *cls, +                  unsigned int http_status, +                  json_t *json, +                  const struct GNUNET_HashCode *h_wire, +                  const struct TALER_Amount *total_amount, +                  unsigned int details_length, +                  const struct TALER_WireDepositDetails *details) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; +  const struct Command *ref; + +  cmd->details.wire_deposits.wdh = NULL; +  ref = find_command (is, +                      cmd->details.wire_deposits.wtid_ref); +  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 (json, stderr, 0); +    fail (is); +    return; +  } +  switch (http_status) +  { +  case MHD_HTTP_OK: +    if (0 != TALER_amount_cmp (total_amount, +                               &ref->details.deposit_wtid.total_amount_expected)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Total amount missmatch to command %s\n", +                  http_status, +                  cmd->label); +      json_dumpf (json, stderr, 0); +      fail (is); +      return; +    } +    if (NULL != ref->details.deposit_wtid.deposit_ref) +    { +      const struct Command *dep; +      struct GNUNET_HashCode hw; + +      dep = find_command (is, +                          ref->details.deposit_wtid.deposit_ref); +      GNUNET_CRYPTO_hash (dep->details.deposit.wire_details, +                          strlen (dep->details.deposit.wire_details), +                          &hw); +      if (0 != memcmp (&hw, +                       h_wire, +                       sizeof (struct GNUNET_HashCode))) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Wire hash missmatch to command %s\n", +                    cmd->label); +        json_dumpf (json, stderr, 0); +        fail (is); +        return; +      } +    } +    break; +  default: +    break; +  } + +  /* move to next command */ +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Function called with detailed wire transfer data. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on bank protocol violation + * @param json original json reply (may include signatures, those have then been + *        validated already) + * @param wtid wire transfer identifier used by the bank, NULL if bank did not + *                  yet execute the transaction + * @param execution_time actual or planned execution time for the wire transfer + * @param coin_contribution contribution to the @a total_amount of the deposited coin (may be NULL) + * @param total_amount total amount of the wire transfer, or NULL if the bank could + *             not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + */ +static void +deposit_wtid_cb (void *cls, +                 unsigned int http_status, +                 json_t *json, +                 const struct TALER_WireTransferIdentifierRawP *wtid, +                 struct GNUNET_TIME_Absolute execution_time, +                 const struct TALER_Amount *coin_contribution, +                 const struct TALER_Amount *total_amount) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; + +  cmd->details.deposit_wtid.dwh = 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 (json, stderr, 0); +    fail (is); +    return; +  } +  switch (http_status) +  { +  case MHD_HTTP_OK: +    cmd->details.deposit_wtid.wtid = *wtid; +    if (0 != TALER_amount_cmp (total_amount, +                               &cmd->details.deposit_wtid.total_amount_expected)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Total amount missmatch to command %s\n", +                  cmd->label); +      json_dumpf (json, stderr, 0); +      fail (is); +      return; +    } +    break; +  default: +    break; +  } + +  /* move to next command */ +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Run the main interpreter loop that performs bank 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_BANK_admin_add_incoming (bank, +                                       &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_BANK_reserve_status (bank, +                                   &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_BANK_reserve_withdraw (bank, +                                     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_DEPOSIT: +    { +      struct GNUNET_HashCode h_contract; +      const struct TALER_CoinSpendPrivateKeyP *coin_priv; +      const struct TALER_BANK_DenomPublicKey *coin_pk; +      const struct TALER_DenominationSignature *coin_pk_sig; +      struct TALER_CoinSpendPublicKeyP coin_pub; +      struct TALER_CoinSpendSignatureP coin_sig; +      struct GNUNET_TIME_Absolute refund_deadline; +      struct GNUNET_TIME_Absolute wire_deadline; +      struct GNUNET_TIME_Absolute timestamp; +      struct GNUNET_CRYPTO_EddsaPrivateKey *priv; +      struct TALER_MerchantPublicKeyP merchant_pub; +      json_t *contract; +      json_t *wire; + +      GNUNET_assert (NULL != +                     cmd->details.deposit.coin_ref); +      ref = find_command (is, +                          cmd->details.deposit.coin_ref); +      GNUNET_assert (NULL != ref); +      switch (ref->oc) +      { +      case OC_WITHDRAW_SIGN: +        coin_priv = &ref->details.reserve_withdraw.coin_priv; +        coin_pk = ref->details.reserve_withdraw.pk; +        coin_pk_sig = &ref->details.reserve_withdraw.sig; +        break; +      case OC_REFRESH_REVEAL: +        { +          const struct FreshCoin *fc; +          unsigned int idx; + +          idx = cmd->details.deposit.coin_idx; +          GNUNET_assert (idx < ref->details.refresh_reveal.num_fresh_coins); +          fc = &ref->details.refresh_reveal.fresh_coins[idx]; + +          coin_priv = &fc->coin_priv; +          coin_pk = fc->pk; +          coin_pk_sig = &fc->sig; +        } +        break; +      default: +        GNUNET_assert (0); +      } +      if (GNUNET_OK != +          TALER_string_to_amount (cmd->details.deposit.amount, +                                  &amount)) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Failed to parse amount `%s' at %u\n", +                    cmd->details.deposit.amount, +                    is->ip); +        fail (is); +        return; +      } +      contract = json_loads (cmd->details.deposit.contract, +                             JSON_REJECT_DUPLICATES, +                             NULL); +      if (NULL == contract) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Failed to parse contract details `%s' at %u/%s\n", +                    cmd->details.deposit.contract, +                    is->ip, +                    cmd->label); +        fail (is); +        return; +      } +      TALER_hash_json (contract, +                       &h_contract); +      wire = json_loads (cmd->details.deposit.wire_details, +                         JSON_REJECT_DUPLICATES, +                         NULL); +      if (NULL == wire) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Failed to parse wire details `%s' at %u/%s\n", +                    cmd->details.deposit.wire_details, +                    is->ip, +                    cmd->label); +        fail (is); +        return; +      } +      GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, +                                          &coin_pub.eddsa_pub); + +      priv = GNUNET_CRYPTO_eddsa_key_create (); +      cmd->details.deposit.merchant_priv.eddsa_priv = *priv; +      GNUNET_free (priv); +      if (0 != cmd->details.deposit.refund_deadline.rel_value_us) +      { +        refund_deadline = GNUNET_TIME_relative_to_absolute (cmd->details.deposit.refund_deadline); +      } +      else +      { +        refund_deadline = GNUNET_TIME_UNIT_ZERO_ABS; +      } +      GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.deposit.merchant_priv.eddsa_priv, +                                          &merchant_pub.eddsa_pub); + +      wire_deadline = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS); +      timestamp = GNUNET_TIME_absolute_get (); +      TALER_round_abs_time (×tamp); +      { +        struct TALER_DepositRequestPS dr; + +        memset (&dr, 0, sizeof (dr)); +        dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); +        dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); +        dr.h_contract = h_contract; +        TALER_hash_json (wire, +                         &dr.h_wire); +        dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); +        dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); +        dr.transaction_id = GNUNET_htonll (cmd->details.deposit.transaction_id); +        TALER_amount_hton (&dr.amount_with_fee, +                           &amount); +        TALER_amount_hton (&dr.deposit_fee, +                           &coin_pk->fee_deposit); +        dr.merchant = merchant_pub; +        dr.coin_pub = coin_pub; +        GNUNET_assert (GNUNET_OK == +                       GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv, +                                                 &dr.purpose, +                                                 &coin_sig.eddsa_signature)); +      } +      cmd->details.deposit.dh +        = TALER_BANK_deposit (bank, +                              &amount, +                              wire_deadline, +                              wire, +                              &h_contract, +                              &coin_pub, +                              coin_pk_sig, +                              &coin_pk->key, +                              timestamp, +                              cmd->details.deposit.transaction_id, +                              &merchant_pub, +                              refund_deadline, +                              &coin_sig, +                              &deposit_cb, +                              is); +      if (NULL == cmd->details.deposit.dh) +      { +        GNUNET_break (0); +        json_decref (wire); +        fail (is); +        return; +      } +      json_decref (wire); +      trigger_context_task (); +      return; +    } +  case OC_REFRESH_MELT: +    { +      unsigned int num_melted_coins; +      unsigned int num_fresh_coins; + +      cmd->details.refresh_melt.noreveal_index = UINT16_MAX; +      for (num_melted_coins=0; +           NULL != cmd->details.refresh_melt.melted_coins[num_melted_coins].amount; +           num_melted_coins++) ; +      for (num_fresh_coins=0; +           NULL != cmd->details.refresh_melt.fresh_amounts[num_fresh_coins]; +           num_fresh_coins++) ; + +      cmd->details.refresh_melt.fresh_pks +        = GNUNET_new_array (num_fresh_coins, +                            const struct TALER_BANK_DenomPublicKey *); +      { +        struct TALER_CoinSpendPrivateKeyP melt_privs[num_melted_coins]; +        struct TALER_Amount melt_amounts[num_melted_coins]; +        struct TALER_DenominationSignature melt_sigs[num_melted_coins]; +        struct TALER_BANK_DenomPublicKey melt_pks[num_melted_coins]; +        struct TALER_BANK_DenomPublicKey fresh_pks[num_fresh_coins]; +        unsigned int i; + +        for (i=0;i<num_melted_coins;i++) +        { +          const struct MeltDetails *md = &cmd->details.refresh_melt.melted_coins[i]; +          ref = find_command (is, +                              md->coin_ref); +          GNUNET_assert (NULL != ref); +          GNUNET_assert (OC_WITHDRAW_SIGN == ref->oc); + +          melt_privs[i] = ref->details.reserve_withdraw.coin_priv; +          if (GNUNET_OK != +              TALER_string_to_amount (md->amount, +                                      &melt_amounts[i])) +          { +            GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                        "Failed to parse amount `%s' at %u\n", +                        md->amount, +                        is->ip); +            fail (is); +            return; +          } +          melt_sigs[i] = ref->details.reserve_withdraw.sig; +          melt_pks[i] = *ref->details.reserve_withdraw.pk; +        } +        for (i=0;i<num_fresh_coins;i++) +        { +          if (GNUNET_OK != +              TALER_string_to_amount (cmd->details.refresh_melt.fresh_amounts[i], +                                      &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.refresh_melt.fresh_pks[i] +            = find_pk (is->keys, +                       &amount); +          fresh_pks[i] = *cmd->details.refresh_melt.fresh_pks[i]; +        } +        cmd->details.refresh_melt.refresh_data +          = TALER_BANK_refresh_prepare (num_melted_coins, +                                        melt_privs, +                                        melt_amounts, +                                        melt_sigs, +                                        melt_pks, +                                        GNUNET_YES, +                                        num_fresh_coins, +                                        fresh_pks, +                                        &cmd->details.refresh_melt.refresh_data_length); +        if (NULL == cmd->details.refresh_melt.refresh_data) +        { +          GNUNET_break (0); +          fail (is); +          return; +        } +        cmd->details.refresh_melt.rmh +          = TALER_BANK_refresh_melt (bank, +                                     cmd->details.refresh_melt.refresh_data_length, +                                     cmd->details.refresh_melt.refresh_data, +                                     &melt_cb, +                                     is); +        if (NULL == cmd->details.refresh_melt.rmh) +        { +          GNUNET_break (0); +          fail (is); +          return; +        } +      } +    } +    trigger_context_task (); +    return; +  case OC_REFRESH_REVEAL: +    ref = find_command (is, +                        cmd->details.refresh_reveal.melt_ref); +    cmd->details.refresh_reveal.rrh +      = TALER_BANK_refresh_reveal (bank, +                                   ref->details.refresh_melt.refresh_data_length, +                                   ref->details.refresh_melt.refresh_data, +                                   ref->details.refresh_melt.noreveal_index, +                                   &reveal_cb, +                                   is); +    if (NULL == cmd->details.refresh_reveal.rrh) +    { +      GNUNET_break (0); +      fail (is); +      return; +    } +    trigger_context_task (); +    return; +  case OC_REFRESH_LINK: +    /* find reveal command */ +    ref = find_command (is, +                        cmd->details.refresh_link.reveal_ref); +    /* find melt command */ +    ref = find_command (is, +                        ref->details.refresh_reveal.melt_ref); +    /* find reserve_withdraw command */ +    { +      unsigned int idx; +      const struct MeltDetails *md; +      unsigned int num_melted_coins; + +      for (num_melted_coins=0; +           NULL != ref->details.refresh_melt.melted_coins[num_melted_coins].amount; +           num_melted_coins++) ; +      idx = cmd->details.refresh_link.coin_idx; +      GNUNET_assert (idx < num_melted_coins); +      md = &ref->details.refresh_melt.melted_coins[idx]; +      ref = find_command (is, +                          md->coin_ref); +    } +    GNUNET_assert (OC_WITHDRAW_SIGN == ref->oc); +    /* finally, use private key from withdraw sign command */ +    cmd->details.refresh_link.rlh +      = TALER_BANK_refresh_link (bank, +                                 &ref->details.reserve_withdraw.coin_priv, +                                 &link_cb, +                                 is); +    if (NULL == cmd->details.refresh_link.rlh) +    { +      GNUNET_break (0); +      fail (is); +      return; +    } +    trigger_context_task (); +    return; +  case OC_WIRE: +    cmd->details.wire.wh = TALER_BANK_wire (bank, +                                            &wire_cb, +                                            is); +    trigger_context_task (); +    return; +  case OC_WIRE_DEPOSITS: +    if (NULL != cmd->details.wire_deposits.wtid_ref) +    { +      ref = find_command (is, +                          cmd->details.wire_deposits.wtid_ref); +      GNUNET_assert (NULL != ref); +      cmd->details.wire_deposits.wtid = ref->details.deposit_wtid.wtid; +    } +    cmd->details.wire_deposits.wdh +      = TALER_BANK_wire_deposits (bank, +                                  &cmd->details.wire_deposits.wtid, +                                  &wire_deposits_cb, +                                  is); +    trigger_context_task (); +    return; +  case OC_DEPOSIT_WTID: +    { +      struct GNUNET_HashCode h_wire; +      struct GNUNET_HashCode h_contract; +      json_t *wire; +      json_t *contract; +      const struct Command *coin; +      struct TALER_CoinSpendPublicKeyP coin_pub; + +      ref = find_command (is, +                          cmd->details.deposit_wtid.deposit_ref); +      GNUNET_assert (NULL != ref); +      coin = find_command (is, +                           ref->details.deposit.coin_ref); +      GNUNET_assert (NULL != coin); +      switch (coin->oc) +      { +      case OC_WITHDRAW_SIGN: +        GNUNET_CRYPTO_eddsa_key_get_public (&coin->details.reserve_withdraw.coin_priv.eddsa_priv, +                                            &coin_pub.eddsa_pub); +        break; +      case OC_REFRESH_REVEAL: +        { +          const struct FreshCoin *fc; +          unsigned int idx; + +          idx = ref->details.deposit.coin_idx; +          GNUNET_assert (idx < coin->details.refresh_reveal.num_fresh_coins); +          fc = &coin->details.refresh_reveal.fresh_coins[idx]; + +          GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, +                                              &coin_pub.eddsa_pub); +        } +        break; +      default: +        GNUNET_assert (0); +      } + +      wire = json_loads (ref->details.deposit.wire_details, +                         JSON_REJECT_DUPLICATES, +                         NULL); +      GNUNET_assert (NULL != wire); +      TALER_hash_json (wire, +                       &h_wire); +      json_decref (wire); +      contract = json_loads (ref->details.deposit.contract, +                             JSON_REJECT_DUPLICATES, +                             NULL); +      GNUNET_assert (NULL != contract); +      TALER_hash_json (contract, +                       &h_contract); +      json_decref (contract); +      cmd->details.deposit_wtid.dwh +        = TALER_BANK_deposit_wtid (bank, +                                   &ref->details.deposit.merchant_priv, +                                   &h_wire, +                                   &h_contract, +                                   &coin_pub, +                                   ref->details.deposit.transaction_id, +                                   &deposit_wtid_cb, +                                   is); +      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; +  } +} + + +/** + * 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_BANK_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_BANK_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_BANK_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_DEPOSIT: +      if (NULL != cmd->details.deposit.dh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_BANK_deposit_cancel (cmd->details.deposit.dh); +        cmd->details.deposit.dh = NULL; +      } +      break; +    case OC_REFRESH_MELT: +      if (NULL != cmd->details.refresh_melt.rmh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_BANK_refresh_melt_cancel (cmd->details.refresh_melt.rmh); +        cmd->details.refresh_melt.rmh = NULL; +      } +      GNUNET_free_non_null (cmd->details.refresh_melt.fresh_pks); +      cmd->details.refresh_melt.fresh_pks = NULL; +      GNUNET_free_non_null (cmd->details.refresh_melt.refresh_data); +      cmd->details.refresh_melt.refresh_data = NULL; +      cmd->details.refresh_melt.refresh_data_length = 0; +      break; +    case OC_REFRESH_REVEAL: +      if (NULL != cmd->details.refresh_reveal.rrh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_BANK_refresh_reveal_cancel (cmd->details.refresh_reveal.rrh); +        cmd->details.refresh_reveal.rrh = NULL; +      } +      { +        unsigned int j; +        struct FreshCoin *fresh_coins; + +        fresh_coins = cmd->details.refresh_reveal.fresh_coins; +        for (j=0;j<cmd->details.refresh_reveal.num_fresh_coins;j++) +          GNUNET_CRYPTO_rsa_signature_free (fresh_coins[j].sig.rsa_signature); +      } +      GNUNET_free_non_null (cmd->details.refresh_reveal.fresh_coins); +      cmd->details.refresh_reveal.fresh_coins = NULL; +      cmd->details.refresh_reveal.num_fresh_coins = 0; +      break; +    case OC_REFRESH_LINK: +      if (NULL != cmd->details.refresh_link.rlh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_BANK_refresh_link_cancel (cmd->details.refresh_link.rlh); +        cmd->details.refresh_link.rlh = NULL; +      } +      break; +    case OC_WIRE: +      if (NULL != cmd->details.wire.wh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_BANK_wire_cancel (cmd->details.wire.wh); +        cmd->details.wire.wh = NULL; +      } +      break; +    case OC_WIRE_DEPOSITS: +      if (NULL != cmd->details.wire_deposits.wdh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_BANK_wire_deposits_cancel (cmd->details.wire_deposits.wdh); +        cmd->details.wire_deposits.wdh = NULL; +      } +      break; +    case OC_DEPOSIT_WTID: +      if (NULL != cmd->details.deposit_wtid.dwh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_BANK_deposit_wtid_cancel (cmd->details.deposit_wtid.dwh); +        cmd->details.deposit_wtid.dwh = NULL; +      } +      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 != bank) +  { +    TALER_BANK_disconnect (bank); +    bank = NULL; +  } +  if (NULL != ctx) +  { +    TALER_BANK_fini (ctx); +    ctx = NULL; +  } +} + + +/** + * Functions of this type are called to provide the retrieved signing and + * denomination keys of the bank.  No TALER_BANK_*() functions should be called + * in this callback. + * + * @param cls closure + * @param keys information about keys of the bank + */ +static void +cert_cb (void *cls, +         const struct TALER_BANK_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_BANK_perform (ctx); +  max_fd = -1; +  timeout = -1; +  FD_ZERO (&read_fd_set); +  FD_ZERO (&write_fd_set); +  FD_ZERO (&except_fd_set); +  TALER_BANK_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 MeltDetails melt_coins_1[] = { +    { .amount = "EUR:4", +      .coin_ref = "refresh-withdraw-coin-1" }, +    { NULL, NULL } +  }; +  static const char *melt_fresh_amounts_1[] = { +    "EUR:1", +    "EUR:1", +    "EUR:1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.01", +    "EUR:0.01", +    "EUR:0.01", +    "EUR:0.01", +    "EUR:0.01", +    "EUR:0.01", +    /* with 0.01 withdraw fees (except for 1ct coins), +       this totals up to exactly EUR:3.97, and with +       the 0.03 refresh fee, to EUR:4.0*/ +    NULL +  }; +  static struct Command commands[] = +  { +    /* *************** start of /wire testing ************** */ + +#if WIRE_TEST +    { .oc = OC_WIRE, +      .label = "wire-test", +      /* /wire/test replies with a 302 redirect */ +      .expected_response_code = MHD_HTTP_FOUND, +      .details.wire.format = "test" }, +#endif +#if WIRE_SEPA +    { .oc = OC_WIRE, +      .label = "wire-sepa", +      /* /wire/sepa replies with a 200 redirect */ +      .expected_response_code = MHD_HTTP_OK, +      .details.wire.format = "sepa" }, +#endif +    /* *************** end of /wire testing ************** */ + +#if WIRE_TEST +    /* None of this works if 'test' is not allowed as we do +       /admin/add/incoming with format 'test' */ + +    /* 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 deposit the 5 EUR coin (in full) */ +    { .oc = OC_DEPOSIT, +      .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 overdraw funds ... */ +    { .oc = OC_WITHDRAW_SIGN, +      .label = "withdraw-coin-2", +      .expected_response_code = MHD_HTTP_PAYMENT_REQUIRED, +      .details.reserve_withdraw.reserve_reference = "create-reserve-1", +      .details.reserve_withdraw.amount = "EUR:5" }, + +    /* Try to double-spend the 5 EUR coin with different wire details */ +    { .oc = OC_DEPOSIT, +      .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_DEPOSIT, +      .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_DEPOSIT, +      .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 }, + +    /* ***************** /refresh testing ******************** */ + +    /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct */ +    { .oc = OC_ADMIN_ADD_INCOMING, +      .label = "refresh-create-reserve-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.admin_add_incoming.wire = "{ \"type\":\"TEST\", \"bank\":\"source bank\", \"account\":424 }", +      .details.admin_add_incoming.amount = "EUR:5.01" }, +    /* Withdraw a 5 EUR coin, at fee of 1 ct */ +    { .oc = OC_WITHDRAW_SIGN, +      .label = "refresh-withdraw-coin-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.reserve_withdraw.reserve_reference = "refresh-create-reserve-1", +      .details.reserve_withdraw.amount = "EUR:5" }, +    /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in full) +       (merchant would receive EUR:0.99 due to 1 ct deposit fee) */ +    { .oc = OC_DEPOSIT, +      .label = "refresh-deposit-partial", +      .expected_response_code = MHD_HTTP_OK, +      .details.deposit.amount = "EUR:1", +      .details.deposit.coin_ref = "refresh-withdraw-coin-1", +      .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", +      .details.deposit.contract = "{ \"items\" : [ { \"name\":\"ice cream\", \"value\":\"EUR:1\" } ] }", +      .details.deposit.transaction_id = 42421 }, + +    /* Melt the rest of the coin's value (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */ + +    { .oc = OC_REFRESH_MELT, +      .label = "refresh-melt-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.refresh_melt.melted_coins = melt_coins_1, +      .details.refresh_melt.fresh_amounts = melt_fresh_amounts_1 }, + + +    /* Complete (successful) melt operation, and withdraw the coins */ +    { .oc = OC_REFRESH_REVEAL, +      .label = "refresh-reveal-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.refresh_reveal.melt_ref = "refresh-melt-1" }, + +    /* Test that /refresh/link works */ +    { .oc = OC_REFRESH_LINK, +      .label = "refresh-link-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.refresh_link.reveal_ref = "refresh-reveal-1" }, + + +    /* Test successfully spending coins from the refresh operation: +       first EUR:1 */ +    { .oc = OC_DEPOSIT, +      .label = "refresh-deposit-refreshed-1a", +      .expected_response_code = MHD_HTTP_OK, +      .details.deposit.amount = "EUR:1", +      .details.deposit.coin_ref = "refresh-reveal-1", +      .details.deposit.coin_idx = 0, +      .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", +      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }", +      .details.deposit.transaction_id = 2 }, + +    /* Test successfully spending coins from the refresh operation: +       finally EUR:0.1 */ +    { .oc = OC_DEPOSIT, +      .label = "refresh-deposit-refreshed-1b", +      .expected_response_code = MHD_HTTP_OK, +      .details.deposit.amount = "EUR:0.1", +      .details.deposit.coin_ref = "refresh-reveal-1", +      .details.deposit.coin_idx = 4, +      .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", +      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }", +      .details.deposit.transaction_id = 2 }, + +    /* Test running a failing melt operation (same operation again must fail) */ +    { .oc = OC_REFRESH_MELT, +      .label = "refresh-melt-failing", +      .expected_response_code = MHD_HTTP_FORBIDDEN, +      .details.refresh_melt.melted_coins = melt_coins_1, +      .details.refresh_melt.fresh_amounts = melt_fresh_amounts_1 }, + +    // FIXME: also test with coin that was already melted +    // (signature differs from coin that was deposited...) +    /* *************** end of /refresh testing ************** */ + +    /* ************** Test tracking API ******************** */ +    /* Try resolving a deposit's WTID, as we never triggered +       execution of transactions, the answer should be that +       the bank knows about the deposit, but has no WTID yet. */ +    { .oc = OC_DEPOSIT_WTID, +      .label = "deposit-wtid-found", +      .expected_response_code = MHD_HTTP_ACCEPTED, +      .details.deposit_wtid.deposit_ref = "deposit-simple" }, +    /* Try resolving a deposit's WTID for a failed deposit. +       As the deposit failed, the answer should be that +       the bank does NOT know about the deposit. */ +    { .oc = OC_DEPOSIT_WTID, +      .label = "deposit-wtid-failing", +      .expected_response_code = MHD_HTTP_NOT_FOUND, +      .details.deposit_wtid.deposit_ref = "deposit-double-2" }, +    /* Try resolving an undefined (all zeros) WTID; this +       should fail as obviously the bank didn't use that +       WTID value for any transaction. */ +    { .oc = OC_WIRE_DEPOSITS, +      .label = "wire-deposit-failing", +      .expected_response_code = MHD_HTTP_NOT_FOUND }, + +    /* TODO: trigger aggregation logic and then check the +       cases where tracking succeeds! */ + +    /* ************** End of tracking API testing************* */ + + +#endif + +    { .oc = OC_END } +  }; + +  is = GNUNET_new (struct InterpreterState); +  is->commands = commands; + +  ctx = TALER_BANK_init (); +  GNUNET_assert (NULL != ctx); +  ctx_task = GNUNET_SCHEDULER_add_now (&context_task, +                                       ctx); +  bank = TALER_BANK_connect (ctx, +                             "http://localhost:8081", +                             &cert_cb, is, +                             TALER_BANK_OPTION_END); +  GNUNET_assert (NULL != bank); +  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 bank 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 *bankd; + +  GNUNET_log_setup ("test-bank-api", +                    "WARNING", +                    NULL); +  proc = GNUNET_OS_start_process (GNUNET_NO, +                                  GNUNET_OS_INHERIT_STD_ALL, +                                  NULL, NULL, NULL, +                                  "taler-bank-keyup", +                                  "taler-bank-keyup", +                                  "-d", "test-bank-home", +                                  "-m", "test-bank-home/master.priv", +                                  NULL); +  GNUNET_OS_process_wait (proc); +  GNUNET_OS_process_destroy (proc); +  bankd = GNUNET_OS_start_process (GNUNET_NO, +                                   GNUNET_OS_INHERIT_STD_ALL, +                                   NULL, NULL, NULL, +                                   "taler-bank-httpd", +                                   "taler-bank-httpd", +                                   "-d", "test-bank-home", +                                   NULL); +  /* give child time to start and bind against the socket */ +  fprintf (stderr, "Waiting for taler-bank-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 (bankd, +                          SIGTERM); +  GNUNET_OS_process_wait (bankd); +  GNUNET_OS_process_destroy (bankd); +  return (GNUNET_OK == result) ? 0 : 1; +} + +/* end of test_bank_api.c */ diff --git a/src/include/taler_bank_service.h b/src/include/taler_bank_service.h new file mode 100644 index 00000000..b19e213a --- /dev/null +++ b/src/include/taler_bank_service.h @@ -0,0 +1,160 @@ +/* +  This file is part of TALER +  Copyright (C) 2015, 2016 GNUnet e.V. + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU 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 include/taler_bank_service.h + * @brief C interface of libtalerbank, a C library to use the Taler bank's HTTP API + * @author Christian Grothoff + */ +#ifndef _TALER_BANK_SERVICE_H +#define _TALER_BANK_SERVICE_H + +#include "taler_util.h" + +/* ********************* event loop *********************** */ + +/** + * @brief Handle to this library context.  This is where the + * main event loop logic lives. + */ +struct TALER_BANK_Context; + + +/** + * Initialise a context.  A context should be used for each thread and should + * not be shared among multiple threads. + * + * @param url HTTP base URL for the bank + * @return the context, NULL on error (failure to initialize) + */ +struct TALER_BANK_Context * +TALER_BANK_init (const char *url); + + +/** + * Obtain the information for a select() call to wait until + * #TALER_BANK_perform() is ready again.  Note that calling + * any other TALER_BANK-API may also imply that the library + * is again ready for #TALER_BANK_perform(). + * + * Basically, a client should use this API to prepare for select(), + * then block on select(), then call #TALER_BANK_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_BANK_perform(). + */ +void +TALER_BANK_get_select_info (struct TALER_BANK_Context *ctx, +                            fd_set *read_fd_set, +                            fd_set *write_fd_set, +                            fd_set *except_fd_set, +                            int *max_fd, +                            long *timeout); + + +/** + * Run the main event loop for the Taler interaction. + * + * @param ctx the library context + */ +void +TALER_BANK_perform (struct TALER_BANK_Context *ctx); + + +/** + * 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_BANK_fini (struct TALER_BANK_Context *ctx); + + +/* ********************* /admin/add/incoming *********************** */ + + +/** + * @brief A /admin/add/incoming Handle + */ +struct TALER_BANK_AdminAddIncomingHandle; + + +/** + * Callbacks of this type are used to serve the result of submitting + * information about an incoming transaction to a bank. + * + * @param cls closure + * @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) + */ +typedef void +(*TALER_BANK_AdminAddIncomingResultCallback) (void *cls, +                                              unsigned int http_status); + + +/** + * Notify the bank that we have received an incoming transaction + * which fills a reserve.  Note that this API is an administrative + * API and thus not accessible to typical bank clients, but only + * to the operators of the bank. + * + * @param bank the bank handle; the bank must be ready to operate + * @param reserve_pub public key of the reserve + * @param amount amount that was deposited + * @param execution_date when did we receive the amount + * @param wire wire details + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for the above callback + * @return NULL + *         if the inputs are invalid (i.e. invalid amount). + *         In this case, the callback is not called. + */ +struct TALER_BANK_AdminAddIncomingHandle * +TALER_BANK_admin_add_incoming (struct TALER_BANK_Context *bank, +                               const struct TALER_WireTransferIdentifierRawP *wtid, +                               const struct TALER_Amount *amount, +                               const json_t *wire, +                               TALER_BANK_AdminAddIncomingResultCallback res_cb, +                               void *res_cb_cls); + + +/** + * Cancel an add incoming.  This function cannot be used on a request + * handle if a response is already served for it. + * + * @param aai the admin add incoming request handle + */ +void +TALER_BANK_admin_add_incoming_cancel (struct TALER_BANK_AdminAddIncomingHandle *aai); + +#endif  /* _TALER_BANK_SERVICE_H */ | 
