diff options
| author | Christian Grothoff <christian@grothoff.org> | 2015-03-28 11:06:00 +0100 | 
|---|---|---|
| committer | Christian Grothoff <christian@grothoff.org> | 2015-03-28 11:06:00 +0100 | 
| commit | c626ccac51dc968725b2ead1ecb0cebedd9987fc (patch) | |
| tree | 3acda45b9071865598d2b2670bf6b4bc3f29a57f /src/mintdb | |
| parent | eae1896a4bfc02b5d7586f81bbedfea69b29acc8 (diff) | |
second round of renamefest
Diffstat (limited to 'src/mintdb')
| -rw-r--r-- | src/mintdb/Makefile.am | 68 | ||||
| -rw-r--r-- | src/mintdb/mintdb_keyio.c | 347 | ||||
| -rw-r--r-- | src/mintdb/mintdb_plugin.c | 149 | ||||
| -rw-r--r-- | src/mintdb/plugin_mintdb_common.c | 118 | ||||
| -rw-r--r-- | src/mintdb/plugin_mintdb_postgres.c | 2356 | ||||
| -rw-r--r-- | src/mintdb/test_mintdb.c | 393 | ||||
| -rw-r--r-- | src/mintdb/test_mintdb_deposits.c | 142 | ||||
| -rw-r--r-- | src/mintdb/test_mintdb_keyio.c | 86 | 
8 files changed, 3659 insertions, 0 deletions
diff --git a/src/mintdb/Makefile.am b/src/mintdb/Makefile.am new file mode 100644 index 00000000..ceaa2e21 --- /dev/null +++ b/src/mintdb/Makefile.am @@ -0,0 +1,68 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/pq/ $(POSTGRESQL_CPPFLAGS) + +plugindir = $(libdir)/taler + +plugin_LTLIBRARIES = \ +  libtaler_plugin_mintdb_postgres.la + +EXTRA_DIST = plugin_mintdb_common.c + +libtaler_plugin_mintdb_postgres_la_SOURCES = \ +  plugin_mintdb_postgres.c +libtaler_plugin_mintdb_postgres_la_LIBADD = \ +  $(LTLIBINTL) +libtaler_plugin_mintdb_postgres_la_LDFLAGS = \ +  $(TALER_PLUGIN_LDFLAGS) \ +  -lpq \ +  -lgnunetutil + +lib_LTLIBRARIES = \ +  libtalermintdb.la + +libtalermintdb_la_SOURCES = \ +  mintdb_keyio.c \ +  mintdb_plugin.c + +libtalermintdb_la_LIBADD = \ +  $(top_builddir)/src/util/libtalerutil.la \ +  -lgnunetutil + +libtalermintdb_la_LDFLAGS = \ +  $(POSTGRESQL_LDFLAGS) \ +  -version-info 0:0:0 \ +  -no-undefined + + +check_PROGRAMS = \ +  test-mintdb-deposits \ +  test-mintdb-keyio \ +  test-mintdb-postgres + +test_mintdb_deposits_SOURCES = \ +  test_mintdb_deposits.c +test_mintdb_deposits_LDADD = \ +  libtalermintdb.la \ +  $(top_srcdir)/src/util/libtalerutil.la \ +  $(top_srcdir)/src/pq/libtalerpq.la \ +  -lgnunetutil \ +  -ljansson \ +  -lpq + +test_mintdb_keyio_SOURCES = \ +  test_mintdb_keyio.c +test_mintdb_keyio_LDADD = \ +  libtalermintdb.la \ +  $(top_srcdir)/src/util/libtalerutil.la \ +  $(top_srcdir)/src/pq/libtalerpq.la \ +  -lgnunetutil + +test_mintdb_postgres_SOURCES = \ +  test_mintdb.c +test_mintdb_postgres_LDADD = \ +  libtalermintdb.la \ +  $(top_srcdir)/src/util/libtalerutil.la \ +  $(top_srcdir)/src/pq/libtalerpq.la \ +  -lgnunetutil -ljansson +EXTRA_test_mintdb_postgres_DEPENDENCIES = \ +  libtaler_plugin_mintdb_postgres.la diff --git a/src/mintdb/mintdb_keyio.c b/src/mintdb/mintdb_keyio.c new file mode 100644 index 00000000..321b890c --- /dev/null +++ b/src/mintdb/mintdb_keyio.c @@ -0,0 +1,347 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mintdb/mintdb_keyio.c + * @brief I/O operations for the Mint's private keys + * @author Florian Dold + * @author Benedikt Mueller + * @author Sree Harsha Totakura + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_mintdb_lib.h" + + +/** + * Closure for the #signkeys_iterate_dir_iter(). + */ +struct SignkeysIterateContext +{ + +  /** +   * Function to call on each signing key. +   */ +  TALER_MINT_SignkeyIterator it; + +  /** +   * Closure for @e it. +   */ +  void *it_cls; +}; + + +/** + * Function called on each file in the directory with + * our signing keys. Parses the file and calls the + * iterator from @a cls. + * + * @param cls the `struct SignkeysIterateContext *` + * @param filename name of the file to parse + * @return #GNUNET_OK to continue, + *         #GNUNET_NO to stop iteration without error, + *         #GNUNET_SYSERR to stop iteration with error + */ +static int +signkeys_iterate_dir_iter (void *cls, +                           const char *filename) +{ +  struct SignkeysIterateContext *skc = cls; +  ssize_t nread; +  struct TALER_MintSigningKeyValidityPSPriv issue; + +  nread = GNUNET_DISK_fn_read (filename, +                               &issue, +                               sizeof (struct TALER_MintSigningKeyValidityPSPriv)); +  if (nread != sizeof (struct TALER_MintSigningKeyValidityPSPriv)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Invalid signkey file `%s': wrong size\n", +                filename); +    return GNUNET_OK; +  } +  return skc->it (skc->it_cls, +                  filename, +                  &issue); +} + + +/** + * Call @a it for each signing key found in the @a mint_base_dir. + * + * @param mint_base_dir base directory for the mint, + *                      the signing keys must be in the #DIR_SIGNKEYS + *                      subdirectory + * @param it function to call on each signing key + * @param it_cls closure for @a it + * @return number of files found (may not match + *         number of keys given to @a it as malformed + *         files are simply skipped), -1 on error + */ +int +TALER_MINT_signkeys_iterate (const char *mint_base_dir, +                             TALER_MINT_SignkeyIterator it, +                             void *it_cls) +{ +  char *signkey_dir; +  struct SignkeysIterateContext skc; +  int ret; + +  GNUNET_asprintf (&signkey_dir, +                   "%s" DIR_SEPARATOR_STR DIR_SIGNKEYS, +                   mint_base_dir); +  skc.it = it; +  skc.it_cls = it_cls; +  ret = GNUNET_DISK_directory_scan (signkey_dir, +                                    &signkeys_iterate_dir_iter, +                                    &skc); +  GNUNET_free (signkey_dir); +  return ret; +} + + +/** + * Import a denomination key from the given file. + * + * @param filename the file to import the key from + * @param[OUT] dki set to the imported denomination key + * @return #GNUNET_OK upon success; + *         #GNUNET_SYSERR upon failure + */ +int +TALER_MINT_read_denom_key (const char *filename, +                           struct TALER_DenominationKeyIssueInformation *dki) +{ +  uint64_t size; +  size_t offset; +  void *data; +  struct GNUNET_CRYPTO_rsa_PrivateKey *priv; + +  if (GNUNET_OK != GNUNET_DISK_file_size (filename, +                                          &size, +                                          GNUNET_YES, +                                          GNUNET_YES)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                "Skipping inaccessable denomination key file `%s'\n", +                filename); +    return GNUNET_SYSERR; +  } +  offset = sizeof (struct TALER_DenominationKeyValidityPS); +  if (size <= offset) +  { +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  data = GNUNET_malloc (size); +  if (size != +      GNUNET_DISK_fn_read (filename, +                           data, +                           size)) +  { +    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, +                              "read", +                              filename); +    GNUNET_free (data); +    return GNUNET_SYSERR; +  } +  if (NULL == +      (priv = GNUNET_CRYPTO_rsa_private_key_decode (data + offset, +                                                    size - offset))) +  { +    GNUNET_free (data); +    return GNUNET_SYSERR; +  } +  dki->denom_priv.rsa_private_key = priv; +  dki->denom_pub.rsa_public_key +    = GNUNET_CRYPTO_rsa_private_key_get_public (priv); +  memcpy (&dki->issue, +          data, +          offset); +  GNUNET_free (data); +  return GNUNET_OK; +} + + +/** + * Exports a denomination key to the given file. + * + * @param filename the file where to write the denomination key + * @param dki the denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. + */ +int +TALER_MINT_write_denom_key (const char *filename, +                            const struct TALER_DenominationKeyIssueInformation *dki) +{ +  char *priv_enc; +  size_t priv_enc_size; +  struct GNUNET_DISK_FileHandle *fh; +  ssize_t wrote; +  size_t wsize; +  int ret; + +  fh = NULL; +  priv_enc_size +    = GNUNET_CRYPTO_rsa_private_key_encode (dki->denom_priv.rsa_private_key, +                                            &priv_enc); +  ret = GNUNET_SYSERR; +  if (NULL == (fh = GNUNET_DISK_file_open +               (filename, +                GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE, +                GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE))) +    goto cleanup; +  wsize = sizeof (struct TALER_DenominationKeyValidityPS); +  if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh, +                                                        &dki->issue.signature, +                                                        wsize))) +    goto cleanup; +  if (wrote != wsize) +    goto cleanup; +  if (GNUNET_SYSERR == +      (wrote = GNUNET_DISK_file_write (fh, +                                       priv_enc, +                                       priv_enc_size))) +    goto cleanup; +  if (wrote != priv_enc_size) +    goto cleanup; +  ret = GNUNET_OK; + cleanup: +  GNUNET_free_non_null (priv_enc); +  if (NULL != fh) +    (void) GNUNET_DISK_file_close (fh); +  return ret; +} + + +/** + * Closure for #denomkeys_iterate_keydir_iter() and + * #denomkeys_iterate_topdir_iter(). + */ +struct DenomkeysIterateContext +{ + +  /** +   * Set to the name of the directory below the top-level directory +   * during the call to #denomkeys_iterate_keydir_iter(). +   */ +  const char *alias; + +  /** +   * Function to call on each denomination key. +   */ +  TALER_MINT_DenomkeyIterator it; + +  /** +   * Closure for @e it. +   */ +  void *it_cls; +}; + + +/** + * Decode the denomination key in the given file @a filename and call + * the callback in @a cls with the information. + * + * @param cls the `struct DenomkeysIterateContext *` + * @param filename name of a file that should contain + *                 a denomination key + * @return #GNUNET_OK to continue to iterate + *         #GNUNET_NO to abort iteration with success + *         #GNUNET_SYSERR to abort iteration with failure + */ +static int +denomkeys_iterate_keydir_iter (void *cls, +                               const char *filename) +{ +  struct DenomkeysIterateContext *dic = cls; +  struct TALER_DenominationKeyIssueInformation issue; + +  if (GNUNET_OK != +      TALER_MINT_read_denom_key (filename, +                                 &issue)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Invalid denomkey file: '%s'\n", +                filename); +    return GNUNET_OK; +  } +  return dic->it (dic->it_cls, +                  dic->alias, +                  &issue); +} + + +/** + * Function called on each subdirectory in the #DIR_DENOMKEYS.  Will + * call the #denomkeys_iterate_keydir_iter() on each file in the + * subdirectory. + * + * @param cls the `struct DenomkeysIterateContext *` + * @param filename name of the subdirectory to scan + * @return #GNUNET_OK on success, + *         #GNUNET_SYSERR if we need to abort + */ +static int +denomkeys_iterate_topdir_iter (void *cls, +                               const char *filename) +{ +  struct DenomkeysIterateContext *dic = cls; + +  dic->alias = GNUNET_STRINGS_get_short_name (filename); +  if (0 > GNUNET_DISK_directory_scan (filename, +                                      &denomkeys_iterate_keydir_iter, +                                      dic)) +    return GNUNET_SYSERR; +  return GNUNET_OK; +} + + +/** + * Call @a it for each denomination key found in the @a mint_base_dir. + * + * @param mint_base_dir base directory for the mint, + *                      the signing keys must be in the #DIR_DENOMKEYS + *                      subdirectory + * @param it function to call on each denomination key found + * @param it_cls closure for @a it + * @return -1 on error, 0 if no files were found, otherwise + *         a positive number (however, even with a positive + *         number it is possible that @a it was never called + *         as maybe none of the files were well-formed) + */ +int +TALER_MINT_denomkeys_iterate (const char *mint_base_dir, +                              TALER_MINT_DenomkeyIterator it, +                              void *it_cls) +{ +  char *dir; +  struct DenomkeysIterateContext dic; +  int ret; + +  GNUNET_asprintf (&dir, +                   "%s" DIR_SEPARATOR_STR DIR_DENOMKEYS, +                   mint_base_dir); +  dic.it = it; +  dic.it_cls = it_cls; +  ret = GNUNET_DISK_directory_scan (dir, +                                    &denomkeys_iterate_topdir_iter, +                                    &dic); +  GNUNET_free (dir); +  return ret; +} + + +/* end of key_io.c */ diff --git a/src/mintdb/mintdb_plugin.c b/src/mintdb/mintdb_plugin.c new file mode 100644 index 00000000..b109ff3d --- /dev/null +++ b/src/mintdb/mintdb_plugin.c @@ -0,0 +1,149 @@ +/* +  This file is part of TALER +  Copyright (C) 2015 Christian Grothoff (and other contributing authors) + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mintdb/mintdb_plugin.c + * @brief Logic to load database plugin + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_mintdb_lib.h" +#include "taler_mintdb_plugin.h" +#include <ltdl.h> + + +/** + * Libtool search path before we started. + */ +static char *old_dlsearchpath; + + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +struct TALER_MINTDB_Plugin * +TALER_MINT_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ +  char *plugin_name; +  char *lib_name; +  struct GNUNET_CONFIGURATION_Handle *cfg_dup; +  struct TALER_MINTDB_Plugin *plugin; + +  if (GNUNET_SYSERR == +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "mint", +                                             "db", +                                             &plugin_name)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "mint", +                               "db"); +    return NULL; +  } +  (void) GNUNET_asprintf (&lib_name, +                          "libtaler_plugin_mintdb_%s", +                          plugin_name); +  GNUNET_free (plugin_name); +  cfg_dup = GNUNET_CONFIGURATION_dup (cfg); +  plugin = GNUNET_PLUGIN_load (lib_name, cfg_dup); +  if (NULL != plugin) +    plugin->library_name = lib_name; +  else +    GNUNET_free (lib_name); +  GNUNET_CONFIGURATION_destroy (cfg_dup); +  return plugin; +} + + +/** + * Shutdown the plugin. + * + * @param plugin the plugin to unload + */ +void +TALER_MINT_plugin_unload (struct TALER_MINTDB_Plugin *plugin) +{ +  char *lib_name; + +  if (NULL == plugin) +    return; +  lib_name = plugin->library_name; +  GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name, +                                               plugin)); +  GNUNET_free (lib_name); +} + + +/** + * Setup libtool paths. + */ +void __attribute__ ((constructor)) +plugin_init () +{ +  int err; +  const char *opath; +  char *path; +  char *cpath; + +  err = lt_dlinit (); +  if (err > 0) +  { +    FPRINTF (stderr, +             _("Initialization of plugin mechanism failed: %s!\n"), +             lt_dlerror ()); +    return; +  } +  opath = lt_dlgetsearchpath (); +  if (NULL != opath) +    old_dlsearchpath = GNUNET_strdup (opath); +  path = TALER_os_installation_get_path (GNUNET_OS_IPK_LIBDIR); +  if (NULL != path) +  { +    if (NULL != opath) +    { +      GNUNET_asprintf (&cpath, "%s:%s", opath, path); +      lt_dlsetsearchpath (cpath); +      GNUNET_free (path); +      GNUNET_free (cpath); +    } +    else +    { +      lt_dlsetsearchpath (path); +      GNUNET_free (path); +    } +  } +} + + +/** + * Shutdown libtool. + */ +void __attribute__ ((destructor)) +plugin_fini () +{ +  lt_dlsetsearchpath (old_dlsearchpath); +  if (NULL != old_dlsearchpath) +  { +    GNUNET_free (old_dlsearchpath); +    old_dlsearchpath = NULL; +  } +  lt_dlexit (); +} + + +/* end of plugin.c */ diff --git a/src/mintdb/plugin_mintdb_common.c b/src/mintdb/plugin_mintdb_common.c new file mode 100644 index 00000000..a95cf4be --- /dev/null +++ b/src/mintdb/plugin_mintdb_common.c @@ -0,0 +1,118 @@ +/* +  This file is part of TALER +  Copyright (C) 2015 Christian Grothoff (and other contributing authors) + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mint/plugin_mintdb_common.c + * @brief Functions shared across plugins, this file is meant to be + *        #include-d in each plugin. + * @author Christian Grothoff + */ + +/** + * Free memory associated with the given reserve history. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param rh history to free. + */ +static void +common_free_reserve_history (void *cls, +                             struct ReserveHistory *rh) +{ +  struct BankTransfer *bt; +  struct CollectableBlindcoin *cbc; +  struct ReserveHistory *backref; + +  while (NULL != rh) +  { +    switch(rh->type) +    { +    case TALER_MINT_DB_RO_BANK_TO_MINT: +      bt = rh->details.bank; +      if (NULL != bt->wire) +        json_decref (bt->wire); +      GNUNET_free (bt); +      break; +    case TALER_MINT_DB_RO_WITHDRAW_COIN: +      cbc = rh->details.withdraw; +      GNUNET_CRYPTO_rsa_signature_free (cbc->sig.rsa_signature); +      GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub.rsa_public_key); +      GNUNET_free (cbc); +      break; +    } +    backref = rh; +    rh = rh->next; +    GNUNET_free (backref); +  } +} + + +/** + * Free memory of the link data list. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param ldl link data list to release + */ +static void +common_free_link_data_list (void *cls, +                            struct LinkDataList *ldl) +{ +  struct LinkDataList *next; + +  while (NULL != ldl) +  { +    next = ldl->next; +    GNUNET_free (ldl->link_data_enc); +    GNUNET_free (ldl); +    ldl = next; +  } +} + + +/** + * Free linked list of transactions. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param list list to free + */ +static void +common_free_coin_transaction_list (void *cls, +                                   struct TALER_MINT_DB_TransactionList *list) +{ +  struct TALER_MINT_DB_TransactionList *next; + +  while (NULL != list) +  { +    next = list->next; + +    switch (list->type) +    { +    case TALER_MINT_DB_TT_DEPOSIT: +      json_decref (list->details.deposit->wire); +      GNUNET_free (list->details.deposit); +      break; +    case TALER_MINT_DB_TT_REFRESH_MELT: +      GNUNET_free (list->details.melt); +      break; +    case TALER_MINT_DB_TT_LOCK: +      GNUNET_free (list->details.lock); +      /* FIXME: look at this again once locking is implemented (#3625) */ +      break; +    } +    GNUNET_free (list); +    list = next; +  } +} + +/* end of plugin_mintdb_common.c */ diff --git a/src/mintdb/plugin_mintdb_postgres.c b/src/mintdb/plugin_mintdb_postgres.c new file mode 100644 index 00000000..fa2c19db --- /dev/null +++ b/src/mintdb/plugin_mintdb_postgres.c @@ -0,0 +1,2356 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file plugin_mintdb_postgres.c + * @brief Low-level (statement-level) Postgres database access for the mint + * @author Florian Dold + * @author Christian Grothoff + * @author Sree Harsha Totakura + */ +#include "platform.h" +#include "taler_pq_lib.h" +#include "taler_signatures.h" +#include "taler_mintdb_plugin.h" +#include <pthread.h> +#include <libpq-fe.h> + +#include "plugin_mintdb_common.c" + +#define TALER_TEMP_SCHEMA_NAME "taler_temporary" + +#define QUERY_ERR(result)                          \ +  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed at %s:%u: %s\n", __FILE__, __LINE__, PQresultErrorMessage (result)) + + +#define BREAK_DB_ERR(result) do { \ +    GNUNET_break(0); \ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ +  } while (0) + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond)                                              \ +  do {                                                            \ +    if (cond) { GNUNET_break (0); goto EXITIF_exit; }             \ +  } while (0) + + +#define SQLEXEC_(conn, sql, result)                                     \ +  do {                                                                  \ +    result = PQexec (conn, sql);                                        \ +    if (PGRES_COMMAND_OK != PQresultStatus (result))                    \ +    {                                                                   \ +      BREAK_DB_ERR (result);                                            \ +      PQclear (result); result = NULL;                                  \ +      goto SQLEXEC_fail;                                                \ +    }                                                                   \ +    PQclear (result); result = NULL;                                    \ +  } while (0) + +/** + * This the length of the currency strings (without 0-termination) we use.  Note + * that we need to use this at the DB layer instead of TALER_CURRENCY_LEN as the + * DB only needs to store 3 bytes instead of 8 bytes. + */ +#define TALER_PQ_CURRENCY_LEN 3 + + +/** + * Handle for a database session (per-thread, for transactions). + */ +struct TALER_MINTDB_Session +{ +  /** +   * Postgres connection handle. +   */ +  PGconn *conn; +}; + + +/** + * Type of the "cls" argument given to each of the functions in + * our API. + */ +struct PostgresClosure +{ + +  /** +   * Thread-local database connection. +   * Contains a pointer to PGconn or NULL. +   */ +  pthread_key_t db_conn_threadlocal; + +  /** +   * Database connection string, as read from +   * the configuration. +   */ +  char *connection_cfg_str; +}; + + + +/** + * Set the given connection to use a temporary schema + * + * @param db the database connection + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon error + */ +static int +set_temporary_schema (PGconn *db) +{ +  PGresult *result; + +  SQLEXEC_(db, +           "CREATE SCHEMA IF NOT EXISTS " TALER_TEMP_SCHEMA_NAME ";" +           "SET search_path to " TALER_TEMP_SCHEMA_NAME ";", +           result); +  return GNUNET_OK; + SQLEXEC_fail: +  return GNUNET_SYSERR; +} + + +/** + * Drop the temporary taler schema.  This is only useful for testcases + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_drop_temporary (void *cls, +                         struct TALER_MINTDB_Session *session) +{ +  PGresult *result; + +  SQLEXEC_ (session->conn, +            "DROP SCHEMA " TALER_TEMP_SCHEMA_NAME " CASCADE;", +            result); +  return GNUNET_OK; + SQLEXEC_fail: +  return GNUNET_SYSERR; +} + + +/** + * Create the necessary tables if they are not present + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param temporary should we use a temporary schema + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_create_tables (void *cls, +                        int temporary) +{ +  struct PostgresClosure *pc = cls; +  PGresult *result; +  PGconn *conn; + +  result = NULL; +  conn = PQconnectdb (pc->connection_cfg_str); +  if (CONNECTION_OK != PQstatus (conn)) +  { +    TALER_LOG_ERROR ("Database connection failed: %s\n", +               PQerrorMessage (conn)); +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  if ( (GNUNET_YES == temporary) && +       (GNUNET_SYSERR == set_temporary_schema (conn))) +  { +    PQfinish (conn); +    return GNUNET_SYSERR; +  } +#define SQLEXEC(sql) SQLEXEC_(conn, sql, result); +  /* reserves table is for summarization of a reserve.  It is updated when new +     funds are added and existing funds are withdrawn */ +  SQLEXEC ("CREATE TABLE IF NOT EXISTS reserves" +           "(" +           " reserve_pub BYTEA PRIMARY KEY" +           ",current_balance_value INT8 NOT NULL" +           ",current_balance_fraction INT4 NOT NULL" +           ",balance_currency VARCHAR(4) NOT NULL" +           ",expiration_date INT8 NOT NULL" +           ")"); +  /* reserves_in table collects the transactions which transfer funds into the +     reserve.  The amount and expiration date for the corresponding reserve are +     updated when new transfer funds are added.  The rows of this table +     correspond to each incoming transaction. */ +  SQLEXEC("CREATE TABLE IF NOT EXISTS reserves_in" +          "(" +          " reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" +          ",balance_value INT8 NOT NULL" +          ",balance_fraction INT4 NOT NULL" +          ",balance_currency VARCHAR(4) NOT NULL" +          ",expiration_date INT8 NOT NULL" +          ");"); +  /* Create an index on the foreign key as it is not created automatically by PSQL */ +  SQLEXEC ("CREATE INDEX reserves_in_reserve_pub_index" +           " ON reserves_in (reserve_pub);"); +  SQLEXEC ("CREATE TABLE IF NOT EXISTS collectable_blindcoins" +           "(" +           "blind_ev BYTEA PRIMARY KEY" +           ",denom_pub BYTEA NOT NULL" /* FIXME: Make this a foreign key? */ +           ",denom_sig BYTEA NOT NULL" +           ",reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" +           ",reserve_sig BYTEA NOT NULL" +           ");"); +  SQLEXEC ("CREATE INDEX collectable_blindcoins_reserve_pub_index ON" +           " collectable_blindcoins (reserve_pub)"); +  SQLEXEC("CREATE TABLE IF NOT EXISTS known_coins " +          "(" +          " coin_pub BYTEA NOT NULL PRIMARY KEY" +          ",denom_pub BYTEA NOT NULL" +          ",denom_sig BYTEA NOT NULL" +          ",expended_value INT8 NOT NULL" +          ",expended_fraction INT4 NOT NULL" +          ",expended_currency VARCHAR(4) NOT NULL" +          ",refresh_session_hash BYTEA" +          ")"); +  SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_sessions " +          "(" +          " session_hash BYTEA PRIMARY KEY CHECK (length(session_hash) = 32)" +          ",session_melt_sig BYTEA" +          ",session_commit_sig BYTEA" +          ",noreveal_index INT2 NOT NULL" +          // non-zero if all reveals were ok +          // and the new coin signatures are ready +          ",reveal_ok BOOLEAN NOT NULL DEFAULT false" +          ") "); +  SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_order " +          "( " +          " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)" +          ",newcoin_index INT2 NOT NULL " +          ",denom_pub BYTEA NOT NULL " +          ",PRIMARY KEY (session_hash, newcoin_index)" +          ") "); +  SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_link" +          "(" +          " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)" +          ",transfer_pub BYTEA NOT NULL" +          ",link_secret_enc BYTEA NOT NULL" +          // index of the old coin in the customer's request +          ",oldcoin_index INT2 NOT NULL" +          // index for cut and choose, +          // ranges from 0 to #TALER_CNC_KAPPA-1 +          ",cnc_index INT2 NOT NULL" +          ")"); +  SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_coin" +          "(" +          " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " +          ",link_vector_enc BYTEA NOT NULL" +          // index of the new coin in the customer's request +          ",newcoin_index INT2 NOT NULL" +          // index for cut and choose, +          ",cnc_index INT2 NOT NULL" +          ",coin_ev BYTEA NOT NULL" +          ")"); +  SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_melt" +          "(" +          " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " +          ",coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) " +          ",denom_pub BYTEA NOT NULL " +          ",oldcoin_index INT2 NOT NULL" +          ")"); +  SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_collectable" +          "(" +          " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " +          ",ev_sig BYTEA NOT NULL" +          ",newcoin_index INT2 NOT NULL" +          ")"); +  SQLEXEC("CREATE TABLE IF NOT EXISTS deposits " +          "( " +          " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)" +          ",denom_pub BYTEA NOT NULL" /* FIXME: Link this as a foreign key? */ +          ",denom_sig BYTEA NOT NULL" +          ",transaction_id INT8 NOT NULL" +          ",amount_currency VARCHAR(4) NOT NULL" +          ",amount_value INT8 NOT NULL" +          ",amount_fraction INT4 NOT NULL" +          ",merchant_pub BYTEA NOT NULL CHECK (length(merchant_pub)=32)" +          ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=64)" +          ",h_wire BYTEA NOT NULL CHECK (length(h_wire)=64)" +          ",coin_sig BYTEA NOT NULL CHECK (length(coin_sig)=64)" +          ",wire TEXT NOT NULL" +          ")"); +#undef SQLEXEC + +  PQfinish (conn); +  return GNUNET_OK; + + SQLEXEC_fail: +  PQfinish (conn); +  return GNUNET_SYSERR; +} + + +/** + * Setup prepared statements. + * + * @param db_conn connection handle to initialize + * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure + */ +static int +postgres_prepare (PGconn *db_conn) +{ +  PGresult *result; + +#define PREPARE(name, sql, ...)                                 \ +  do {                                                          \ +    result = PQprepare (db_conn, name, sql, __VA_ARGS__);       \ +    if (PGRES_COMMAND_OK != PQresultStatus (result))            \ +    {                                                           \ +      BREAK_DB_ERR (result);                                    \ +      PQclear (result); result = NULL;                          \ +      return GNUNET_SYSERR;                                     \ +    }                                                           \ +    PQclear (result); result = NULL;                            \ +  } while (0); + +  PREPARE ("get_reserve", +           "SELECT " +           "current_balance_value" +           ",current_balance_fraction" +           ",balance_currency " +           ",expiration_date " +           "FROM reserves " +           "WHERE reserve_pub=$1 " +           "LIMIT 1; ", +           1, NULL); +  PREPARE ("create_reserve", +           "INSERT INTO reserves (" +           " reserve_pub," +           " current_balance_value," +           " current_balance_fraction," +           " balance_currency," +           " expiration_date) VALUES (" +           "$1, $2, $3, $4, $5);", +           5, NULL); +  PREPARE ("update_reserve", +           "UPDATE reserves " +           "SET" +           " current_balance_value=$2 " +           ",current_balance_fraction=$3 " +           ",expiration_date=$4 " +           "WHERE reserve_pub=$1 ", +           4, NULL); +  PREPARE ("create_reserves_in_transaction", +           "INSERT INTO reserves_in (" +           " reserve_pub," +           " balance_value," +           " balance_fraction," +           " balance_currency," +           " expiration_date) VALUES (" +           " $1, $2, $3, $4, $5);", +           5, NULL); +  PREPARE ("get_reserves_in_transactions", +           "SELECT" +           " balance_value" +           ",balance_fraction" +           ",balance_currency" +           ",expiration_date" +           " FROM reserves_in WHERE reserve_pub=$1", +           1, NULL); +  PREPARE ("insert_collectable_blindcoin", +           "INSERT INTO collectable_blindcoins ( " +           " blind_ev" +           ",denom_pub, denom_sig" +           ",reserve_pub, reserve_sig) " +           "VALUES ($1, $2, $3, $4, $5)", +           5, NULL); +  PREPARE ("get_collectable_blindcoin", +           "SELECT " +           " denom_pub, denom_sig" +           ",reserve_sig, reserve_pub " +           "FROM collectable_blindcoins " +           "WHERE blind_ev = $1", +           1, NULL); +  PREPARE ("get_reserves_blindcoins", +           "select" +           " blind_ev" +           ",denom_pub, denom_sig" +           ",reserve_sig" +           " FROM collectable_blindcoins" +           " WHERE reserve_pub=$1;", +           1, NULL); + +  /* FIXME: does it make sense to store these computed values in the DB? */ +#if 0 +  PREPARE ("get_refresh_session", +           "SELECT " +           " (SELECT count(*) FROM refresh_melt WHERE session_hash = $1)::INT2 as num_oldcoins " +           ",(SELECT count(*) FROM refresh_blind_session_keys " +           "  WHERE session_hash = $1 and cnc_index = 0)::INT2 as num_newcoins " +           ",(SELECT count(*) FROM refresh_blind_session_keys " +           "  WHERE session_hash = $1 and newcoin_index = 0)::INT2 as kappa " +           ",noreveal_index" +           ",session_commit_sig " +           ",reveal_ok " +           "FROM refresh_sessions " +           "WHERE session_hash = $1", +           1, NULL); +#endif + +  PREPARE ("get_known_coin", +           "SELECT " +           " coin_pub, denom_pub, denom_sig " +           ",expended_value, expended_fraction, expended_currency " +           ",refresh_session_hash " +           "FROM known_coins " +           "WHERE coin_pub = $1", +           1, NULL); +  PREPARE ("update_known_coin", +           "UPDATE known_coins " +           "SET " +           " denom_pub = $2 " +           ",denom_sig = $3 " +           ",expended_value = $4 " +           ",expended_fraction = $5 " +           ",expended_currency = $6 " +           ",refresh_session_hash = $7 " +           "WHERE " +           " coin_pub = $1 ", +           7, NULL); +  PREPARE ("insert_known_coin", +           "INSERT INTO known_coins (" +           " coin_pub" +           ",denom_pub" +           ",denom_sig" +           ",expended_value" +           ",expended_fraction" +           ",expended_currency" +           ",refresh_session_hash" +           ")" +           "VALUES ($1,$2,$3,$4,$5,$6,$7)", +           7, NULL); +  PREPARE ("get_refresh_commit_link", +           "SELECT " +           " transfer_pub " +           ",link_secret_enc " +           "FROM refresh_commit_link " +           "WHERE session_hash = $1 AND cnc_index = $2 AND oldcoin_index = $3", +           3, NULL); +  PREPARE ("get_refresh_commit_coin", +           "SELECT " +           " link_vector_enc " +           ",coin_ev " +           "FROM refresh_commit_coin " +           "WHERE session_hash = $1 AND cnc_index = $2 AND newcoin_index = $3", +           3, NULL); +  PREPARE ("insert_refresh_order", +           "INSERT INTO refresh_order ( " +           " newcoin_index " +           ",session_hash " +           ",denom_pub " +           ") " +           "VALUES ($1, $2, $3) ", +           3, NULL); +  PREPARE ("insert_refresh_melt", +           "INSERT INTO refresh_melt ( " +           " session_hash " +           ",oldcoin_index " +           ",coin_pub " +           ",denom_pub " +           ") " +           "VALUES ($1, $2, $3, $4) ", +           3, NULL); +  PREPARE ("get_refresh_order", +           "SELECT denom_pub " +           "FROM refresh_order " +           "WHERE session_hash = $1 AND newcoin_index = $2", +           2, NULL); +  PREPARE ("get_refresh_collectable", +           "SELECT ev_sig " +           "FROM refresh_collectable " +           "WHERE session_hash = $1 AND newcoin_index = $2", +           2, NULL); +  PREPARE ("get_refresh_melt", +           "SELECT coin_pub " +           "FROM refresh_melt " +           "WHERE session_hash = $1 AND oldcoin_index = $2", +           2, NULL); +  PREPARE ("insert_refresh_session", +           "INSERT INTO refresh_sessions ( " +           " session_hash " +           ",noreveal_index " +           ") " +           "VALUES ($1, $2) ", +           2, NULL); +  PREPARE ("insert_refresh_commit_link", +           "INSERT INTO refresh_commit_link ( " +           " session_hash " +           ",transfer_pub " +           ",cnc_index " +           ",oldcoin_index " +           ",link_secret_enc " +           ") " +           "VALUES ($1, $2, $3, $4, $5) ", +           5, NULL); +  PREPARE ("insert_refresh_commit_coin", +           "INSERT INTO refresh_commit_coin ( " +           " session_hash " +           ",coin_ev " +           ",cnc_index " +           ",newcoin_index " +           ",link_vector_enc " +           ") " +           "VALUES ($1, $2, $3, $4, $5) ", +           5, NULL); +  PREPARE ("insert_refresh_collectable", +           "INSERT INTO refresh_collectable ( " +           " session_hash " +           ",newcoin_index " +           ",ev_sig " +           ") " +           "VALUES ($1, $2, $3) ", +           3, NULL); +  PREPARE ("set_reveal_ok", +           "UPDATE refresh_sessions " +           "SET reveal_ok = TRUE " +           "WHERE session_hash = $1 ", +           1, NULL); +  PREPARE ("get_link", +           "SELECT link_vector_enc, ro.denom_pub, ev_sig " +           "FROM refresh_melt rm " +           "     JOIN refresh_order ro USING (session_hash) " +           "     JOIN refresh_commit_coin rcc USING (session_hash) " +           "     JOIN refresh_sessions rs USING (session_hash) " +           "     JOIN refresh_collectable rc USING (session_hash) " +           "WHERE rm.coin_pub = $1 " +           "AND ro.newcoin_index = rcc.newcoin_index " +           "AND ro.newcoin_index = rc.newcoin_index " +           "AND  rcc.cnc_index = rs.noreveal_index % ( " +           "         SELECT count(*) FROM refresh_commit_coin rcc2 " +           "         WHERE rcc2.newcoin_index = 0 AND rcc2.session_hash = rs.session_hash " +           "     ) ", +           1, NULL); +  PREPARE ("get_transfer", +           "SELECT transfer_pub, link_secret_enc " +           "FROM refresh_melt rm " +           "     JOIN refresh_commit_link rcl USING (session_hash) " +           "     JOIN refresh_sessions rs USING (session_hash) " +           "WHERE rm.coin_pub = $1 " +           "AND  rm.oldcoin_index = rcl.oldcoin_index " +           "AND  rcl.cnc_index = rs.noreveal_index % ( " +           "         SELECT count(*) FROM refresh_commit_coin rcc2 " +           "         WHERE newcoin_index = 0 AND rcc2.session_hash = rm.session_hash " +           "     ) ", +           1, NULL); +  PREPARE ("insert_deposit", +           "INSERT INTO deposits (" +           "coin_pub," +           "denom_pub," +           "denom_sig," +           "transaction_id," +           "amount_value," +           "amount_fraction," +           "amount_currency," +           "merchant_pub," +           "h_contract," +           "h_wire," +           "coin_sig," +           "wire" +           ") VALUES (" +           "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12" +           ")", +           12, NULL); +  PREPARE ("get_deposit", +           "SELECT " +           "coin_pub," +           "denom_pub," +           "transaction_id," +           "amount_value," +           "amount_fraction," +           "amount_currency," +           "merchant_pub," +           "h_contract," +           "h_wire," +           "coin_sig" +           " FROM deposits WHERE (" +           "(coin_pub = $1) AND" +           "(transaction_id = $2) AND" +           "(merchant_pub = $3)" +           ")", +           3, NULL); +  return GNUNET_OK; +#undef PREPARE +} + + +/** + * Close thread-local database connection when a thread is destroyed. + * + * @param closure we get from pthreads (the db handle) + */ +static void +db_conn_destroy (void *cls) +{ +  PGconn *db_conn = cls; + +  if (NULL != db_conn) +    PQfinish (db_conn); +} + + +/** + * Get the thread-local database-handle. + * Connect to the db if the connection does not exist yet. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param temporary #GNUNET_YES to use a temporary schema; #GNUNET_NO to use the + *        database default one + * @return the database connection, or NULL on error + */ +static struct TALER_MINTDB_Session * +postgres_get_session (void *cls, +                      int temporary) +{ +  struct PostgresClosure *pc = cls; +  PGconn *db_conn; +  struct TALER_MINTDB_Session *session; + +  if (NULL != (session = pthread_getspecific (pc->db_conn_threadlocal))) +    return session; +  db_conn = PQconnectdb (pc->connection_cfg_str); +  if (CONNECTION_OK != +      PQstatus (db_conn)) +  { +    TALER_LOG_ERROR ("Database connection failed: %s\n", +               PQerrorMessage (db_conn)); +    GNUNET_break (0); +    return NULL; +  } +  if ((GNUNET_YES == temporary) +      && (GNUNET_SYSERR == set_temporary_schema(db_conn))) +  { +    GNUNET_break (0); +    return NULL; +  } +  if (GNUNET_OK != +      postgres_prepare (db_conn)) +  { +    GNUNET_break (0); +    return NULL; +  } +  session = GNUNET_new (struct TALER_MINTDB_Session); +  session->conn = db_conn; +  if (0 != pthread_setspecific (pc->db_conn_threadlocal, +                                session)) +  { +    GNUNET_break (0); +    // FIXME: close db_conn! +    GNUNET_free (session); +    return NULL; +  } +  return session; +} + + +/** + * Start a transaction. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static int +postgres_start (void *cls, +                struct TALER_MINTDB_Session *session) +{ +  PGresult *result; + +  result = PQexec (session->conn, +                   "BEGIN"); +  if (PGRES_COMMAND_OK != +      PQresultStatus (result)) +  { +    TALER_LOG_ERROR ("Failed to start transaction: %s\n", +               PQresultErrorMessage (result)); +    GNUNET_break (0); +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  PQclear (result); +  return GNUNET_OK; +} + + +/** + * Roll back the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static void +postgres_rollback (void *cls, +                   struct TALER_MINTDB_Session *session) +{ +  PGresult *result; + +  result = PQexec (session->conn, +                   "ROLLBACK"); +  GNUNET_break (PGRES_COMMAND_OK == +                PQresultStatus (result)); +  PQclear (result); +} + + +/** + * Commit the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static int +postgres_commit (void *cls, +                 struct TALER_MINTDB_Session *session) +{ +  PGresult *result; + +  result = PQexec (session->conn, +                   "COMMIT"); +  if (PGRES_COMMAND_OK != +      PQresultStatus (result)) +  { +    GNUNET_break (0); +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  PQclear (result); +  return GNUNET_OK; +} + + +/** + * Get the summary of a reserve. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection handle + * @param reserve the reserve data.  The public key of the reserve should be set + *          in this structure; it is used to query the database.  The balance + *          and expiration are then filled accordingly. + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_reserve_get (void *cls, +                      struct TALER_MINTDB_Session *session, +                      struct Reserve *reserve) +{ +  PGresult *result; +  uint64_t expiration_date_nbo; +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR(&reserve->pub), +    TALER_PQ_QUERY_PARAM_END +  }; + +  result = TALER_PQ_exec_prepared (session->conn, +                                   "get_reserve", +                                   params); +  if (PGRES_TUPLES_OK != PQresultStatus (result)) +  { +    QUERY_ERR (result); +    PQclear (result); +    return GNUNET_SYSERR; +  } +  if (0 == PQntuples (result)) +  { +    PQclear (result); +    return GNUNET_NO; +  } +  struct TALER_PQ_ResultSpec rs[] = { +    TALER_PQ_RESULT_SPEC("expiration_date", &expiration_date_nbo), +    TALER_PQ_RESULT_SPEC_END +  }; +  EXITIF (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)); +  EXITIF (GNUNET_OK != +          TALER_PQ_extract_amount (result, 0, +                                   "current_balance_value", +                                   "current_balance_fraction", +                                   "balance_currency", +                                   &reserve->balance)); +  reserve->expiry.abs_value_us = GNUNET_ntohll (expiration_date_nbo); +  PQclear (result); +  return GNUNET_OK; + + EXITIF_exit: +  PQclear (result); +  return GNUNET_SYSERR; +} + + +/** + * Updates a reserve with the data from the given reserve structure. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @param reserve the reserve structure whose data will be used to update the + *          corresponding record in the database. + * @return #GNUNET_OK upon successful update; #GNUNET_SYSERR upon any error + */ +static int +postgres_reserves_update (void *cls, +                          struct TALER_MINTDB_Session *session, +                          struct Reserve *reserve) +{ +  PGresult *result; +  struct TALER_AmountNBO balance_nbo; +  struct GNUNET_TIME_AbsoluteNBO expiry_nbo; +  int ret; + +  if (NULL == reserve) +    return GNUNET_SYSERR; +  ret = GNUNET_OK; +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), +    TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), +    TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), +    TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), +    TALER_PQ_QUERY_PARAM_END +  }; +  TALER_amount_hton (&balance_nbo, +                     &reserve->balance); +  expiry_nbo = GNUNET_TIME_absolute_hton (reserve->expiry); +  result = TALER_PQ_exec_prepared (session->conn, +                                   "update_reserve", +                                   params); +  if (PGRES_COMMAND_OK != PQresultStatus(result)) +  { +    QUERY_ERR (result); +    ret = GNUNET_SYSERR; +  } +  PQclear (result); +  return ret; +} + + +/** + * Insert a incoming transaction into reserves.  New reserves are also created + * through this function. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection handle + * @param reserve the reserve structure.  The public key of the reserve should + *          be set here.  Upon successful execution of this function, the + *          balance and expiration of the reserve will be updated. + * @param balance the amount that has to be added to the reserve + * @param expiry the new expiration time for the reserve + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failures + */ +static int +postgres_reserves_in_insert (void *cls, +                             struct TALER_MINTDB_Session *session, +                             struct Reserve *reserve, +                             const struct TALER_Amount *balance, +                             const struct GNUNET_TIME_Absolute expiry) +{ +  struct TALER_AmountNBO balance_nbo; +  struct GNUNET_TIME_AbsoluteNBO expiry_nbo; +  PGresult *result; +  int reserve_exists; + +  result = NULL; +  if (NULL == reserve) +  { +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  if (GNUNET_OK != postgres_start (cls, +                                   session)) +  { +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  reserve_exists = postgres_reserve_get (cls, +                                         session, +                                         reserve); +  if (GNUNET_SYSERR == reserve_exists) +  { +    postgres_rollback (cls, +                       session); +    return GNUNET_SYSERR; +  } +  TALER_amount_hton (&balance_nbo, +                     balance); +  expiry_nbo = GNUNET_TIME_absolute_hton (expiry); +  if (GNUNET_NO == reserve_exists) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +                "Reserve does not exist; creating a new one\n"); +    struct TALER_PQ_QueryParam params[] = { +      TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), +      TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), +      TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), +      TALER_PQ_QUERY_PARAM_PTR_SIZED (balance_nbo.currency, +                                      TALER_PQ_CURRENCY_LEN), +      TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), +      TALER_PQ_QUERY_PARAM_END +    }; +    result = TALER_PQ_exec_prepared (session->conn, +                                     "create_reserve", +                                     params); +    if (PGRES_COMMAND_OK != PQresultStatus(result)) +    { +      QUERY_ERR (result); +      goto rollback; +    } +  } +  if (NULL != result) +    PQclear (result); +  result = NULL; +  /* create new incoming transaction */ +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), +    TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), +    TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), +    TALER_PQ_QUERY_PARAM_PTR_SIZED (&balance_nbo.currency, +                                    TALER_PQ_CURRENCY_LEN), +    TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), +    TALER_PQ_QUERY_PARAM_END +  }; +  result = TALER_PQ_exec_prepared (session->conn, +                                   "create_reserves_in_transaction", +                                   params); +  if (PGRES_COMMAND_OK != PQresultStatus(result)) +  { +    QUERY_ERR (result); +    goto rollback; +  } +  PQclear (result); +  result = NULL; +  if (GNUNET_NO == reserve_exists) +  { +    if (GNUNET_OK != postgres_commit (cls, +                                      session)) +      return GNUNET_SYSERR; +    reserve->balance = *balance; +    reserve->expiry = expiry; +    return GNUNET_OK; +  } +  /* Update reserve */ +  struct Reserve updated_reserve; +  updated_reserve.pub = reserve->pub; + +  if (GNUNET_OK != +      TALER_amount_add (&updated_reserve.balance, +                        &reserve->balance, +                        balance)) +  { +    return GNUNET_SYSERR; +  } +  updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry, reserve->expiry); +  if (GNUNET_OK != postgres_reserves_update (cls, +                                             session, +                                             &updated_reserve)) +    goto rollback; +  if (GNUNET_OK != postgres_commit (cls, +                                    session)) +    return GNUNET_SYSERR; +  reserve->balance = updated_reserve.balance; +  reserve->expiry = updated_reserve.expiry; +  return GNUNET_OK; + + rollback: +  PQclear (result); +  postgres_rollback (cls, +                     session); +  return GNUNET_SYSERR; +} + + +/** + * Locate the response for a /withdraw request under the + * key of the hash of the blinded message. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param h_blind hash of the blinded message + * @param collectable corresponding collectable coin (blind signature) + *                    if a coin is found + * @return #GNUNET_SYSERR on internal error + *         #GNUNET_NO if the collectable was not found + *         #GNUNET_YES on success + */ +static int +postgres_get_collectable_blindcoin (void *cls, +                                    struct TALER_MINTDB_Session *session, +                                    const struct GNUNET_HashCode *h_blind, +                                    struct CollectableBlindcoin *collectable) +{ +  PGresult *result; +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR (h_blind), +    TALER_PQ_QUERY_PARAM_END +  }; +  struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; +  struct GNUNET_CRYPTO_rsa_Signature *denom_sig; +  char *denom_pub_enc; +  char *denom_sig_enc; +  size_t denom_pub_enc_size; +  size_t denom_sig_enc_size; +  int ret; + +  ret = GNUNET_SYSERR; +  denom_pub = NULL; +  denom_pub_enc = NULL; +  denom_sig_enc = NULL; +  result = TALER_PQ_exec_prepared (session->conn, +                                   "get_collectable_blindcoin", +                                   params); + +  if (PGRES_TUPLES_OK != PQresultStatus (result)) +  { +    QUERY_ERR (result); +    goto cleanup; +  } +  if (0 == PQntuples (result)) +  { +    ret = GNUNET_NO; +    goto cleanup; +  } +  struct TALER_PQ_ResultSpec rs[] = { +    TALER_PQ_RESULT_SPEC_VAR("denom_pub", &denom_pub_enc, &denom_pub_enc_size), +    TALER_PQ_RESULT_SPEC_VAR("denom_sig", &denom_sig_enc, &denom_sig_enc_size), +    TALER_PQ_RESULT_SPEC("reserve_sig", &collectable->reserve_sig), +    TALER_PQ_RESULT_SPEC("reserve_pub", &collectable->reserve_pub), +    TALER_PQ_RESULT_SPEC_END +  }; + +  if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) +  { +    GNUNET_break (0); +    goto cleanup; +  } +  denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, +                                                   denom_pub_enc_size); +  denom_sig = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, +                                                  denom_sig_enc_size); +  if ((NULL == denom_pub) || (NULL == denom_sig)) +  { +    GNUNET_break (0); +    goto cleanup; +  } +  collectable->denom_pub.rsa_public_key = denom_pub; +  collectable->sig.rsa_signature = denom_sig; +  ret = GNUNET_YES; + + cleanup: +  PQclear (result); +  GNUNET_free_non_null (denom_pub_enc); +  GNUNET_free_non_null (denom_sig_enc); +  if (GNUNET_YES != ret) +  { if (NULL != denom_pub) +      GNUNET_CRYPTO_rsa_public_key_free (denom_pub); +    if (NULL != denom_sig) +      GNUNET_CRYPTO_rsa_signature_free (denom_sig); +  } +  return ret; +} + + +/** + * Store collectable bit coin under the corresponding + * hash of the blinded message. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param h_blind hash of the blinded message + * @param withdraw amount by which the reserve will be withdrawn with this + *          transaction + * @param collectable corresponding collectable coin (blind signature) + *                    if a coin is found + * @return #GNUNET_SYSERR on internal error + *         #GNUNET_NO if the collectable was not found + *         #GNUNET_YES on success + */ +static int +postgres_insert_collectable_blindcoin (void *cls, +                                       struct TALER_MINTDB_Session *session, +                                       const struct GNUNET_HashCode *h_blind, +                                       struct TALER_Amount withdraw, +                                       const struct CollectableBlindcoin *collectable) +{ +  PGresult *result; +  struct Reserve reserve; +  char *denom_pub_enc = NULL; +  char *denom_sig_enc = NULL; +  size_t denom_pub_enc_size; +  size_t denom_sig_enc_size; +  int ret; + +  ret = GNUNET_SYSERR; +  denom_pub_enc_size = +      GNUNET_CRYPTO_rsa_public_key_encode (collectable->denom_pub.rsa_public_key, +                                           &denom_pub_enc); +  denom_sig_enc_size = +      GNUNET_CRYPTO_rsa_signature_encode (collectable->sig.rsa_signature, +                                          &denom_sig_enc); +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR (h_blind), +    TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size - 1), +    TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size - 1), /* DB doesn't like the trailing \0 */ +    TALER_PQ_QUERY_PARAM_PTR (&collectable->reserve_pub), +    TALER_PQ_QUERY_PARAM_PTR (&collectable->reserve_sig), +    TALER_PQ_QUERY_PARAM_END +  }; +  if (GNUNET_OK != postgres_start (cls, +                                   session)) +    goto cleanup; +  result = TALER_PQ_exec_prepared (session->conn, +                                   "insert_collectable_blindcoin", +                                   params); +  if (PGRES_COMMAND_OK != PQresultStatus (result)) +  { +    QUERY_ERR (result); +    goto rollback; +  } +  reserve.pub = collectable->reserve_pub; +  if (GNUNET_OK != postgres_reserve_get (cls, +                                         session, +                                         &reserve)) +    goto rollback; +  if (GNUNET_SYSERR == +      TALER_amount_subtract (&reserve.balance, +                             &reserve.balance, +                             &withdraw)) +    goto rollback; +  if (GNUNET_OK != postgres_reserves_update (cls, +                                             session, +                                             &reserve)) +    goto rollback; +  if (GNUNET_OK == postgres_commit (cls, +                                    session)) +  { +    ret = GNUNET_OK; +    goto cleanup; +  } + + rollback: +  postgres_rollback (cls, +                     session); + cleanup: +  PQclear (result); +  GNUNET_free_non_null (denom_pub_enc); +  GNUNET_free_non_null (denom_sig_enc); +  return ret; +} + + +/** + * Get all of the transaction history associated with the specified + * reserve. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session connection to use + * @param reserve_pub public key of the reserve + * @return known transaction history (NULL if reserve is unknown) + */ +static struct ReserveHistory * +postgres_get_reserve_history (void *cls, +                              struct TALER_MINTDB_Session *session, +                              const struct TALER_ReservePublicKeyP *reserve_pub) +{ +  PGresult *result; +  struct ReserveHistory *rh; +  struct ReserveHistory *rh_head; +  int rows; +  int ret; + +  result = NULL; +  rh = NULL; +  rh_head = NULL; +  ret = GNUNET_SYSERR; +  { +    struct BankTransfer *bt; +    struct TALER_PQ_QueryParam params[] = { +      TALER_PQ_QUERY_PARAM_PTR (reserve_pub), +      TALER_PQ_QUERY_PARAM_END +    }; + +    result = TALER_PQ_exec_prepared (session->conn, +                                     "get_reserves_in_transactions", +                                     params); +    if (PGRES_TUPLES_OK != PQresultStatus (result)) +    { +      QUERY_ERR (result); +      goto cleanup; +    } +    if (0 == (rows = PQntuples (result))) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +                  "Asked to fetch history for an unknown reserve.\n"); +      goto cleanup; +    } +    while (0 < rows) +    { +      bt = GNUNET_new (struct BankTransfer); +      if (GNUNET_OK != TALER_PQ_extract_amount (result, +                                                --rows, +                                                "balance_value", +                                                "balance_fraction", +                                                "balance_currency", +                                                &bt->amount)) +      { +        GNUNET_free (bt); +        GNUNET_break (0); +        goto cleanup; +      } +      bt->reserve_pub = *reserve_pub; +      if (NULL != rh_head) +      { +        rh_head->next = GNUNET_new (struct ReserveHistory); +        rh_head = rh_head->next; +      } +      else +      { +        rh_head = GNUNET_new (struct ReserveHistory); +        rh = rh_head; +      } +      rh_head->type = TALER_MINT_DB_RO_BANK_TO_MINT; +      rh_head->details.bank = bt; +    } +  } +  PQclear (result); +  result = NULL; +  { +    struct GNUNET_HashCode blind_ev; +    struct TALER_ReserveSignatureP reserve_sig; +    struct CollectableBlindcoin *cbc; +    char *denom_pub_enc; +    char *denom_sig_enc; +    size_t denom_pub_enc_size; +    size_t denom_sig_enc_size; + +    struct TALER_PQ_QueryParam params[] = { +      TALER_PQ_QUERY_PARAM_PTR (reserve_pub), +      TALER_PQ_QUERY_PARAM_END +    }; +    result = TALER_PQ_exec_prepared (session->conn, +                                     "get_reserves_blindcoins", +                                     params); +    if (PGRES_TUPLES_OK != PQresultStatus (result)) +    { +      QUERY_ERR (result); +      goto cleanup; +    } +    if (0 == (rows = PQntuples (result))) +    { +      ret = GNUNET_OK;          /* Its OK if there are no withdrawls yet */ +      goto cleanup; +    } +    struct TALER_PQ_ResultSpec rs[] = { +      TALER_PQ_RESULT_SPEC ("blind_ev", &blind_ev), +      TALER_PQ_RESULT_SPEC_VAR ("denom_pub", &denom_pub_enc, &denom_pub_enc_size), +      TALER_PQ_RESULT_SPEC_VAR ("denom_sig", &denom_sig_enc, &denom_sig_enc_size), +      TALER_PQ_RESULT_SPEC ("reserve_sig", &reserve_sig), +      TALER_PQ_RESULT_SPEC_END +    }; +    GNUNET_assert (NULL != rh); +    GNUNET_assert (NULL != rh_head); +    GNUNET_assert (NULL == rh_head->next); +    while (0 < rows) +    { +      if (GNUNET_YES != TALER_PQ_extract_result (result, rs, --rows)) +      { +        GNUNET_break (0); +        goto cleanup; +      } +      cbc = GNUNET_new (struct CollectableBlindcoin); +      cbc->sig.rsa_signature +        = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, +                                              denom_sig_enc_size); +      GNUNET_free (denom_sig_enc); +      denom_sig_enc = NULL; +      cbc->denom_pub.rsa_public_key +        = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, +                                               denom_pub_enc_size); +      GNUNET_free (denom_pub_enc); +      denom_pub_enc = NULL; +      if ( (NULL == cbc->sig.rsa_signature) || +           (NULL == cbc->denom_pub.rsa_public_key) ) +      { +        if (NULL != cbc->sig.rsa_signature) +          GNUNET_CRYPTO_rsa_signature_free (cbc->sig.rsa_signature); +        if (NULL != cbc->denom_pub.rsa_public_key) +          GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub.rsa_public_key); +        GNUNET_free (cbc); +        GNUNET_break (0); +        goto cleanup; +      } +      (void) memcpy (&cbc->h_coin_envelope, &blind_ev, sizeof (blind_ev)); +      (void) memcpy (&cbc->reserve_pub, reserve_pub, sizeof (cbc->reserve_pub)); +      (void) memcpy (&cbc->reserve_sig, &reserve_sig, sizeof (cbc->reserve_sig)); +      rh_head->next = GNUNET_new (struct ReserveHistory); +      rh_head = rh_head->next; +      rh_head->type = TALER_MINT_DB_RO_WITHDRAW_COIN; +      rh_head->details.withdraw = cbc; +    } +  } +  ret = GNUNET_OK; + + cleanup: +  if (NULL != result) +    PQclear (result); +  if (GNUNET_SYSERR == ret) +  { +    common_free_reserve_history (cls, +                                 rh); +    rh = NULL; +  } +  return rh; +} + + +/** + * Check if we have the specified deposit already in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param deposit deposit to search for + * @return #GNUNET_YES if we know this operation, + *         #GNUNET_NO if this deposit is unknown to us + */ +static int +postgres_have_deposit (void *cls, +                       struct TALER_MINTDB_Session *session, +                       const struct Deposit *deposit) +{ +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR (&deposit->coin.coin_pub), +    TALER_PQ_QUERY_PARAM_PTR (&deposit->transaction_id), +    TALER_PQ_QUERY_PARAM_PTR (&deposit->merchant_pub), +    TALER_PQ_QUERY_PARAM_END +  }; +  PGresult *result; +  int ret; + +  ret = GNUNET_SYSERR; +  result = TALER_PQ_exec_prepared (session->conn, +                                   "get_deposit", +                                   params); +  if (PGRES_TUPLES_OK != +      PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    goto cleanup; +  } + +  if (0 == PQntuples (result)) +  { +    ret = GNUNET_NO; +    goto cleanup; +  } +  ret = GNUNET_YES; + + cleanup: +  PQclear (result); +  return ret; +} + + +/** + * Insert information about deposited coin into the + * database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session connection to the database + * @param deposit deposit information to store + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +postgres_insert_deposit (void *cls, +                         struct TALER_MINTDB_Session *session, +                         const struct Deposit *deposit) +{ +  char *denom_pub_enc; +  char *denom_sig_enc; +  char *json_wire_enc; +  PGresult *result; +  struct TALER_AmountNBO amount_nbo; +  size_t denom_pub_enc_size; +  size_t denom_sig_enc_size; +  int ret; + +  ret = GNUNET_SYSERR; +  denom_pub_enc_size = +      GNUNET_CRYPTO_rsa_public_key_encode (deposit->coin.denom_pub.rsa_public_key, +                                           &denom_pub_enc); +  denom_sig_enc_size = +      GNUNET_CRYPTO_rsa_signature_encode (deposit->coin.denom_sig.rsa_signature, +                                          &denom_sig_enc); +  json_wire_enc = json_dumps (deposit->wire, JSON_COMPACT); +  TALER_amount_hton (&amount_nbo, +                     &deposit->amount_with_fee); +  struct TALER_PQ_QueryParam params[]= { +    TALER_PQ_QUERY_PARAM_PTR (&deposit->coin.coin_pub), +    TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size), +    TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size), +    TALER_PQ_QUERY_PARAM_PTR (&deposit->transaction_id), +    TALER_PQ_QUERY_PARAM_PTR (&amount_nbo.value), +    TALER_PQ_QUERY_PARAM_PTR (&amount_nbo.fraction), +    TALER_PQ_QUERY_PARAM_PTR_SIZED (amount_nbo.currency, +                                    TALER_CURRENCY_LEN - 1), +    TALER_PQ_QUERY_PARAM_PTR (&deposit->merchant_pub), +    TALER_PQ_QUERY_PARAM_PTR (&deposit->h_contract), +    TALER_PQ_QUERY_PARAM_PTR (&deposit->h_wire), +    TALER_PQ_QUERY_PARAM_PTR (&deposit->csig), +    TALER_PQ_QUERY_PARAM_PTR_SIZED (json_wire_enc, +                                    strlen (json_wire_enc)), +    TALER_PQ_QUERY_PARAM_END +  }; +  result = TALER_PQ_exec_prepared (session->conn, "insert_deposit", params); +  if (PGRES_COMMAND_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    goto cleanup; +  } +  ret = GNUNET_OK; + + cleanup: +  PQclear (result); +  GNUNET_free_non_null (denom_pub_enc); +  GNUNET_free_non_null (denom_sig_enc); +  GNUNET_free_non_null (json_wire_enc); +  return ret; +} + + +/** + * Lookup refresh session data under the given @a session_hash. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database handle to use + * @param session_hash hash over the melt to use to locate the session + * @param refresh_session[OUT] where to store the result + * @return #GNUNET_YES on success, + *         #GNUNET_NO if not found, + *         #GNUNET_SYSERR on DB failure + */ +static int +postgres_get_refresh_session (void *cls, +                              struct TALER_MINTDB_Session *session, +                              const struct GNUNET_HashCode *session_hash, +                              struct RefreshSession *refresh_session) +{ +  // FIXME: check logic! +  int res; +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR(session_hash), +    TALER_PQ_QUERY_PARAM_END +  }; + +  PGresult *result = TALER_PQ_exec_prepared (session->conn, +                                             "get_refresh_session", +                                             params); + +  if (PGRES_TUPLES_OK != PQresultStatus (result)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Query failed: %s\n", +                PQresultErrorMessage (result)); +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  if (0 == PQntuples (result)) +    return GNUNET_NO; + +  GNUNET_assert (1 == PQntuples (result)); + +  /* We're done if the caller is only interested in +   * whether the session exists or not */ + +  if (NULL == refresh_session) +    return GNUNET_YES; + +  memset (session, 0, sizeof (struct RefreshSession)); + +  struct TALER_PQ_ResultSpec rs[] = { +    TALER_PQ_RESULT_SPEC("num_oldcoins", &refresh_session->num_oldcoins), +    TALER_PQ_RESULT_SPEC("num_newcoins", &refresh_session->num_newcoins), +    TALER_PQ_RESULT_SPEC("noreveal_index", &refresh_session->noreveal_index), +    TALER_PQ_RESULT_SPEC_END +  }; + +  res = TALER_PQ_extract_result (result, rs, 0); + +  if (GNUNET_OK != res) +  { +    GNUNET_break (0); +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  refresh_session->num_oldcoins = ntohs (refresh_session->num_oldcoins); +  refresh_session->num_newcoins = ntohs (refresh_session->num_newcoins); +  refresh_session->noreveal_index = ntohs (refresh_session->noreveal_index); + +  PQclear (result); +  return GNUNET_YES; +} + + +/** + * Store new refresh session data under the given @a session_hash. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database handle to use + * @param session_hash hash over the melt to use to locate the session + * @param refresh_session session data to store + * @return #GNUNET_YES on success, + *         #GNUNET_SYSERR on DB failure + */ +static int +postgres_create_refresh_session (void *cls, +                                 struct TALER_MINTDB_Session *session, +                                 const struct GNUNET_HashCode *session_hash, +                                 const struct RefreshSession *refresh_session) +{ +  // FIXME: actually store session data! +  uint16_t noreveal_index; +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR(session_hash), +    TALER_PQ_QUERY_PARAM_PTR(&noreveal_index), +    TALER_PQ_QUERY_PARAM_END +  }; + +  noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 1<<15); +  noreveal_index = htonl (noreveal_index); + +  PGresult *result = TALER_PQ_exec_prepared (session->conn, +                                             "insert_refresh_session", +                                             params); + +  if (PGRES_COMMAND_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  PQclear (result); +  return GNUNET_OK; +} + + +/** + * Store the given /refresh/melt request in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param oldcoin_index index of the coin to store + * @param melt melt operation details to store; includes +   *             the session hash of the melt + * @return #GNUNET_OK on success + *         #GNUNET_SYSERR on internal error + */ +static int +postgres_insert_refresh_melt (void *cls, +                              struct TALER_MINTDB_Session *session, +                              uint16_t oldcoin_index, +                              const struct RefreshMelt *melt) +{ +  // FIXME: check logic! +  uint16_t oldcoin_index_nbo = htons (oldcoin_index); +  char *buf; +  size_t buf_size; +  PGresult *result; + +  buf_size = GNUNET_CRYPTO_rsa_public_key_encode (melt->coin.denom_pub.rsa_public_key, +                                                  &buf); +  { +    struct TALER_PQ_QueryParam params[] = { +      TALER_PQ_QUERY_PARAM_PTR(&melt->session_hash), +      TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), +      TALER_PQ_QUERY_PARAM_PTR(&melt->coin.coin_pub), +      TALER_PQ_QUERY_PARAM_PTR_SIZED(buf, buf_size), +      TALER_PQ_QUERY_PARAM_END +    }; +    result = TALER_PQ_exec_prepared (session->conn, +                                     "insert_refresh_melt", +                                     params); +  } +  GNUNET_free (buf); +  if (PGRES_COMMAND_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    PQclear (result); +    return GNUNET_SYSERR; +  } +  PQclear (result); +  return GNUNET_OK; +} + + +/** + * Get information about melted coin details from the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param refresh_session session key of the melt operation + * @param oldcoin_index index of the coin to retrieve + * @param melt melt data to fill in + * @return #GNUNET_OK on success + *         #GNUNET_SYSERR on internal error + */ +static int +postgres_get_refresh_melt (void *cls, +                           struct TALER_MINTDB_Session *session, +                           const struct GNUNET_HashCode *session_hash, +                           uint16_t oldcoin_index, +                           struct RefreshMelt *melt) +{ +  // FIXME: check logic! +  GNUNET_break (0); +  return GNUNET_SYSERR; +} + + +/** + * Store in the database which coin(s) we want to create + * in a given refresh operation. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_hash hash to identify refresh session + * @param num_newcoins number of coins to generate, size of the @a denom_pubs array + * @param denom_pubs array denominations of the coins to create + * @return #GNUNET_OK on success + *         #GNUNET_SYSERR on internal error + */ +static int +postgres_insert_refresh_order (void *cls, +                               struct TALER_MINTDB_Session *session, +                               const struct GNUNET_HashCode *session_hash, +                               uint16_t num_newcoins, +                               const struct TALER_DenominationPublicKey *denom_pubs) +{ +  // FIXME: check logic: was written for just one COIN! +  uint16_t newcoin_index_nbo = htons (num_newcoins); +  char *buf; +  size_t buf_size; +  PGresult *result; + +  buf_size = GNUNET_CRYPTO_rsa_public_key_encode (denom_pubs->rsa_public_key, +                                                  &buf); + +  { +    struct TALER_PQ_QueryParam params[] = { +      TALER_PQ_QUERY_PARAM_PTR (&newcoin_index_nbo), +      TALER_PQ_QUERY_PARAM_PTR (session_hash), +      TALER_PQ_QUERY_PARAM_PTR_SIZED (buf, buf_size), +      TALER_PQ_QUERY_PARAM_END +    }; +    result = TALER_PQ_exec_prepared (session->conn, +                                     "insert_refresh_order", +                                     params); +  } +  GNUNET_free (buf); +  if (PGRES_COMMAND_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    PQclear (result); +    return GNUNET_SYSERR; +  } +  if (0 != strcmp ("1", PQcmdTuples (result))) +  { +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  PQclear (result); +  return GNUNET_OK; +} + + +/** + * Lookup in the database the coins that we want to + * create in the given refresh operation. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_hash hash to identify refresh session + * @param newcoin_index array of the @a denom_pubs array + * @param denom_pubs where to store the deomination keys + * @return #GNUNET_OK on success + *         #GNUNET_SYSERR on internal error + */ +static int +postgres_get_refresh_order (void *cls, +                            struct TALER_MINTDB_Session *session, +                            const struct GNUNET_HashCode *session_hash, +                            uint16_t num_newcoins, +                            struct TALER_DenominationPublicKey *denom_pubs) +{ +  // FIXME: check logic -- was written for just one coin! +  char *buf; +  size_t buf_size; +  uint16_t newcoin_index_nbo = htons (num_newcoins); + +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR(session_hash), +    TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), +    TALER_PQ_QUERY_PARAM_END +  }; + +  PGresult *result = TALER_PQ_exec_prepared (session->conn, +                                             "get_refresh_order", params); + +  if (PGRES_TUPLES_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  if (0 == PQntuples (result)) +  { +    PQclear (result); +    /* FIXME: may want to distinguish between different error cases! */ +    return GNUNET_SYSERR; +  } +  GNUNET_assert (1 == PQntuples (result)); +  struct TALER_PQ_ResultSpec rs[] = { +    TALER_PQ_RESULT_SPEC_VAR ("denom_pub", &buf, &buf_size), +    TALER_PQ_RESULT_SPEC_END +  }; +  if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) +  { +    PQclear (result); +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  PQclear (result); +  denom_pubs->rsa_public_key +    = GNUNET_CRYPTO_rsa_public_key_decode (buf, +                                           buf_size); +  GNUNET_free (buf); +  return GNUNET_OK; +} + + + +/** + * Store information about the commitment of the + * given coin for the given refresh session in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param num_newcoins coin index size of the @a commit_coins array + * @param commit_coins array of coin commitments to store + * @return #GNUNET_OK on success + *         #GNUNET_SYSERR on error + */ +static int +postgres_insert_refresh_commit_coins (void *cls, +                                      struct TALER_MINTDB_Session *session, +                                      const struct GNUNET_HashCode *session_hash, +                                      unsigned int i, +                                      unsigned int num_newcoins, +                                      const struct RefreshCommitCoin *commit_coins) +{ +  // FIXME: check logic! -- was written for single commit_coin! +  uint16_t cnc_index_nbo = htons (i); +  uint16_t newcoin_index_nbo = htons (num_newcoins); +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR(session_hash), +    TALER_PQ_QUERY_PARAM_PTR_SIZED(commit_coins->coin_ev, commit_coins->coin_ev_size), +    TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), +    TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), +    TALER_PQ_QUERY_PARAM_PTR_SIZED (commit_coins->refresh_link->coin_priv_enc, +                                    commit_coins->refresh_link->blinding_key_enc_size + +                                    sizeof (union TALER_CoinSpendPrivateKeyP)), +    TALER_PQ_QUERY_PARAM_END +  }; + +  PGresult *result = TALER_PQ_exec_prepared (session->conn, +                                             "insert_refresh_commit_coin", +                                             params); + +  if (PGRES_COMMAND_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  if (0 != strcmp ("1", PQcmdTuples (result))) +  { +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } + +  PQclear (result); +  return GNUNET_OK; +} + + +/** + * Obtain information about the commitment of the + * given coin of the given refresh session from the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to refreshed (new) coins + * @param commit_coin[OUT] coin commitment to return + * @return #GNUNET_OK on success + *         #GNUNET_NO if not found + *         #GNUNET_SYSERR on error + */ +static int +postgres_get_refresh_commit_coins (void *cls, +                                   struct TALER_MINTDB_Session *session, +                                   const struct GNUNET_HashCode *session_hash, +                                   unsigned int cnc_index, +                                   unsigned int newcoin_index, +                                   struct RefreshCommitCoin *cc) +{ +  // FIXME: check logic! +  uint16_t cnc_index_nbo = htons (cnc_index); +  uint16_t newcoin_index_nbo = htons (newcoin_index); +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR(session_hash), +    TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), +    TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), +    TALER_PQ_QUERY_PARAM_END +  }; +  char *c_buf; +  size_t c_buf_size; +  char *rl_buf; +  size_t rl_buf_size; +  struct TALER_RefreshLinkEncrypted *rl; + +  PGresult *result = TALER_PQ_exec_prepared (session->conn, +                                             "get_refresh_commit_coin", +                                             params); + +  if (PGRES_TUPLES_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  if (0 == PQntuples (result)) +  { +    PQclear (result); +    return GNUNET_NO; +  } + +  struct TALER_PQ_ResultSpec rs[] = { +    TALER_PQ_RESULT_SPEC_VAR("coin_ev", &c_buf, &c_buf_size), +    TALER_PQ_RESULT_SPEC_VAR("link_vector_enc", &rl_buf, &rl_buf_size), +    TALER_PQ_RESULT_SPEC_END +  }; +  if (GNUNET_YES != TALER_PQ_extract_result (result, rs, 0)) +  { +    PQclear (result); +    return GNUNET_SYSERR; +  } +  PQclear (result); +  if (rl_buf_size < sizeof (union TALER_CoinSpendPrivateKeyP)) +  { +    GNUNET_free (c_buf); +    GNUNET_free (rl_buf); +    return GNUNET_SYSERR; +  } +  rl = TALER_refresh_link_encrypted_decode (rl_buf, +                                            rl_buf_size); +  GNUNET_free (rl_buf); +  cc->refresh_link = rl; +  cc->coin_ev = c_buf; +  cc->coin_ev_size = c_buf_size; +  return GNUNET_YES; +} + + +/** + * Store the commitment to the given (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to melted (old) coins + * @param commit_link link information to store + * @return #GNUNET_SYSERR on internal error, #GNUNET_OK on success + */ +static int +postgres_insert_refresh_commit_links (void *cls, +                                      struct TALER_MINTDB_Session *session, +                                      const struct GNUNET_HashCode *session_hash, +                                      unsigned int i, +                                      unsigned int j, +                                      const struct RefreshCommitLink *commit_link) +{ +  // FIXME: check logic! +  uint16_t cnc_index_nbo = htons (i); +  uint16_t oldcoin_index_nbo = htons (j); +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR(session_hash), +    TALER_PQ_QUERY_PARAM_PTR(&commit_link->transfer_pub), +    TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), +    TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), +    TALER_PQ_QUERY_PARAM_PTR(&commit_link->shared_secret_enc), +    TALER_PQ_QUERY_PARAM_END +  }; + +  PGresult *result = TALER_PQ_exec_prepared (session->conn, +                                             "insert_refresh_commit_link", +                                             params); +  if (PGRES_COMMAND_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  if (0 != strcmp ("1", PQcmdTuples (result))) +  { +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } + +  PQclear (result); +  return GNUNET_OK; +} + + +/** + * Obtain the commited (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param num_links size of the @a commit_link array + * @param links[OUT] array of link information to return + * @return #GNUNET_SYSERR on internal error, + *         #GNUNET_NO if commitment was not found + *         #GNUNET_OK on success + */ +static int +postgres_get_refresh_commit_links (void *cls, +                                   struct TALER_MINTDB_Session *session, +                                   const struct GNUNET_HashCode *session_hash, +                                   unsigned int i, +                                   unsigned int num_links, +                                   struct RefreshCommitLink *links) +{ +  // FIXME: check logic: was written for a single link! +  uint16_t cnc_index_nbo = htons (i); +  uint16_t oldcoin_index_nbo = htons (num_links); + +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR(session_hash), +    TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), +    TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), +    TALER_PQ_QUERY_PARAM_END +  }; + +  PGresult *result = TALER_PQ_exec_prepared (session->conn, +                                             "get_refresh_commit_link", +                                             params); +  if (PGRES_TUPLES_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  if (0 == PQntuples (result)) +  { +    PQclear (result); +    return GNUNET_NO; +  } + +  struct TALER_PQ_ResultSpec rs[] = { +    TALER_PQ_RESULT_SPEC("transfer_pub", &links->transfer_pub), +    TALER_PQ_RESULT_SPEC("link_secret_enc", &links->shared_secret_enc), +    TALER_PQ_RESULT_SPEC_END +  }; + +  if (GNUNET_YES != TALER_PQ_extract_result (result, rs, 0)) +  { +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  PQclear (result); +  return GNUNET_OK; +} + + +/** + * Insert signature of a new coin generated during refresh into + * the database indexed by the refresh session and the index + * of the coin.  This data is later used should an old coin + * be used to try to obtain the private keys during "/refresh/link". + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_hash hash to identify refresh session + * @param newcoin_index coin index + * @param ev_sig coin signature + * @return #GNUNET_OK on success + */ +static int +postgres_insert_refresh_collectable (void *cls, +                                     struct TALER_MINTDB_Session *session, +                                     const struct GNUNET_HashCode *session_hash, +                                     uint16_t newcoin_index, +                                     const struct TALER_DenominationSignature *ev_sig) +{ +  // FIXME: check logic! +  uint16_t newcoin_index_nbo = htons (newcoin_index); +  char *buf; +  size_t buf_size; +  PGresult *result; + +  buf_size = GNUNET_CRYPTO_rsa_signature_encode (ev_sig->rsa_signature, +                                                 &buf); +  { +    struct TALER_PQ_QueryParam params[] = { +      TALER_PQ_QUERY_PARAM_PTR(session_hash), +      TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), +      TALER_PQ_QUERY_PARAM_PTR_SIZED(buf, buf_size), +      TALER_PQ_QUERY_PARAM_END +    }; +    result = TALER_PQ_exec_prepared (session->conn, +                                     "insert_refresh_collectable", +                                     params); +  } +  GNUNET_free (buf); +  if (PGRES_COMMAND_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    PQclear (result); +    return GNUNET_SYSERR; +  } +  PQclear (result); +  return GNUNET_OK; +} + + +/** + * Obtain the link data of a coin, that is the encrypted link + * information, the denomination keys and the signatures. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub public key to use to retrieve linkage data + * @return all known link data for the coin + */ +static struct LinkDataList * +postgres_get_link_data_list (void *cls, +                             struct TALER_MINTDB_Session *session, +                             const union TALER_CoinSpendPublicKeyP *coin_pub) +{ +  // FIXME: check logic! +  struct LinkDataList *ldl; +  struct LinkDataList *pos; +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR(coin_pub), +    TALER_PQ_QUERY_PARAM_END +  }; +  PGresult *result = TALER_PQ_exec_prepared (session->conn, "get_link", params); + +  ldl = NULL; +  if (PGRES_TUPLES_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    PQclear (result); +    return NULL; +  } + +  if (0 == PQntuples (result)) +  { +    PQclear (result); +    return NULL; +  } + + +  int i = 0; + +  for (i = 0; i < PQntuples (result); i++) +  { +    struct TALER_RefreshLinkEncrypted *link_enc; +    struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; +    struct GNUNET_CRYPTO_rsa_Signature *sig; +    char *ld_buf; +    size_t ld_buf_size; +    char *pk_buf; +    size_t pk_buf_size; +    char *sig_buf; +    size_t sig_buf_size; +    struct TALER_PQ_ResultSpec rs[] = { +      TALER_PQ_RESULT_SPEC_VAR("link_vector_enc", &ld_buf, &ld_buf_size), +      TALER_PQ_RESULT_SPEC_VAR("denom_pub", &pk_buf, &pk_buf_size), +      TALER_PQ_RESULT_SPEC_VAR("ev_sig", &sig_buf, &sig_buf_size), +      TALER_PQ_RESULT_SPEC_END +    }; + +    if (GNUNET_OK != TALER_PQ_extract_result (result, rs, i)) +    { +      PQclear (result); +      GNUNET_break (0); +      common_free_link_data_list (cls, +                                  ldl); +      return NULL; +    } +    if (ld_buf_size < sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)) +    { +      PQclear (result); +      GNUNET_free (pk_buf); +      GNUNET_free (sig_buf); +      GNUNET_free (ld_buf); +      common_free_link_data_list (cls, +                                  ldl); +      return NULL; +    } +    // FIXME: use util API for this! +    link_enc = GNUNET_malloc (sizeof (struct TALER_RefreshLinkEncrypted) + +                              ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)); +    link_enc->blinding_key_enc = (const char *) &link_enc[1]; +    link_enc->blinding_key_enc_size = ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey); +    memcpy (link_enc->coin_priv_enc, +            ld_buf, +            ld_buf_size); + +    sig +      = GNUNET_CRYPTO_rsa_signature_decode (sig_buf, +                                            sig_buf_size); +    denom_pub +      = GNUNET_CRYPTO_rsa_public_key_decode (pk_buf, +                                             pk_buf_size); +    GNUNET_free (pk_buf); +    GNUNET_free (sig_buf); +    GNUNET_free (ld_buf); +    if ( (NULL == sig) || +         (NULL == denom_pub) ) +    { +      if (NULL != denom_pub) +        GNUNET_CRYPTO_rsa_public_key_free (denom_pub); +      if (NULL != sig) +        GNUNET_CRYPTO_rsa_signature_free (sig); +      GNUNET_free (link_enc); +      GNUNET_break (0); +      PQclear (result); +      common_free_link_data_list (cls, +                                  ldl); +      return NULL; +    } +    pos = GNUNET_new (struct LinkDataList); +    pos->next = ldl; +    pos->link_data_enc = link_enc; +    pos->denom_pub.rsa_public_key = denom_pub; +    pos->ev_sig.rsa_signature = sig; +    ldl = pos; +  } +  return ldl; +} + + +/** + * Obtain shared secret and transfer public key from the public key of + * the coin.  This information and the link information returned by + * #postgres_get_link_data_list() enable the owner of an old coin to + * determine the private keys of the new coins after the melt. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub public key of the coin + * @param transfer_pub[OUT] public transfer key + * @param shared_secret_enc[OUT] set to shared secret + * @return #GNUNET_OK on success, + *         #GNUNET_NO on failure (not found) + *         #GNUNET_SYSERR on internal failure (database issue) + */ +static int +postgres_get_transfer (void *cls, +                       struct TALER_MINTDB_Session *session, +                       const union TALER_CoinSpendPublicKeyP *coin_pub, +                       struct TALER_TransferPublicKeyP *transfer_pub, +                       struct TALER_EncryptedLinkSecretP *shared_secret_enc) +{ +  // FIXME: check logic! +  struct TALER_PQ_QueryParam params[] = { +    TALER_PQ_QUERY_PARAM_PTR(coin_pub), +    TALER_PQ_QUERY_PARAM_END +  }; + +  PGresult *result = TALER_PQ_exec_prepared (session->conn, "get_transfer", params); + +  if (PGRES_TUPLES_OK != PQresultStatus (result)) +  { +    BREAK_DB_ERR (result); +    PQclear (result); +    return GNUNET_SYSERR; +  } + +  if (0 == PQntuples (result)) +  { +    PQclear (result); +    return GNUNET_NO; +  } + +  if (1 != PQntuples (result)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "got %d tuples for get_transfer\n", +                PQntuples (result)); +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } + +  struct TALER_PQ_ResultSpec rs[] = { +    TALER_PQ_RESULT_SPEC("transfer_pub", transfer_pub), +    TALER_PQ_RESULT_SPEC("link_secret_enc", shared_secret_enc), +    TALER_PQ_RESULT_SPEC_END +  }; + +  if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) +  { +    PQclear (result); +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } + +  PQclear (result); +  return GNUNET_OK; +} + + +/** + * Compile a list of all (historic) transactions performed + * with the given coin (/refresh/melt and /deposit operations). + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub coin to investigate + * @return list of transactions, NULL if coin is fresh + */ +static struct TALER_MINT_DB_TransactionList * +postgres_get_coin_transactions (void *cls, +                                struct TALER_MINTDB_Session *session, +                                const union TALER_CoinSpendPublicKeyP *coin_pub) +{ +  // FIXME: check logic! +  GNUNET_break (0); // FIXME: implement! +  return NULL; +} + + + +/** + * Initialize Postgres database subsystem. + * + * @param cls a configuration instance + * @return NULL on error, otherwise a `struct TALER_MINTDB_Plugin` + */ +void * +libtaler_plugin_mintdb_postgres_init (void *cls) +{ +  struct GNUNET_CONFIGURATION_Handle *cfg = cls; +  struct PostgresClosure *pg; +  struct TALER_MINTDB_Plugin *plugin; + +  pg = GNUNET_new (struct PostgresClosure); + +  if (0 != pthread_key_create (&pg->db_conn_threadlocal, +                               &db_conn_destroy)) +  { +    TALER_LOG_ERROR ("Cannnot create pthread key.\n"); +    return NULL; +  } +  /* FIXME: use configuration section with "postgres" in its name... */ +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "mint", "db_conn_str", +                                             &pg->connection_cfg_str)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "mint", +                               "db_conn_str"); +    return NULL; +  } +  plugin = GNUNET_new (struct TALER_MINTDB_Plugin); +  plugin->cls = pg; +  plugin->get_session = &postgres_get_session; +  plugin->drop_temporary = &postgres_drop_temporary; +  plugin->create_tables = &postgres_create_tables; +  plugin->start = &postgres_start; +  plugin->commit = &postgres_commit; +  plugin->rollback = &postgres_rollback; +  plugin->reserve_get = &postgres_reserve_get; +  plugin->reserves_in_insert = &postgres_reserves_in_insert; +  plugin->get_collectable_blindcoin = &postgres_get_collectable_blindcoin; +  plugin->insert_collectable_blindcoin = &postgres_insert_collectable_blindcoin; +  plugin->get_reserve_history = &postgres_get_reserve_history; +  plugin->free_reserve_history = &common_free_reserve_history; +  plugin->have_deposit = &postgres_have_deposit; +  plugin->insert_deposit = &postgres_insert_deposit; +  plugin->get_refresh_session = &postgres_get_refresh_session; +  plugin->create_refresh_session = &postgres_create_refresh_session; +  plugin->insert_refresh_melt = &postgres_insert_refresh_melt; +  plugin->get_refresh_melt = &postgres_get_refresh_melt; +  plugin->insert_refresh_order = &postgres_insert_refresh_order; +  plugin->get_refresh_order = &postgres_get_refresh_order; +  plugin->insert_refresh_commit_coins = &postgres_insert_refresh_commit_coins; +  plugin->get_refresh_commit_coins = &postgres_get_refresh_commit_coins; +  plugin->insert_refresh_commit_links = &postgres_insert_refresh_commit_links; +  plugin->get_refresh_commit_links = &postgres_get_refresh_commit_links; +  plugin->insert_refresh_collectable = &postgres_insert_refresh_collectable; +  plugin->get_link_data_list = &postgres_get_link_data_list; +  plugin->free_link_data_list = &common_free_link_data_list; +  plugin->get_transfer = &postgres_get_transfer; +  // plugin->have_lock = &postgres_have_lock; +  // plugin->insert_lock = &postgres_insert_lock; +  plugin->get_coin_transactions = &postgres_get_coin_transactions; +  plugin->free_coin_transaction_list = &common_free_coin_transaction_list; +  return plugin; +} + + +/** + * Shutdown Postgres database subsystem. + * + * @param cls a `struct TALER_MINTDB_Plugin` + * @return NULL (always) + */ +void * +libtaler_plugin_mintdb_postgres_done (void *cls) +{ +  struct TALER_MINTDB_Plugin *plugin = cls; +  struct PostgresClosure *pg = plugin->cls; + +  GNUNET_free (pg->connection_cfg_str); +  GNUNET_free (pg); +  GNUNET_free (plugin); +  return NULL; +} + +/* end of plugin_mintdb_postgres.c */ diff --git a/src/mintdb/test_mintdb.c b/src/mintdb/test_mintdb.c new file mode 100644 index 00000000..a8209407 --- /dev/null +++ b/src/mintdb/test_mintdb.c @@ -0,0 +1,393 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mint/test_mint_db.c + * @brief test cases for DB interaction functions + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include "plugin.h" + +static int result; + +#define FAILIF(cond)                              \ +  do {                                          \ +    if (!(cond)){ break;}                      \ +    GNUNET_break (0);                           \ +    goto drop;                                  \ +  } while (0) + + +#define RND_BLK(ptr)                                                    \ +  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr)) + +#define ZR_BLK(ptr) \ +  memset (ptr, 0, sizeof (*ptr)) + + +#define CURRENCY "EUR" + +/** + * Checks if the given reserve has the given amount of balance and expiry + * + * @param session the database connection + * @param pub the public key of the reserve + * @param value balance value + * @param fraction balance fraction + * @param currency currency of the reserve + * @param expiry expiration of the reserve + * @return #GNUNET_OK if the given reserve has the same balance and expiration + *           as the given parameters; #GNUNET_SYSERR if not + */ +static int +check_reserve (struct TALER_MINTDB_Session *session, +               const struct TALER_ReservePublicKeyP *pub, +               uint64_t value, +               uint32_t fraction, +               const char *currency, +               uint64_t expiry) +{ +  struct Reserve reserve; + +  reserve.pub = *pub; + +  FAILIF (GNUNET_OK != +          plugin->reserve_get (plugin->cls, +                               session, +                               &reserve)); +  FAILIF (value != reserve.balance.value); +  FAILIF (fraction != reserve.balance.fraction); +  FAILIF (0 != strcmp (currency, reserve.balance.currency)); +  FAILIF (expiry != reserve.expiry.abs_value_us); + +  return GNUNET_OK; + drop: +  return GNUNET_SYSERR; +} + + +struct DenomKeyPair +{ +  struct TALER_DenominationPrivateKey priv; +  struct TALER_DenominationPublicKey pub; +}; + + +static struct DenomKeyPair * +create_denom_key_pair (unsigned int size) +{ +  struct DenomKeyPair *dkp; + +  dkp = GNUNET_new (struct DenomKeyPair); +  dkp->priv.rsa_private_key = GNUNET_CRYPTO_rsa_private_key_create (size); +  GNUNET_assert (NULL != dkp->priv.rsa_private_key); +  dkp->pub.rsa_public_key +    = GNUNET_CRYPTO_rsa_private_key_get_public (dkp->priv.rsa_private_key); +  return dkp; +} + + +static void +destroy_denon_key_pair (struct DenomKeyPair *dkp) +{ +  GNUNET_CRYPTO_rsa_public_key_free (dkp->pub.rsa_public_key); +  GNUNET_CRYPTO_rsa_private_key_free (dkp->priv.rsa_private_key); +  GNUNET_free (dkp); +} + +/** + * 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 cfg configuration + */ +static void +run (void *cls, +     char *const *args, +     const char *cfgfile, +     const struct GNUNET_CONFIGURATION_Handle *cfg) +{ +  struct TALER_MINTDB_Session *session; +  struct TALER_ReservePublicKeyP reserve_pub; +  struct Reserve reserve; +  struct GNUNET_TIME_Absolute expiry; +  struct TALER_Amount amount; +  struct DenomKeyPair *dkp; +  struct GNUNET_HashCode h_blind; +  struct CollectableBlindcoin cbc; +  struct CollectableBlindcoin cbc2; +  struct ReserveHistory *rh; +  struct ReserveHistory *rh_head; +  struct BankTransfer *bt; +  struct CollectableBlindcoin *withdraw; +  struct Deposit deposit; +  struct Deposit deposit2; +  struct json_t *wire; +  const char * const json_wire_str = +      "{ \"type\":\"SEPA\", \ +\"IBAN\":\"DE67830654080004822650\",                    \ +\"name\":\"GNUnet e.V.\",                               \ +\"bic\":\"GENODEF1SLR\",                                 \ +\"edate\":\"1449930207000\",                                \ +\"r\":123456789,                                     \ +\"address\": \"foobar\"}"; +  unsigned int cnt; + +  dkp = NULL; +  rh = NULL; +  wire = NULL; +  session = NULL; +  ZR_BLK (&cbc); +  ZR_BLK (&cbc2); +  if (GNUNET_OK != +      TALER_MINT_plugin_load (cfg)) +  { +    result = 1; +    return; +  } +  if (GNUNET_OK != +      plugin->create_tables (plugin->cls, +                             GNUNET_YES)) +  { +    result = 2; +    goto drop; +  } +  if (NULL == +      (session = plugin->get_session (plugin->cls, +                                      GNUNET_YES))) +  { +    result = 3; +    goto drop; +  } +  RND_BLK (&reserve_pub); +  reserve.pub = reserve_pub; +  amount.value = 1; +  amount.fraction = 1; +  strcpy (amount.currency, CURRENCY); +  expiry = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), +                                     GNUNET_TIME_UNIT_HOURS); +  result = 4; +  FAILIF (GNUNET_OK != +          plugin->reserves_in_insert (plugin->cls, +                                      session, +                                      &reserve, +                                      &amount, +                                      expiry)); +  FAILIF (GNUNET_OK != +          check_reserve (session, +                         &reserve_pub, +                         amount.value, +                         amount.fraction, +                         amount.currency, +                         expiry.abs_value_us)); +  FAILIF (GNUNET_OK != +          plugin->reserves_in_insert (plugin->cls, +                                      session, +                                      &reserve, +                                      &amount, +                                      expiry)); +  FAILIF (GNUNET_OK != +          check_reserve (session, +                         &reserve_pub, +                         ++amount.value, +                         ++amount.fraction, +                         amount.currency, +                         expiry.abs_value_us)); +  dkp = create_denom_key_pair (1024); +  RND_BLK(&h_blind); +  RND_BLK(&cbc.reserve_sig); +  cbc.denom_pub = dkp->pub; +  cbc.sig.rsa_signature +    = GNUNET_CRYPTO_rsa_sign (dkp->priv.rsa_private_key, +                              &h_blind, +                              sizeof (h_blind)); +  (void) memcpy (&cbc.reserve_pub, +                 &reserve_pub, +                 sizeof (reserve_pub)); +  amount.value--; +  amount.fraction--; +  FAILIF (GNUNET_OK != +          plugin->insert_collectable_blindcoin (plugin->cls, +                                                session, +                                                &h_blind, +                                                amount, +                                                &cbc)); +  FAILIF (GNUNET_OK != +          check_reserve (session, +                         &reserve_pub, +                         amount.value, +                         amount.fraction, +                         amount.currency, +                         expiry.abs_value_us)); +  FAILIF (GNUNET_YES != +          plugin->get_collectable_blindcoin (plugin->cls, +                                             session, +                                             &h_blind, +                                             &cbc2)); +  FAILIF (NULL == cbc2.denom_pub.rsa_public_key); +  FAILIF (0 != memcmp (&cbc2.reserve_sig, +                       &cbc.reserve_sig, +                       sizeof (cbc2.reserve_sig))); +  FAILIF (0 != memcmp (&cbc2.reserve_pub, +                       &cbc.reserve_pub, +                       sizeof (cbc2.reserve_pub))); +  FAILIF (GNUNET_OK != +          GNUNET_CRYPTO_rsa_verify (&h_blind, +                                    cbc2.sig.rsa_signature, +                                    dkp->pub.rsa_public_key)); +  rh = plugin->get_reserve_history (plugin->cls, +                                    session, +                                    &reserve_pub); +  FAILIF (NULL == rh); +  rh_head = rh; +  for (cnt=0; NULL != rh_head; rh_head=rh_head->next, cnt++) +  { +    switch (rh_head->type) +    { +    case TALER_MINT_DB_RO_BANK_TO_MINT: +      bt = rh_head->details.bank; +      FAILIF (0 != memcmp (&bt->reserve_pub, +                           &reserve_pub, +                           sizeof (reserve_pub))); +      FAILIF (1 != bt->amount.value); +      FAILIF (1 != bt->amount.fraction); +      FAILIF (0 != strcmp (CURRENCY, bt->amount.currency)); +      FAILIF (NULL != bt->wire); /* FIXME: write wire details to db */ +      break; +    case TALER_MINT_DB_RO_WITHDRAW_COIN: +      withdraw = rh_head->details.withdraw; +      FAILIF (0 != memcmp (&withdraw->reserve_pub, +                           &reserve_pub, +                           sizeof (reserve_pub))); +      FAILIF (0 != memcmp (&withdraw->h_coin_envelope, +                           &h_blind, sizeof (h_blind))); +      break; +    } +  } +  FAILIF (3 != cnt); +  /* Tests for deposits */ +  RND_BLK (&deposit.coin.coin_pub); +  deposit.coin.denom_pub = dkp->pub; +  deposit.coin.denom_sig = cbc.sig; +  RND_BLK (&deposit.csig); +  RND_BLK (&deposit.merchant_pub); +  RND_BLK (&deposit.h_contract); +  RND_BLK (&deposit.h_wire); +  wire = json_loads (json_wire_str, 0, NULL); +  deposit.wire = wire; +  deposit.transaction_id = +      GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX); +  deposit.amount_with_fee = amount; +  FAILIF (GNUNET_OK != +          plugin->insert_deposit (plugin->cls, +                                  session, &deposit)); +  FAILIF (GNUNET_YES != +          plugin->have_deposit (plugin->cls, +                                session, +                                &deposit)); +  (void) memcpy (&deposit2, +                 &deposit, +                 sizeof (deposit)); +  deposit2.transaction_id++;     /* should fail if transaction id is different */ +  FAILIF (GNUNET_NO != +          plugin->have_deposit (plugin->cls, +                                session, +                                &deposit2)); +  deposit2.transaction_id = deposit.transaction_id; +  RND_BLK (&deposit2.merchant_pub); /* should fail if merchant is different */ +  FAILIF (GNUNET_NO != +          plugin->have_deposit (plugin->cls, +                                session, +                                &deposit2)); +  (void) memcpy (&deposit2.merchant_pub, +                 &deposit.merchant_pub, +                 sizeof (deposit.merchant_pub)); +  RND_BLK (&deposit2.coin.coin_pub); /* should fail if coin is different */ +  FAILIF (GNUNET_NO != +          plugin->have_deposit (plugin->cls, +                                session, +                                &deposit2)); +  result = 0; + + drop: +  if (NULL != wire) +    json_decref (wire); +  if (NULL != rh) +    plugin->free_reserve_history (plugin->cls, +                                  rh); +  rh = NULL; +  if (NULL != session) +    GNUNET_break (GNUNET_OK == +                  plugin->drop_temporary (plugin->cls, +                                          session)); +  if (NULL != dkp) +    destroy_denon_key_pair (dkp); +  if (NULL != cbc.sig.rsa_signature) +    GNUNET_CRYPTO_rsa_signature_free (cbc.sig.rsa_signature); +  if (NULL != cbc2.denom_pub.rsa_public_key) +    GNUNET_CRYPTO_rsa_public_key_free (cbc2.denom_pub.rsa_public_key); +  if (NULL != cbc2.sig.rsa_signature) +    GNUNET_CRYPTO_rsa_signature_free (cbc2.sig.rsa_signature); +  dkp = NULL; +  TALER_MINT_plugin_unload (); +} + + +int +main (int argc, +      char *const argv[]) +{ +   static const struct GNUNET_GETOPT_CommandLineOption options[] = { +    GNUNET_GETOPT_OPTION_END +  }; +   char *argv2[] = { +     "test-mint-db-<plugin_name>", /* will be replaced later */ +     "-c", "test-mint-db-<plugin_name>.conf", /* will be replaced later */ +     NULL, +   }; +   const char *plugin_name; +   char *config_filename; +   char *testname; + +   result = -1; +   if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) +   { +     GNUNET_break (0); +     return -1; +   } +   plugin_name++; +   (void) GNUNET_asprintf (&testname, +                           "test-mint-db-%s", plugin_name); +   (void) GNUNET_asprintf (&config_filename, +                           "%s.conf", testname); +   argv2[0] = argv[0]; +   argv2[2] = config_filename; +  if (GNUNET_OK != +      GNUNET_PROGRAM_run ((sizeof (argv2)/sizeof (char *)) - 1, argv2, +                          testname, +                          "Test cases for mint database helper functions.", +                          options, &run, NULL)) +  { +    GNUNET_free (config_filename); +    GNUNET_free (testname); +    return 3; +  } +  GNUNET_free (config_filename); +  GNUNET_free (testname); +  return result; +} diff --git a/src/mintdb/test_mintdb_deposits.c b/src/mintdb/test_mintdb_deposits.c new file mode 100644 index 00000000..dbe12e88 --- /dev/null +++ b/src/mintdb/test_mintdb_deposits.c @@ -0,0 +1,142 @@ +/* +  This file is part of TALER +  Copyright (C) 2014 Christian Grothoff (and other contributing authors) + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mint/test_mint_deposits.c + * @brief testcase for mint deposits + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include <libpq-fe.h> +#include <gnunet/gnunet_util_lib.h> +#include "plugin.h" +#include "taler_pq_lib.h" +#include "taler-mint-httpd.h" + +#define DB_URI "postgres:///taler" + +#define break_db_err(result) do { \ +    GNUNET_break(0); \ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ +  } while (0) + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond)                                              \ +  do {                                                            \ +    if (cond) { GNUNET_break (0); goto EXITIF_exit; }             \ +  } while (0) + + +/** + * Should we not interact with a temporary table? + */ +static int persistent; + +/** + * Testcase result + */ +static int result; + + +/** + * 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 cfg configuration + */ +static void +run (void *cls, +     char *const *args, +     const char *cfgfile, +     const struct GNUNET_CONFIGURATION_Handle *cfg) +{ +  static const char wire[] = "{" +      "\"type\":\"SEPA\"," +      "\"IBAN\":\"DE67830654080004822650\"," +      "\"NAME\":\"GNUNET E.V\"," +      "\"BIC\":\"GENODEF1SRL\"" +      "}"; +  struct Deposit *deposit; +  uint64_t transaction_id; +  struct TALER_MINTDB_Session *session; + +  deposit = NULL; +  EXITIF (GNUNET_OK != TALER_MINT_plugin_load (cfg)); +  EXITIF (GNUNET_OK != +          plugin->create_tables (plugin->cls, +                                 ! persistent)); +  session = plugin->get_session (plugin->cls, +                                 ! persistent); +  EXITIF (NULL == session); +  deposit = GNUNET_malloc (sizeof (struct Deposit) + sizeof (wire)); +  /* Makeup a random coin public key */ +  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, +                              deposit, +                              sizeof (struct Deposit)); +  /* Makeup a random 64bit transaction ID */ +  transaction_id = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, +                                             UINT64_MAX); +  deposit->transaction_id = GNUNET_htonll (transaction_id); +  /* Random amount */ +  deposit->amount_with_fee.value = +      htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); +  deposit->amount_with_fee.fraction = +      htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); +  GNUNET_assert (strlen (TMH_MINT_CURRENCY) < sizeof (deposit->amount_with_fee.currency)); +  strcpy (deposit->amount_with_fee.currency, TMH_MINT_CURRENCY); +  /* Copy wireformat */ +  deposit->wire = json_loads (wire, 0, NULL); +  EXITIF (GNUNET_OK != +          plugin->insert_deposit (plugin->cls, +                                  session, +                                  deposit)); +  EXITIF (GNUNET_OK != +          plugin->have_deposit (plugin->cls, +                                session, +                                deposit)); +  result = GNUNET_OK; + + EXITIF_exit: +  GNUNET_free_non_null (deposit); +  return; +} + + +int +main (int argc, +      char *const argv[]) +{ +  static const struct GNUNET_GETOPT_CommandLineOption options[] = { +    {'T', "persist", NULL, +     gettext_noop ("Use a persistent database table instead of a temporary one"), +     GNUNET_NO, &GNUNET_GETOPT_set_one, &persistent}, +    GNUNET_GETOPT_OPTION_END +  }; + + +  persistent = GNUNET_NO; +  result = GNUNET_SYSERR; +  if (GNUNET_OK != +      GNUNET_PROGRAM_run (argc, argv, +                          "test-mint-deposits", +                          "testcase for mint deposits", +                          options, &run, NULL)) +    return 3; +  return (GNUNET_OK == result) ? 0 : 1; +} diff --git a/src/mintdb/test_mintdb_keyio.c b/src/mintdb/test_mintdb_keyio.c new file mode 100644 index 00000000..83df2504 --- /dev/null +++ b/src/mintdb/test_mintdb_keyio.c @@ -0,0 +1,86 @@ +/* +  This file is part of TALER +  Copyright (C) 2014 GNUnet e. V. (and other contributing authors) + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mint/test_mint_common.c + * @brief test cases for some functions in mint/mint_common.c + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include "gnunet/gnunet_util_lib.h" +#include "taler_signatures.h" +#include "key_io.h" + +#define RSA_KEY_SIZE 1024 + + +#define EXITIF(cond)                                              \ +  do {                                                            \ +    if (cond) { GNUNET_break (0); goto EXITIF_exit; }             \ +  } while (0) + + +int +main (int argc, +      const char *const argv[]) +{ +  struct TALER_DenominationKeyIssueInformation dki; +  char *enc; +  size_t enc_size; +  struct TALER_DenominationKeyIssueInformation dki_read; +  char *enc_read; +  size_t enc_read_size; +  char *tmpfile; +  int ret; + +  ret = 1; +  enc = NULL; +  enc_read = NULL; +  tmpfile = NULL; +  dki.denom_priv.rsa_private_key = NULL; +  dki_read.denom_priv.rsa_private_key = NULL; +  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, +                              &dki.issue.signature, +                              sizeof (dki) - offsetof (struct TALER_DenominationKeyValidityPS, +                                                       signature)); +  dki.denom_priv.rsa_private_key +    = GNUNET_CRYPTO_rsa_private_key_create (RSA_KEY_SIZE); +  enc_size = GNUNET_CRYPTO_rsa_private_key_encode (dki.denom_priv.rsa_private_key, +                                                   &enc); +  EXITIF (NULL == (tmpfile = GNUNET_DISK_mktemp ("test_mint_common"))); +  EXITIF (GNUNET_OK != TALER_MINT_write_denom_key (tmpfile, &dki)); +  EXITIF (GNUNET_OK != TALER_MINT_read_denom_key (tmpfile, &dki_read)); +  enc_read_size = GNUNET_CRYPTO_rsa_private_key_encode (dki_read.denom_priv.rsa_private_key, +                                                        &enc_read); +  EXITIF (enc_size != enc_read_size); +  EXITIF (0 != memcmp (enc, +                       enc_read, +                       enc_size)); +  ret = 0; + +  EXITIF_exit: +  GNUNET_free_non_null (enc); +  if (NULL != tmpfile) +  { +    (void) unlink (tmpfile); +    GNUNET_free (tmpfile); +  } +  GNUNET_free_non_null (enc_read); +  if (NULL != dki.denom_priv.rsa_private_key) +    GNUNET_CRYPTO_rsa_private_key_free (dki.denom_priv.rsa_private_key); +  if (NULL != dki_read.denom_priv.rsa_private_key) +    GNUNET_CRYPTO_rsa_private_key_free (dki_read.denom_priv.rsa_private_key); +  return ret; +}  | 
