diff options
Diffstat (limited to 'src/exchange-lib')
| -rw-r--r-- | src/exchange-lib/Makefile.am | 14 | ||||
| -rw-r--r-- | src/exchange-lib/test_exchange_api_keys_cherry_picking.c | 687 | 
2 files changed, 700 insertions, 1 deletions
diff --git a/src/exchange-lib/Makefile.am b/src/exchange-lib/Makefile.am index d891d1d6..d6e068b7 100644 --- a/src/exchange-lib/Makefile.am +++ b/src/exchange-lib/Makefile.am @@ -45,7 +45,8 @@ endif  endif  check_PROGRAMS = \ -  test_exchange_api +  test_exchange_api \ +  test_exchange_api_keys_cherry_picking  AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; @@ -64,6 +65,17 @@ test_exchange_api_LDADD = \    -lgnunetutil \    -ljansson +test_exchange_api_keys_cherry_picking_SOURCES = \ +  test_exchange_api_keys_cherry_picking.c +test_exchange_api_keys_cherry_picking_LDADD = \ +  libtalerexchange.la \ +  $(LIBGCRYPT_LIBS) \ +  $(top_builddir)/src/json/libtalerjson.la \ +  $(top_builddir)/src/util/libtalerutil.la \ +  -lgnunetcurl \ +  -lgnunetutil \ +  -ljansson +  EXTRA_DIST = \    test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv \    test_exchange_api_home/.config/taler/test.json \ diff --git a/src/exchange-lib/test_exchange_api_keys_cherry_picking.c b/src/exchange-lib/test_exchange_api_keys_cherry_picking.c new file mode 100644 index 00000000..02e62a3e --- /dev/null +++ b/src/exchange-lib/test_exchange_api_keys_cherry_picking.c @@ -0,0 +1,687 @@ +/* +  This file is part of TALER +  Copyright (C) 2014-2017 GNUnet e.V. and Inria + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange/test_exchange_api_keys_cherry_picking.c + * @brief testcase to test exchange's /keys cherry picking ability + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> + + +/** + * Main execution context for the main loop. + */ +static struct GNUNET_CURL_Context *ctx; + +/** + * Handle to access the exchange. + */ +static struct TALER_EXCHANGE_Handle *exchange; + +/** + * Context for running the CURL event loop. + */ +static struct GNUNET_CURL_RescheduleContext *rc; + +/** + * Handle to the exchange process. + */ +static struct GNUNET_OS_Process *exchanged; + +/** + * Task run on timeout. + */ +static struct GNUNET_SCHEDULER_Task *timeout_task; + +/** + * Result of the testcases, #GNUNET_OK on success + */ +static int result; + + +/** + * Opcodes for the interpreter. + */ +enum OpCode +{ +  /** +   * Termination code, stops the interpreter loop (with success). +   */ +  OC_END = 0, + +  /** +   * Run a process. +   */ +  OC_RUN_PROCESS, + +  /** +   * Signal the exchange to reload the keys. +   */ +  OC_SIGNAL_EXCHANGE, + +  /** +   * Check the /keys. +   */ +  OC_CHECK_KEYS + +}; + + +/** + * Details for a exchange operation to execute. + */ +struct Command +{ +  /** +   * Opcode of the command. +   */ +  enum OpCode oc; + +  /** +   * Label for the command, can be NULL. +   */ +  const char *label; + +  /** +   * Details about the command. +   */ +  union +  { + +    struct { + +      /** +       * Binary to execute. +       */ +      const char *binary; + +      /** +       * Command-line arguments for the process to be run. +       */ +      char *const*argv; + +      /** +       * Process handle. +       */ +      struct GNUNET_OS_Process *proc; + +      /** +       * ID of task called whenever we get a SIGCHILD. +       */ +      struct GNUNET_SCHEDULER_Task *child_death_task; + +    } run_process; + +    struct { + +      /** +       * Expected number of denomination keys. +       */ +      unsigned int num_denom_keys; + +    } check_keys; + +  } details; + +}; + + +/** + * State of the interpreter loop. + */ +struct InterpreterState +{ +  /** +   * Keys from the exchange. +   */ +  const struct TALER_EXCHANGE_Keys *keys; + +  /** +   * Commands the interpreter will run. +   */ +  struct Command *commands; + +  /** +   * Interpreter task (if one is scheduled). +   */ +  struct GNUNET_SCHEDULER_Task *task; + +  /** +   * Instruction pointer.  Tells #interpreter_run() which +   * instruction to run next. +   */ +  unsigned int ip; + +}; + + +/** + * Pipe used to communicate child death via signal. + */ +static struct GNUNET_DISK_PipeHandle *sigpipe; + + +/** + * The testcase failed, return with an error code. + * + * @param is interpreter state to clean up + */ +static void +fail (struct InterpreterState *is) +{ +  result = GNUNET_SYSERR; +  GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct InterpreterState` + */ +static void +interpreter_run (void *cls); + + +/** + * Run the next command with the interpreter. + * + * @param is current interpeter state. + */ +static void +next_command (struct InterpreterState *is) +{ +  if (GNUNET_SYSERR == result) +    return; /* ignore, we already failed! */ +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Task triggered whenever we receive a SIGCHLD (child + * process died). + * + * @param cls closure, NULL if we need to self-restart + */ +static void +maint_child_death (void *cls) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; +  const struct GNUNET_DISK_FileHandle *pr; +  char c[16]; + +  switch (cmd->oc) { +  case OC_RUN_PROCESS: +    cmd->details.run_process.child_death_task = NULL; +    pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ); +    GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c))); +    GNUNET_OS_process_wait (cmd->details.run_process.proc); +    GNUNET_OS_process_destroy (cmd->details.run_process.proc); +    cmd->details.run_process.proc = NULL; +    break; +  default: +    GNUNET_break (0); +    fail (is); +    return; +  } +  next_command (is); +} + + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct InterpreterState` + */ +static void +interpreter_run (void *cls) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; +  const struct GNUNET_SCHEDULER_TaskContext *tc; + +  is->task = NULL; +  tc = GNUNET_SCHEDULER_get_task_context (); +  if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) +  { +    fprintf (stderr, +             "Test aborted by shutdown request\n"); +    fail (is); +    return; +  } +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Running command `%s'\n", +              cmd->label); +  switch (cmd->oc) +  { +  case OC_END: +    result = GNUNET_OK; +    GNUNET_SCHEDULER_shutdown (); +    return; +  case OC_RUN_PROCESS: +    { +      const struct GNUNET_DISK_FileHandle *pr; + +      cmd->details.run_process.proc +        = GNUNET_OS_start_process_vap (GNUNET_NO, +                                       GNUNET_OS_INHERIT_STD_ALL, +                                       NULL, NULL, NULL, +                                       cmd->details.run_process.binary, +                                       cmd->details.run_process.argv); +      if (NULL == cmd->details.run_process.proc) +      { +        GNUNET_break (0); +        fail (is); +        return; +      } +      pr = GNUNET_DISK_pipe_handle (sigpipe, +                                    GNUNET_DISK_PIPE_END_READ); +      cmd->details.run_process.child_death_task +        = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, +                                          pr, +                                          &maint_child_death, +                                          is); +      return; +    } +  default: +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unknown instruction %d at %u (%s)\n", +                cmd->oc, +                is->ip, +                cmd->label); +    fail (is); +    return; +  } +} + + +/** + * Signal handler called for SIGCHLD.  Triggers the + * respective handler by writing to the trigger pipe. + */ +static void +sighandler_child_death () +{ +  static char c; +  int old_errno = errno;	/* back-up errno */ + +  GNUNET_break (1 == +		GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle +					(sigpipe, GNUNET_DISK_PIPE_END_WRITE), +					&c, sizeof (c))); +  errno = old_errno;		/* restore errno */ +} + + +/** + * Function run when the test terminates (good or bad) with timeout. + * + * @param cls NULL + */ +static void +do_timeout (void *cls) +{ +  timeout_task = NULL; +  GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Function run when the test terminates (good or bad). + * Cleans up our state. + * + * @param cls the interpreter state. + */ +static void +do_shutdown (void *cls) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd; +  unsigned int i; + +  fprintf (stderr, +           "Executing shutdown at `%s'\n", +           is->commands[is->ip].label); + +  for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++) +  { +    switch (cmd->oc) +    { +    case OC_END: +      GNUNET_assert (0); +      break; +    case OC_RUN_PROCESS: +      if (NULL != cmd->details.run_process.proc) +      { +        GNUNET_break (0 == +                      GNUNET_OS_process_kill (cmd->details.run_process.proc, +                                              SIGKILL)); +        GNUNET_OS_process_wait (cmd->details.run_process.proc); +        GNUNET_OS_process_destroy (cmd->details.run_process.proc); +        cmd->details.run_process.proc = NULL; +      } +      if (NULL != cmd->details.run_process.child_death_task) +      { +        GNUNET_SCHEDULER_cancel (cmd->details.run_process.child_death_task); +        cmd->details.run_process.child_death_task = NULL; +      } +      break; +    default: +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Unknown instruction %d at %u (%s)\n", +                  cmd->oc, +                  i, +                  cmd->label); +      break; +    } +  } +  if (NULL != is->task) +  { +    GNUNET_SCHEDULER_cancel (is->task); +    is->task = NULL; +  } +  GNUNET_free (is); +  if (NULL != exchange) +  { +    TALER_EXCHANGE_disconnect (exchange); +    exchange = NULL; +  } +  if (NULL != ctx) +  { +    GNUNET_CURL_fini (ctx); +    ctx = NULL; +  } +  if (NULL != rc) +  { +    GNUNET_CURL_gnunet_rc_destroy (rc); +    rc = NULL; +  } +  if (NULL != timeout_task) +  { +    GNUNET_SCHEDULER_cancel (timeout_task); +    timeout_task = NULL; +  } +} + + +/** + * Functions of this type are called to provide the retrieved signing and + * denomination keys of the exchange.  No TALER_EXCHANGE_*() functions should be called + * in this callback. + * + * @param cls closure + * @param keys information about keys of the exchange + * @param vc version compatibility + */ +static void +cert_cb (void *cls, +         const struct TALER_EXCHANGE_Keys *keys, +	 enum TALER_EXCHANGE_VersionCompatibility vc) +{ +  struct InterpreterState *is = cls; + +  /* check that keys is OK */ +#define ERR(cond) do { if(!(cond)) break; GNUNET_break (0); GNUNET_SCHEDULER_shutdown(); return; } while (0) +  ERR (NULL == keys); +  ERR (0 == keys->num_sign_keys); +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Read %u signing keys\n", +              keys->num_sign_keys); +  ERR (0 == keys->num_denom_keys); +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Read %u denomination keys\n", +              keys->num_denom_keys); +#undef ERR + +  /* run actual tests via interpreter-loop */ +  is->keys = keys; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + */ +static void +run (void *cls) +{ +  struct InterpreterState *is; +  static struct Command commands[] = +  { +    { .oc = OC_END } +  }; + +  is = GNUNET_new (struct InterpreterState); +  is->commands = commands; + +  ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, +                          &rc); +  GNUNET_assert (NULL != ctx); +  rc = GNUNET_CURL_gnunet_rc_create (ctx); +  exchange = TALER_EXCHANGE_connect (ctx, +                                     "http://localhost:8081", +                                     &cert_cb, is, +                                     TALER_EXCHANGE_OPTION_END); +  GNUNET_assert (NULL != exchange); +  timeout_task +    = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply +                                    (GNUNET_TIME_UNIT_SECONDS, 300), +                                    &do_timeout, NULL); +  GNUNET_SCHEDULER_add_shutdown (&do_shutdown, +                                 is); +} + + +/** + * Remove files from previous runs + */ +static void +cleanup_files () +{ +  struct GNUNET_CONFIGURATION_Handle *cfg; +  char *dir; + +  cfg = GNUNET_CONFIGURATION_create (); +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_load (cfg, +                                 "test_exchange_api.conf")) +  { +    GNUNET_break (0); +    GNUNET_CONFIGURATION_destroy (cfg); +    exit (77); +  } +  GNUNET_assert (GNUNET_OK == +                 GNUNET_CONFIGURATION_get_value_filename (cfg, +                                                          "exchange", +                                                          "keydir", +                                                          &dir)); +  if (GNUNET_YES == +      GNUNET_DISK_directory_test (dir, +                                  GNUNET_NO)) +    GNUNET_break (GNUNET_OK == +                  GNUNET_DISK_directory_remove (dir)); +  GNUNET_free (dir); +  GNUNET_CONFIGURATION_destroy (cfg); +} + + +/** + * Main function for the testcase for the exchange API. + * + * @param argc expected to be 1 + * @param argv expected to only contain the program name + */ +int +main (int argc, +      char * const *argv) +{ +  struct GNUNET_OS_Process *proc; +  struct GNUNET_SIGNAL_Context *shc_chld; +  enum GNUNET_OS_ProcessStatusType type; +  unsigned long code; +  unsigned int iter; + +  /* These might get in the way... */ +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  GNUNET_log_setup ("test-exchange-api-keys-cherry-picking", +                    "INFO", +                    NULL); +  if (GNUNET_OK != +      GNUNET_NETWORK_test_port_free (IPPROTO_TCP, +				     8081)) +  { +    fprintf (stderr, +             "Required port %u not available, skipping.\n", +	     8081); +    return 77; +  } +  cleanup_files (); + +  proc = GNUNET_OS_start_process (GNUNET_NO, +                                  GNUNET_OS_INHERIT_STD_ALL, +                                  NULL, NULL, NULL, +                                  "taler-exchange-keyup", +                                  "taler-exchange-keyup", +                                  "-c", "test_exchange_api.conf", +                                  "-o", "auditor.in", +                                  NULL); +  if (NULL == proc) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +		"Failed to run `taler-exchange-keyup`, is your PATH correct?\n"); +    return 77; +  } +  GNUNET_OS_process_wait (proc); +  GNUNET_OS_process_destroy (proc); + +  proc = GNUNET_OS_start_process (GNUNET_NO, +                                  GNUNET_OS_INHERIT_STD_ALL, +                                  NULL, NULL, NULL, +                                  "taler-auditor-sign", +                                  "taler-auditor-sign", +                                  "-c", "test_exchange_api.conf", +                                  "-u", "http://auditor/", +                                  "-m", "98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG", +                                  "-r", "auditor.in", +                                  "-o", "test_exchange_api_home/.local/share/taler/auditors/auditor.out", +                                  NULL); +  if (NULL == proc) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +		"Failed to run `taler-exchange-keyup`, is your PATH correct?\n"); +    return 77; +  } +  GNUNET_OS_process_wait (proc); +  GNUNET_OS_process_destroy (proc); + +  proc = GNUNET_OS_start_process (GNUNET_NO, +                                  GNUNET_OS_INHERIT_STD_ALL, +                                  NULL, NULL, NULL, +                                  "taler-exchange-dbinit", +                                  "taler-exchange-dbinit", +                                  "-c", "test_exchange_api.conf", +                                  "-r", +                                  NULL); +  if (NULL == proc) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +		"Failed to run `taler-exchange-dbinit`, is your PATH correct?\n"); +    return 77; +  } +  if (GNUNET_SYSERR == +      GNUNET_OS_process_wait_status (proc, +                                     &type, +                                     &code)) +  { +    GNUNET_break (0); +    GNUNET_OS_process_destroy (proc); +    return 1; +  } +  GNUNET_OS_process_destroy (proc); +  if ( (type == GNUNET_OS_PROCESS_EXITED) && +       (0 != code) ) +  { +    fprintf (stderr, +             "Failed to setup database\n"); +    return 77; +  } +  if ( (type != GNUNET_OS_PROCESS_EXITED) || +       (0 != code) ) +  { +    fprintf (stderr, +             "Unexpected error running `taler-exchange-dbinit'!\n"); +    return 1; +  } +  exchanged = GNUNET_OS_start_process (GNUNET_NO, +                                       GNUNET_OS_INHERIT_STD_ALL, +                                       NULL, NULL, NULL, +                                       "taler-exchange-httpd", +                                       "taler-exchange-httpd", +                                       "-c", "test_exchange_api.conf", +                                       "-i", +                                       NULL); +  /* give child time to start and bind against the socket */ +  fprintf (stderr, +           "Waiting for `taler-exchange-httpd' to be ready"); +  iter = 0; +  do +    { +      if (10 == iter) +      { +	fprintf (stderr, +		 "Failed to launch `taler-exchange-httpd' (or `wget')\n"); +	GNUNET_OS_process_kill (exchanged, +				SIGTERM); +	GNUNET_OS_process_wait (exchanged); +	GNUNET_OS_process_destroy (exchanged); +	return 77; +      } +      fprintf (stderr, "."); +      sleep (1); +      iter++; +    } +  while (0 != system ("wget -q -t 1 -T 1 http://127.0.0.1:8081/keys -o /dev/null -O /dev/null")); +  fprintf (stderr, "\n"); +  result = GNUNET_NO; +  sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_NO, GNUNET_NO); +  GNUNET_assert (NULL != sigpipe); +  shc_chld = GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, +                                            &sighandler_child_death); +  GNUNET_SCHEDULER_run (&run, NULL); +  GNUNET_SIGNAL_handler_uninstall (shc_chld); +  shc_chld = NULL; +  GNUNET_DISK_pipe_close (sigpipe); +  GNUNET_OS_process_kill (exchanged, +                          SIGTERM); +  GNUNET_OS_process_wait (exchanged); +  GNUNET_OS_process_destroy (exchanged); +  return (GNUNET_OK == result) ? 0 : 1; +} + +/* end of test_exchange_api_keys_cherry_picking.c */  | 
