/*
  This file is part of Anastasis
  Copyright (C) 2020-2023 Anastasis SARL

  Anastasis 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.

  Anastasis 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
  Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file reducer/anastasis_api_backup_redux.c
 * @brief anastasis reducer backup api
 * @author Christian Grothoff
 * @author Dominik Meister
 * @author Dennis Neufeld
 */

#include "platform.h"
#include "anastasis_redux.h"
#include "anastasis_api_redux.h"
#include "anastasis_api_redux_state.h"
#include <taler/taler_merchant_service.h>

/**
 * How long do Anastasis providers store data if the service
 * is free? Must match #ANASTASIS_MAX_YEARS_STORAGE from
 * anastasis-httpd.h.
 */
#define ANASTASIS_FREE_STORAGE GNUNET_TIME_relative_multiply ( \
          GNUNET_TIME_UNIT_YEARS, 5)

/**
 * CPU limiter: do not evaluate more than 16k
 * possible policy combinations to find the "best"
 * policy.
 */
#define MAX_EVALUATIONS (1024 * 16)


#define GENERATE_STRING(STRING) #STRING,
static const char *backup_strings[] = {
  ANASTASIS_BACKUP_STATES (GENERATE_STRING)
};
#undef GENERATE_STRING


/**
 * Linked list of costs.
 */
struct Costs
{

  /**
   * Kept in a LL.
   */
  struct Costs *next;

  /**
   * Cost in one of the currencies.
   */
  struct TALER_Amount cost;
};


/**
 * Pick the single amount a provider contributes to an advisory total.
 *
 * A price list is not a sum: a provider offering both EUR and CHF wants
 * one or the other, not both, so adding every entry would tell the user
 * they owe twice over.  Each provider therefore contributes exactly one
 * amount --- in the preferred currency if it offers one, and otherwise in
 * its own primary currency, which is the honest answer that this part of
 * the backup will be settled separately.
 *
 * @param common state holding the user's preferred currency
 * @param al price list to pick from
 * @return the amount to add, NULL if @a al is empty
 */
static const struct TALER_Amount *
preferred_price (const struct ANASTASIS_ReduxCommon *common,
                 const struct TALER_AmountList *al)
{
  if (NULL != common->preferred_currency)
  {
    const struct TALER_Amount *a;

    a = TALER_amount_list_find (al,
                                common->preferred_currency);
    if (NULL != a)
      return a;
  }
  if (0 == al->tal_len)
    return NULL;
  return &al->tal[0];
}


/**
 * Check if two price lists offer the same prices, ignoring order.
 *
 * @param a first list
 * @param b second list
 * @return true if they agree on every currency
 */
static bool
amount_lists_equal (const struct TALER_AmountList *a,
                    const struct TALER_AmountList *b)
{
  if (a->tal_len != b->tal_len)
    return false;
  for (unsigned int i = 0; i<a->tal_len; i++)
  {
    const struct TALER_Amount *o;

    o = TALER_amount_list_find (b,
                                a->tal[i].currency);
    if ( (NULL == o) ||
         (0 != TALER_amount_cmp (&a->tal[i],
                                 o)) )
      return false;
  }
  return true;
}


/**
 * Add amount from @a cost to @a my_cost list.
 *
 * @param[in,out] my_cost pointer to list to modify
 * @param cost amount to add
 */
static void
add_cost (struct Costs **my_cost,
          const struct TALER_Amount *cost)
{
  for (struct Costs *pos = *my_cost;
       NULL != pos;
       pos = pos->next)
  {
    if (GNUNET_OK !=
        TALER_amount_cmp_currency (&pos->cost,
                                   cost))
      continue;
    GNUNET_assert (0 <=
                   TALER_amount_add (&pos->cost,
                                     &pos->cost,
                                     cost));
    return;
  }
  {
    struct Costs *nc;

    nc = GNUNET_new (struct Costs);
    nc->cost = *cost;
    nc->next = *my_cost;
    *my_cost = nc;
  }
}


/**
 * Add amount from @a cost to @a my_cost list.
 *
 * @param[in,out] my_cost pointer to list to modify
 * @param cost amount to add
 */
static void
add_costs (struct Costs **my_cost,
           const struct Costs *costs)
{
  for (const struct Costs *pos = costs;
       NULL != pos;
       pos = pos->next)
  {
    add_cost (my_cost,
              &pos->cost);
  }
}


enum ANASTASIS_BackupState
ANASTASIS_backup_state_from_string_ (const char *state_string)
{
  for (enum ANASTASIS_BackupState i = 0;
       i < sizeof (backup_strings) / sizeof(*backup_strings);
       i++)
    if (0 == strcmp (state_string,
                     backup_strings[i]))
      return i;
  return ANASTASIS_BACKUP_STATE_INVALID;
}


const char *
ANASTASIS_backup_state_to_string_ (enum ANASTASIS_BackupState bs)
{
  if ( (bs < 0) ||
       (bs >= sizeof (backup_strings) / sizeof(*backup_strings)) )
  {
    GNUNET_break_op (0);
    return NULL;
  }
  return backup_strings[bs];
}


/**
 * Transition @a rs to @a new_backup_state.
 *
 * @param[in,out] rs the state to transition
 * @param new_backup_state the state to transition to
 */
static void
set_state (struct ANASTASIS_ReduxState *rs,
           enum ANASTASIS_BackupState new_backup_state)
{
  GNUNET_assert (ANASTASIS_RT_BACKUP == rs->type);
  rs->details.backup.state = new_backup_state;
}


/**
 * Returns an initial ANASTASIS backup state (CONTINENT_SELECTING).
 *
 * @param cfg handle for gnunet configuration
 * @return NULL on failure
 */
json_t *
ANASTASIS_backup_start (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  json_t *initial_state;
  const char *external_reducer = ANASTASIS_REDUX_probe_external_reducer ();

  if (NULL != external_reducer)
  {
    int pipefd_stdout[2];
    pid_t pid = 0;
    int status;
    FILE *reducer_stdout;

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Using external reducer '%s' for backup start status\n",
                external_reducer);

    GNUNET_assert (0 == pipe (pipefd_stdout));
    pid = fork ();
    if (pid == 0)
    {
      GNUNET_assert (0 ==
                     close (pipefd_stdout[0]));
      GNUNET_assert (STDOUT_FILENO ==
                     dup2 (pipefd_stdout[1],
                           STDOUT_FILENO));
      execlp (external_reducer,
              external_reducer,
              "-b",
              NULL);
      GNUNET_assert (0);
    }

    GNUNET_assert (0 ==
                   close (pipefd_stdout[1]));
    reducer_stdout = fdopen (pipefd_stdout[0],
                             "r");
    GNUNET_assert (NULL != reducer_stdout);
    {
      json_error_t err;

      initial_state = json_loadf (reducer_stdout,
                                  0,
                                  &err);

      if (NULL == initial_state)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "External reducer did not output valid JSON: %s:%d:%d %s\n",
                    err.source,
                    err.line,
                    err.column,
                    err.text);
        GNUNET_assert (0 == fclose (reducer_stdout));
        waitpid (pid, &status, 0);
        return NULL;
      }
    }

    GNUNET_assert (NULL != initial_state);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Waiting for external reducer to terminate.\n");
    GNUNET_assert (0 == fclose (reducer_stdout));
    reducer_stdout = NULL;
    waitpid (pid, &status, 0);

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "External reducer finished with exit status '%d'\n",
                status);
    return initial_state;
  }

  (void) cfg;
  initial_state = ANASTASIS_REDUX_load_continents_ ();
  if (NULL == initial_state)
    return NULL;
  GNUNET_assert (
    0 ==
    json_object_set_new (initial_state,
                         "reducer_type",
                         json_string ("backup")));
  GNUNET_assert (
    0 ==
    json_object_set_new (
      initial_state,
      "backup_state",
      json_string (
        ANASTASIS_backup_state_to_string_ (
          ANASTASIS_BACKUP_STATE_CONTINENT_SELECTING))));
  return initial_state;
}


/**
 * Test if @a challenge_size is small enough for the provider's
 * @a size_limit_in_mb.
 *
 * We add 1024 to @a challenge_size here as a "safety margin" as
 * the encrypted challenge has some additional headers around it
 *
 * @param size_limit_in_mb provider's upload limit
 * @param challenge_size actual binary size of the challenge
 * @return true if this fits
 */
static bool
challenge_size_ok (uint32_t size_limit_in_mb,
                   size_t challenge_size)
{
  return (size_limit_in_mb * 1024LLU * 1024LLU >=
          challenge_size + 1024LLU);
}


/**
 * DispatchHandler/Callback function which is called for a
 * "add_authentication" action.
 * Returns an #ANASTASIS_ReduxAction if operation is async.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
add_authentication (struct ANASTASIS_ReduxState *rs,
                    const json_t *arguments,
                    ANASTASIS_ActionCallback cb,
                    void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  const json_t *method;
  const char *method_type;
  const char *instructions;
  const char *mime_type = NULL;
  void *challenge;
  size_t challenge_size;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("type",
                             &method_type),
    GNUNET_JSON_spec_string ("instructions",
                             &instructions),
    GNUNET_JSON_spec_varsize ("challenge",
                              &challenge,
                              &challenge_size),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("mime_type",
                               &mime_type),
      NULL),
    GNUNET_JSON_spec_end ()
  };

  if (! rs->common.have_providers)
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'authentication_providers' missing");
    return NULL;
  }

  method = json_object_get (arguments,
                            "authentication_method");
  if (NULL == method)
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'authentication_method' required");
    return NULL;
  }
  if (GNUNET_OK !=
      GNUNET_JSON_parse (method,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break (0);
    json_dumpf ((json_t *) method,
                stderr,
                JSON_INDENT (2));
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'authentication_method' content malformed");
    return NULL;
  }
  /* Check we know at least one provider that supports this method */
  {
    bool found = false;
    bool too_big = false;

    for (unsigned int i = 0; i < rs->common.providers_len; i++)
    {
      const struct ANASTASIS_ReduxProvider *p = &rs->common.providers[i];
      const struct ANASTASIS_ReduxProviderConfig *cfg = &p->config;

      if (ANASTASIS_RPS_OK != p->status)
        continue;
      if (! p->have_config)
      {
        GNUNET_break (0);
        continue;
      }
      if (MHD_HTTP_OK != cfg->http_status)
        continue; /* skip providers that are down */
      if (0 == cfg->storage_limit_in_megabytes)
      {
        GNUNET_break (0);
        continue;
      }
      for (unsigned int j = 0; j < cfg->methods_len; j++)
        if (0 == strcmp (cfg->methods[j].type,
                         method_type))
        {
          found = true;
          break;
        }
      if (! challenge_size_ok (cfg->storage_limit_in_megabytes,
                               challenge_size))
      {
        /* Challenge data too big for this provider. Try to find another one.
           Note: we add 1024 to challenge-size here as a "safety margin" as
           the encrypted challenge has some additional headers around it */
        too_big = true;
        found = false;
      }
      if (found)
        break;
    }
    if (! found)
    {
      enum TALER_ErrorCode ec
        = too_big
          ? TALER_EC_ANASTASIS_REDUCER_CHALLENGE_DATA_TOO_BIG
          : TALER_EC_ANASTASIS_REDUCER_AUTHENTICATION_METHOD_NOT_SUPPORTED;

      ANASTASIS_REDUX_fail_ (rs,
                             cb,
                             cb_cls,
                             ec,
                             method_type);
      GNUNET_JSON_parse_free (spec);
      return NULL;
    }
  }

  /* append provided method to our array */
  {
    struct ANASTASIS_ReduxAuthMethod am = {
      .type = GNUNET_strdup (method_type),
      .instructions = GNUNET_strdup (instructions),
      .mime_type = (NULL == mime_type) ? NULL : GNUNET_strdup (mime_type),
      .challenge = GNUNET_memdup (challenge,
                                  challenge_size),
      .challenge_size = challenge_size
    };

    GNUNET_array_append (b->authentication_methods,
                         b->authentication_methods_len,
                         am);
    b->have_authentication_methods = true;
  }
  GNUNET_JSON_parse_free (spec);
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/**
 * DispatchHandler/Callback function which is called for a
 * "delete_authentication" action.
 * Returns an #ANASTASIS_ReduxAction if operation is async.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
del_authentication (struct ANASTASIS_ReduxState *rs,
                    const json_t *arguments,
                    ANASTASIS_ActionCallback cb,
                    void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  const json_t *idx;
  json_int_t index;

  if (! b->have_authentication_methods)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'authentication_methods' must be an array");
    return NULL;
  }
  if (NULL == arguments)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "arguments missing");
    return NULL;
  }
  idx = json_object_get (arguments,
                         "authentication_method");
  if ( (NULL == idx) ||
       (! json_is_integer (idx)) )
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'authentication_method' must be a number");
    return NULL;
  }
  index = json_integer_value (idx);
  if ( (index < 0) ||
       (index >= (json_int_t) b->authentication_methods_len) )
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
                           "removal failed");
    return NULL;
  }
  {
    struct ANASTASIS_ReduxAuthMethod *am
      = &b->authentication_methods[index];

    GNUNET_free (am->type);
    GNUNET_free (am->instructions);
    GNUNET_free (am->challenge);
    GNUNET_free (am->mime_type);
    memmove (am,
             am + 1,
             sizeof (*am) * (b->authentication_methods_len - index - 1));
    b->authentication_methods_len--;
  }
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/* ********************** done_authentication ******************** */

/**
 * Which provider would be used for the given challenge,
 * and at what cost?
 */
struct PolicyEntry
{
  /**
   * URL of the provider.
   */
  const char *provider_name;

  /**
   * Recovery fee.
   */
  struct Costs *usage_fee;
};


/**
 * Map from challenges to providers.
 */
struct PolicyMap
{
  /**
   * Kept in a DLL.
   */
  struct PolicyMap *next;

  /**
   * Kept in a DLL.
   */
  struct PolicyMap *prev;

  /**
   * Array of proividers selected for each challenge,
   * with associated costs.
   * Length of the array will be 'req_methods'.
   */
  struct PolicyEntry *providers;

  /**
   * Diversity score for this policy mapping.
   */
  unsigned int diversity;

};


/**
 * Array of challenges for a policy, and DLL with
 * possible mappings of challenges to providers.
 */
struct Policy
{

  /**
   * Kept in DLL of all possible policies.
   */
  struct Policy *next;

  /**
   * Kept in DLL of all possible policies.
   */
  struct Policy *prev;

  /**
   * Head of DLL.
   */
  struct PolicyMap *pm_head;

  /**
   * Tail of DLL.
   */
  struct PolicyMap *pm_tail;

  /**
   * Challenges selected for this policy.
   * Length of the array will be 'req_methods'.
   */
  unsigned int *challenges;

};


/**
 * Information for running done_authentication() logic.
 */
struct PolicyBuilder
{
  /**
   * State we are working on; provides the authentication providers
   * available overall.
   */
  const struct ANASTASIS_ReduxCommon *common;

  /**
   * Backup state we are working on; provides the authentication
   * methods the user entered, and receives the computed policies.
   */
  struct ANASTASIS_ReduxBackup *backup;

  /**
   * Head of DLL of all possible policies.
   */
  struct Policy *p_head;

  /**
   * Tail of DLL of all possible policies.
   */
  struct Policy *p_tail;

  /**
   * Array of length @e req_methods.
   */
  unsigned int *m_idx;

  /**
   * Array of length @e req_methods identifying a set of providers selected
   * for each authentication method, while we are trying to compute the
   * 'best' allocation of providers to authentication methods.
   * Only valid during the go_with() function.
   */
  const char **best_sel;

  /**
   * Error hint to return on failure. Set if @e ec is not #TALER_EC_NONE.
   */
  const char *hint;

  /**
   * Policy we are currently building maps for.
   */
  struct Policy *current_policy;

  /**
   * LL of costs associated with the currently preferred
   * policy.
   */
  struct Costs *best_cost;

  /**
   * Array of 'best' policy maps found so far,
   * ordered by policy.
   */
  struct PolicyMap *best_map;

  /**
   * Array of the currency policy maps under evaluation
   * by find_best_map().
   */
  struct PolicyMap *curr_map;

  /**
   * How many mappings have we evaluated so far?
   * Used to limit the computation by aborting after
   * #MAX_EVALUATIONS trials.
   */
  unsigned int evaluations;

  /**
   * Overall number of challenges provided by the user.
   */
  unsigned int num_methods;

  /**
   * Number of challenges that must be satisfied to recover the secret.
   * Derived from the total number of challenges entered by the user.
   */
  unsigned int req_methods;

  /**
   * Number of different Anastasis providers selected in @e best_sel.
   * Only valid during the go_with() function.
   */
  unsigned int best_diversity;

  /**
   * Number of identical challenges duplicated at
   * various providers in the best case. Smaller is
   * better.
   */
  unsigned int best_duplicates;

  /**
   * Error code to return, #TALER_EC_NONE on success.
   */
  enum TALER_ErrorCode ec;

};


/**
 * Free @a costs LL.
 *
 * @param[in] costs linked list to free
 */
static void
free_costs (struct Costs *costs)
{
  while (NULL != costs)
  {
    struct Costs *next = costs->next;

    GNUNET_free (costs);
    costs = next;
  }
}


/**
 * Check if providers @a p1 and @a p2 have equivalent
 * methods and cost structures.
 *
 * @param pb policy builder with list of providers
 * @param p1 name of provider to compare
 * @param p2 name of provider to compare
 * @return true if the providers are fully equivalent
 */
static bool
equiv_provider (const struct PolicyBuilder *pb,
                const char *p1,
                const char *p2)
{
  const struct ANASTASIS_ReduxProvider *j1;
  const struct ANASTASIS_ReduxProvider *j2;
  const struct ANASTASIS_ReduxProviderConfig *c1;
  const struct ANASTASIS_ReduxProviderConfig *c2;

  j1 = ANASTASIS_REDUX_provider_find_ (pb->common,
                                       p1);
  j2 = ANASTASIS_REDUX_provider_find_ (pb->common,
                                       p2);
  if ( (NULL == j1) ||
       (NULL == j2) ||
       (! j1->have_config) ||
       (! j2->have_config) )
  {
    GNUNET_break (0);
    return false;
  }
  c1 = &j1->config;
  c2 = &j2->config;
  /* Compare the whole price lists, not just the primary currency: two
     providers that agree on EUR but differ on which other currencies
     they take are not interchangeable for a user holding one of them. */
  if (! amount_lists_equal (&c1->truth_upload_fees,
                            &c2->truth_upload_fees))
    return false;

  if (c1->methods_len != c2->methods_len)
    return false;
  for (unsigned int i = 0; i < c1->methods_len; i++)
  {
    const struct ANASTASIS_ReduxMethodSpec *m1 = &c1->methods[i];
    bool matched = false;

    for (unsigned int j = 0; j < c2->methods_len; j++)
    {
      const struct ANASTASIS_ReduxMethodSpec *m2 = &c2->methods[j];

      if ( (0 == strcmp (m1->type,
                         m2->type)) &&
           amount_lists_equal (&m1->usage_fees,
                               &m2->usage_fees) )
      {
        matched = true;
        break;
      }
    }
    if (! matched)
      return false;
  }
  return true;
}


/**
 * Evaluate the cost/benefit of the provider selection in @a prov_sel
 * and if it is better then the best known one in @a pb, update @a pb.
 *
 * @param[in,out] pb our operational context
 * @param[in,out] prov_sel array of req_methods provider indices to complete
 */
static void
eval_provider_selection (struct PolicyBuilder *pb,
                         const char *prov_sel[])
{
  unsigned int curr_diversity;
  struct PolicyEntry policy_ent[pb->req_methods];

  memset (policy_ent,
          0,
          sizeof (policy_ent));
  for (unsigned int i = 0; i < pb->req_methods; i++)
  {
    const struct ANASTASIS_ReduxAuthMethod *am
      = &pb->backup->authentication_methods[pb->m_idx[i]];
    const struct ANASTASIS_ReduxProvider *p;
    const struct ANASTASIS_ReduxProviderConfig *cfg;
    bool found = false;

    policy_ent[i].provider_name = prov_sel[i];
    p = ANASTASIS_REDUX_provider_find_ (pb->common,
                                        prov_sel[i]);
    if ( (NULL == p) ||
         (! p->have_config) ||
         (MHD_HTTP_OK != p->config.http_status) )
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Skipping provider %s: no suitable configuration\n",
                  prov_sel[i]);
      goto cleanup;
    }
    cfg = &p->config;
    for (unsigned int j = 0; j < cfg->methods_len; j++)
    {
      const struct ANASTASIS_ReduxMethodSpec *ms = &cfg->methods[j];

      if ( (0 == strcmp (ms->type,
                         am->type)) &&
           (challenge_size_ok (cfg->storage_limit_in_megabytes,
                               am->challenge_size) ) )
      {
        const struct TALER_Amount *uf;
        const struct TALER_Amount *tf;

        found = true;
        uf = preferred_price (pb->common,
                              &ms->usage_fees);
        tf = preferred_price (pb->common,
                              &cfg->truth_upload_fees);
        if (NULL != uf)
          add_cost (&policy_ent[i].usage_fee,
                    uf);
        if (NULL != tf)
          add_cost (&policy_ent[i].usage_fee,
                    tf);
      }
    }
    if (! found)
    {
      /* Provider does not OFFER this method, combination not possible.
         Cost is basically 'infinite', but we simply then skip this. */
      goto cleanup;
    }
  }

  /* calculate provider diversity by counting number of different
     providers selected */
  curr_diversity = 0;
  for (unsigned int i = 0; i < pb->req_methods; i++)
  {
    bool found = false;

    for (unsigned int j = 0; j < i; j++)
    {
      if (prov_sel[i] == prov_sel[j])
      {
        found = true;
        break;
      }
    }
    if (! found)
      curr_diversity++;
  }
#if DEBUG
  fprintf (stderr,
           "Diversity: %u (best: %u)\n",
           curr_diversity,
           pb->best_diversity);
#endif
  if (curr_diversity < pb->best_diversity)
  {
    /* do not allow combinations that are bad
       for provider diversity */
    goto cleanup;
  }
  if (curr_diversity > pb->best_diversity)
  {
    /* drop existing policies, they are all worse */
    struct PolicyMap *m;

    while (NULL != (m = pb->current_policy->pm_head))
    {
      GNUNET_CONTAINER_DLL_remove (pb->current_policy->pm_head,
                                   pb->current_policy->pm_tail,
                                   m);
      for (unsigned int i = 0; i<pb->req_methods; i++)
      {
        free_costs (m->providers[i].usage_fee);
        m->providers[i].usage_fee = NULL;
      }
      GNUNET_free (m->providers);
      GNUNET_free (m);
    }
    pb->best_diversity = curr_diversity;
  }
  if (NULL == pb->p_head)
  {
    /* For the first policy, check for equivalent
       policy mapping existing: we
     do not want to do spend CPU time investigating
     purely equivalent permutations */
    for (struct PolicyMap *m = pb->current_policy->pm_head;
         NULL != m;
         m = m->next)
    {
      bool equiv = true;
      for (unsigned int i = 0; i<pb->req_methods; i++)
      {
        if (! equiv_provider (pb,
                              m->providers[i].provider_name,
                              policy_ent[i].provider_name))
        {
          equiv = false;
          break;
        }
      }
      if (equiv)
      {
        /* equivalent to known allocation */
        goto cleanup;
      }
    }
  }

  /* Add possible mapping to result list */
  {
    struct PolicyMap *m;

    m = GNUNET_new (struct PolicyMap);
    m->providers = GNUNET_new_array (pb->req_methods,
                                     struct PolicyEntry);
    memcpy (m->providers,
            policy_ent,
            sizeof (struct PolicyEntry) * pb->req_methods);
    m->diversity = curr_diversity;
    GNUNET_CONTAINER_DLL_insert (pb->current_policy->pm_head,
                                 pb->current_policy->pm_tail,
                                 m);
  }
  return;
cleanup:
  for (unsigned int i = 0; i<pb->req_methods; i++)
    free_costs (policy_ent[i].usage_fee);
}


/**
 * Recursively compute possible combination(s) of provider candidates
 * in @e prov_sel. The selection is complete up to index @a i.  Calls
 * eval_provider_selection() upon a feasible provider selection for
 * evaluation, resulting in "better" combinations being persisted in
 * @a pb.
 *
 * @param[in,out] pb our operational context
 * @param[in,out] prov_sel array of req_methods provider URLs to complete
 * @param i index up to which @a prov_sel is already initialized
 */
static void
provider_candidate (struct PolicyBuilder *pb,
                    const char *prov_sel[],
                    unsigned int i)
{
  for (unsigned int k = 0; k < pb->common->providers_len; k++)
  {
    const struct ANASTASIS_ReduxProvider *p = &pb->common->providers[k];

    if ( (ANASTASIS_RPS_DISABLED == p->status) ||
         (! p->have_config) ||
         (MHD_HTTP_OK != p->config.http_status) )
      continue;
    prov_sel[i] = p->url.url;
    if (i == pb->req_methods - 1)
    {
      eval_provider_selection (pb,
                               prov_sel);
      if (TALER_EC_NONE != pb->ec)
        break;
      continue;
    }
    provider_candidate (pb,
                        prov_sel,
                        i + 1);
  }
}


/**
 * Using the selection of authentication methods from @a pb in
 * "m_idx", compute the best choice of providers.
 *
 * @param[in,out] pb our operational context
 */
static void
go_with (struct PolicyBuilder *pb)
{
  const char *prov_sel[pb->req_methods];
  struct Policy *policy;

  /* compute provider selection */
  policy = GNUNET_new (struct Policy);
  policy->challenges = GNUNET_new_array (pb->req_methods,
                                         unsigned int);
  memcpy (policy->challenges,
          pb->m_idx,
          pb->req_methods * sizeof (unsigned int));
  pb->current_policy = policy;
  pb->best_diversity = 0;
  provider_candidate (pb,
                      prov_sel,
                      0);
  GNUNET_CONTAINER_DLL_insert (pb->p_head,
                               pb->p_tail,
                               policy);
  pb->current_policy = NULL;
}


/**
 * Recursively computes all possible subsets of length "req_methods"
 * from an array of length "num_methods", calling "go_with" on each of
 * those subsets (in "m_idx").
 *
 * @param[in,out] pb our operational context
 * @param i offset up to which the "m_idx" has been computed
 */
static void
method_candidate (struct PolicyBuilder *pb,
                  unsigned int i)
{
  unsigned int start;
  unsigned int *m_idx = pb->m_idx;

  start = (i > 0) ? m_idx[i - 1] + 1 : 0;
  for (unsigned int j = start; j < pb->num_methods; j++)
  {
    m_idx[i] = j;
    if (i == pb->req_methods - 1)
    {
#if DEBUG
      fprintf (stderr,
               "Suggesting: ");
      for (unsigned int k = 0; k<pb->req_methods; k++)
      {
        fprintf (stderr,
                 "%u ",
                 m_idx[k]);
      }
      fprintf (stderr, "\n");
#endif
      go_with (pb);
      continue;
    }
    method_candidate (pb,
                      i + 1);
  }
}


/**
 * Compare two cost lists.
 *
 * @param my cost to compare
 * @param be cost to compare
 * @return 0 if costs are estimated equal,
 *         1 if @a my < @a be
 *        -1 if @a my > @a be
 */
static int
compare_costs (const struct Costs *my,
               const struct Costs *be)
{
  int ranking = 0;

  for (const struct Costs *cmp = be;
       NULL != cmp;
       cmp = cmp->next)
  {
    bool found = false;

    for (const struct Costs *pos = my;
         NULL != pos;
         pos = pos->next)
    {
      if (GNUNET_OK !=
          TALER_amount_cmp_currency (&cmp->cost,
                                     &pos->cost))
        continue;
      found = true;
    }
    if (! found)
      ranking--;   /* new policy has no cost in this currency */
  }

  for (const struct Costs *pos = my;
       NULL != pos;
       pos = pos->next)
  {
    bool found = false;

    for (const struct Costs *cmp = be;
         NULL != cmp;
         cmp = cmp->next)
    {
      if (GNUNET_OK !=
          TALER_amount_cmp_currency (&cmp->cost,
                                     &pos->cost))
        continue;
      found = true;
      switch (TALER_amount_cmp (&cmp->cost,
                                &pos->cost))
      {
      case -1:   /* cmp < pos */
        ranking--;
        break;
      case 0:
        break;
      case 1:   /* cmp > pos */
        ranking++;
        break;
      }
      break;
    }
    if (! found)
      ranking++;   /* old policy has no cost in this currency */
  }
  if (0 == ranking)
    return 0;
  return (0 > ranking) ? -1 : 1;
}


/**
 * Evaluate the combined policy map stack in the ``curr_map`` of @a pb
 * and compare to the current best cost. If we are better, save the
 * stack in the ``best_map``.
 *
 * @param[in,out] pb policy builder we evaluate for
 * @param num_policies length of the ``curr_map`` array
 */
static void
evaluate_map (struct PolicyBuilder *pb,
              unsigned int num_policies)
{
  struct Costs *my_cost = NULL;
  unsigned int i = 0;
  unsigned int duplicates = 0;
  int ccmp;

#if DEBUG
  fprintf (stderr,
           "Checking...\n");
#endif
  /* calculate cost */
  for (const struct Policy *p = pb->p_head;
       NULL != p;
       p = p->next)
  {
    const struct PolicyMap *pm = &pb->curr_map[i++];

#if DEBUG
    fprintf (stderr,
             "Evaluating %p (%u): ",
             p,
             pm->diversity);
    for (unsigned int k = 0; k<pb->req_methods; k++)
    {
      const struct PolicyEntry *pe = &pm->providers[k];

      fprintf (stderr,
               "%u->%s ",
               p->challenges[k],
               pe->provider_name);
    }
    fprintf (stderr, "\n");
#endif
    for (unsigned int j = 0; j<pb->req_methods; j++)
    {
      const struct PolicyEntry *pe = &pm->providers[j];
      unsigned int cv = p->challenges[j];
      bool found = false;
      unsigned int i2 = 0;

      /* check for duplicates */
      for (const struct Policy *p2 = pb->p_head;
           p2 != p;
           p2 = p2->next)
      {
        const struct PolicyMap *pm2 = &pb->curr_map[i2++];

        for (unsigned int j2 = 0; j2<pb->req_methods; j2++)
        {
          const struct PolicyEntry *pe2 = &pm2->providers[j2];
          unsigned int cv2 = p2->challenges[j2];

          if (cv != cv2)
            continue; /* different challenge */
          if (0 == strcmp (pe->provider_name,
                           pe2->provider_name))
            found = true; /* same challenge&provider! */
          else
            duplicates++; /* penalty for same challenge at two providers */
        }
      }
      if (! found)
      {
        add_costs (&my_cost,
                   pe->usage_fee);
      }
    }
  }

  ccmp = -1; /* non-zero if 'best_duplicates' is UINT_MAX */
  if ( (UINT_MAX != pb->best_duplicates) &&
       (0 > (ccmp = compare_costs (my_cost,
                                   pb->best_cost))) )
  {
    /* new method not clearly better, do not use it */
    free_costs (my_cost);
#if DEBUG
    fprintf (stderr,
             "... useless\n");
#endif
    return;
  }
  if ( (0 == ccmp) &&
       (duplicates > pb->best_duplicates) )
  {
    /* new method is cost-equal, but looses on duplicates,
       do not use it */
    free_costs (my_cost);
#if DEBUG
    fprintf (stderr,
             "... useless\n");
#endif
    return;
  }
  /* new method is better (or first), set as best */
#if DEBUG
  fprintf (stderr,
           "New best: %u duplicates, %s cost\n",
           duplicates,
           TALER_amount2s (&my_cost->cost));
#endif
  free_costs (pb->best_cost);
  pb->best_cost = my_cost;
  pb->best_duplicates = duplicates;
  memcpy (pb->best_map,
          pb->curr_map,
          sizeof (struct PolicyMap) * num_policies);
}


/**
 * Try all policy maps for @a pos and evaluate the
 * resulting total cost, saving the best result in
 * @a pb.
 *
 * @param[in,out] pb policy builder context
 * @param pos policy we are currently looking at maps for
 * @param off index of @a pos for the policy map
 */
static void
find_best_map (struct PolicyBuilder *pb,
               struct Policy *pos,
               unsigned int off)
{
  if (NULL == pos)
  {
    evaluate_map (pb,
                  off);
    pb->evaluations++;
    return;
  }
  for (struct PolicyMap *pm = pos->pm_head;
       NULL != pm;
       pm = pm->next)
  {
    pb->curr_map[off] = *pm;
    find_best_map (pb,
                   pos->next,
                   off + 1);
    if (pb->evaluations >= MAX_EVALUATIONS)
      break;
  }
}


/**
 * Select cheapest policy combinations and store them in the ``policies``
 * of the backup state @a pb builds.
 *
 * @param[in,out] pb policy builder with our state
 */
static void
select_policies (struct PolicyBuilder *pb)
{
  unsigned int cnt = 0;

  for (struct Policy *p = pb->p_head;
       NULL != p;
       p = p->next)
    cnt++;
  {
    struct PolicyMap best[cnt];
    struct PolicyMap curr[cnt];
    unsigned int off;

    pb->best_map = best;
    pb->curr_map = curr;
    pb->best_duplicates = UINT_MAX; /* worst */
    find_best_map (pb,
                   pb->p_head,
                   0);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Assessed %u/%u policies\n",
                pb->evaluations,
                (unsigned int) MAX_EVALUATIONS);
    off = 0;
    for (struct Policy *p = pb->p_head;
         NULL != p;
         p = p->next)
    {
      struct PolicyMap *pm = &best[off++];
      struct ANASTASIS_ReduxPolicy pol = {
        .methods_len = pb->req_methods,
        .methods = GNUNET_new_array (pb->req_methods,
                                     struct ANASTASIS_ReduxPolicyMethod)
      };

#if DEBUG
      fprintf (stderr,
               "Best map (%u): ",
               pm->diversity);
      for (unsigned int k = 0; k<pb->req_methods; k++)
      {
        fprintf (stderr,
                 "%u->%s ",
                 p->challenges[k],
                 pm->providers[k].provider_name);
      }
      fprintf (stderr, "\n");
#endif
      /* Convert "best" selection into the 'policies' array */
      for (unsigned int i = 0; i < pb->req_methods; i++)
      {
        struct ANASTASIS_ReduxPolicyMethod *pm2 = &pol.methods[i];

        pm2->authentication_method.idx = p->challenges[i];
        ANASTASIS_REDUX_provider_url_set_ (&pm2->provider,
                                           pm->providers[i].provider_name);
      }
      GNUNET_array_append (pb->backup->policies,
                           pb->backup->policies_len,
                           pol);
      pb->backup->have_policies = true;
    }
  }
}


/**
 * Clean up @a pb, in particular the policies DLL.
 *
 * @param[in] pb builder to clean up
 */
static void
clean_pb (struct PolicyBuilder *pb)
{
  struct Policy *p;

  while (NULL != (p = pb->p_head))
  {
    struct PolicyMap *pm;

    while (NULL != (pm = p->pm_head))
    {
      GNUNET_CONTAINER_DLL_remove (p->pm_head,
                                   p->pm_tail,
                                   pm);
      for (unsigned int i = 0; i<pb->req_methods; i++)
        free_costs (pm->providers[i].usage_fee);
      GNUNET_free (pm->providers);
      GNUNET_free (pm);
    }
    GNUNET_CONTAINER_DLL_remove (pb->p_head,
                                 pb->p_tail,
                                 p);
    GNUNET_free (p->challenges);
    GNUNET_free (p);
  }
  free_costs (pb->best_cost);
}


/**
 * DispatchHandler/Callback function which is called for a
 * "done_authentication" action.  Automaticially computes policies
 * based on available Anastasis providers and challenges provided by
 * the user.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
done_authentication (struct ANASTASIS_ReduxState *rs,
                     const json_t *arguments,
                     ANASTASIS_ActionCallback cb,
                     void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  struct PolicyBuilder pb = {
    .ec = TALER_EC_NONE,
    .common = &rs->common,
    .backup = b
  };
  const json_t *providers;

  if (! rs->common.have_providers)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'authentication_providers' must be provided");
    return NULL;
  }
  if (! b->have_authentication_methods)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'authentication_methods' must be provided");
    return NULL;
  }
  pb.num_methods = b->authentication_methods_len;
  switch (pb.num_methods)
  {
  case 0:
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'authentication_methods' must not be empty");
    return NULL;
  case 1:
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "Two factor authentication (2-FA) is required");
    return NULL;
  case 2:
    pb.req_methods = pb.num_methods;
    break;
  case 3:
  case 4:
    pb.req_methods = pb.num_methods - 1;
    break;
  case 5:
  case 6:
    pb.req_methods = pb.num_methods - 2;
    break;
  case 7:
    pb.req_methods = pb.num_methods - 3;
    break;
  default:
    /* cap at 4 for auto-generation, algorithm
       to compute mapping gets too expensive
       otherwise. */
    pb.req_methods = 4;
    break;
  }
  {
    unsigned int m_idx[pb.req_methods];

    /* select req_methods from num_methods. */
    pb.m_idx = m_idx;
    method_candidate (&pb,
                      0);
  }
  /* the computed policies replace whatever was there before */
  ANASTASIS_REDUX_policies_clear_ (b);
  b->have_policies = true;
  select_policies (&pb);
  clean_pb (&pb);
  if (TALER_EC_NONE != pb.ec)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           pb.ec,
                           pb.hint);
    return NULL;
  }
  ANASTASIS_REDUX_policy_providers_clear_ (b);
  b->have_policy_providers = true;
  providers = json_object_get (arguments,
                               "providers");
  if (NULL == providers)
  {
    /* Setup a providers array from all working providers */
    for (unsigned int i = 0; i < rs->common.providers_len; i++)
    {
      const char *url = rs->common.providers[i].url.url;
      struct ANASTASIS_ReduxPolicyProvider pp = { 0 };
      struct ANASTASIS_CRYPTO_ProviderSaltP salt;

      if (GNUNET_OK !=
          ANASTASIS_REDUX_lookup_salt_ (&rs->common,
                                        url,
                                        &salt))
        continue; /* skip providers that are down */
      ANASTASIS_REDUX_provider_url_set_ (&pp.provider_url,
                                         url);
      GNUNET_array_append (b->policy_providers,
                           b->policy_providers_len,
                           pp);
    }
  }
  else
  {
    /* Setup a providers array from the requested providers */
    size_t off;
    json_t *url;

    json_array_foreach (providers, off, url)
    {
      struct ANASTASIS_ReduxPolicyProvider pp = { 0 };
      struct ANASTASIS_CRYPTO_ProviderSaltP salt;
      const char *url_str;

      url_str = json_string_value (url);
      if ( (NULL == url_str) ||
           (GNUNET_OK !=
            ANASTASIS_REDUX_lookup_salt_ (&rs->common,
                                          url_str,
                                          &salt)) )
      {
        GNUNET_break (0);
        ANASTASIS_REDUX_fail_ (rs,
                               cb,
                               cb_cls,
                               TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                               "unworkable provider requested");
        return NULL;
      }
      ANASTASIS_REDUX_provider_url_set_ (&pp.provider_url,
                                         url_str);
      GNUNET_array_append (b->policy_providers,
                           b->policy_providers_len,
                           pp);
    }
  }
  if (0 == b->policy_providers_len)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "no workable providers in state");
    return NULL;
  }
  set_state (rs,
             ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING);
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/* ******************** add_provider ******************* */


/**
 * DispatchHandler/Callback function which is called for a
 * "add_provider" action.  Adds another Anastasis provider
 * to the list of available providers for storing information.
 *
 * @param state state to operate on
 * @param arguments arguments with a provider URL to add
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 */
static struct ANASTASIS_ReduxAction *
add_provider (struct ANASTASIS_ReduxState *rs,
              const json_t *arguments,
              ANASTASIS_ActionCallback cb,
              void *cb_cls)
{
  if (ANASTASIS_add_provider_ (rs,
                               arguments,
                               cb,
                               cb_cls))
    return NULL;
  return ANASTASIS_REDUX_backup_begin_ (rs,
                                        NULL,
                                        cb,
                                        cb_cls);
}


/* ******************** add_policy ******************* */


/**
 * DispatchHandler/Callback function which is called for a
 * "add_policy" action.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
add_policy (struct ANASTASIS_ReduxState *rs,
            const json_t *arguments,
            ANASTASIS_ActionCallback cb,
            void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  const json_t *arg_array;
  struct ANASTASIS_ReduxPolicy pol = { 0 };

  if (NULL == arguments)
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "arguments missing");
    return NULL;
  }
  arg_array = json_object_get (arguments,
                               "policy");
  if (! json_is_array (arg_array))
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'policy' not an array");
    return NULL;
  }
  if (! b->have_policies)
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'policies' not an array");
    return NULL;
  }
  if (! rs->common.have_providers)
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'auth_providers' not an object");
    return NULL;
  }
  if (! b->have_authentication_methods)
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'auth_methods' not an array");
    return NULL;
  }

  /* Add all methods from 'arg_array' to the new policy */
  {
    size_t aindex;
    json_t *method;

    json_array_foreach (arg_array, aindex, method)
    {
      const char *provider_url;
      uint32_t method_idx;
      const char *method_type;
      const struct ANASTASIS_ReduxProvider *p;
      const struct ANASTASIS_ReduxProviderConfig *cfg;
      struct GNUNET_JSON_Specification ispec[] = {
        GNUNET_JSON_spec_string ("provider",
                                 &provider_url),
        GNUNET_JSON_spec_uint32 ("authentication_method",
                                 &method_idx),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (method,
                             ispec,
                             NULL, NULL))
      {
        GNUNET_break (0);
        ANASTASIS_REDUX_policy_clear_ (&pol);
        ANASTASIS_REDUX_fail_ (rs,
                               cb,
                               cb_cls,
                               TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                               "'method' details malformed");
        return NULL;
      }

      p = ANASTASIS_REDUX_provider_find_ (&rs->common,
                                          provider_url);
      if (NULL == p)
      {
        GNUNET_break (0);
        ANASTASIS_REDUX_policy_clear_ (&pol);
        ANASTASIS_REDUX_fail_ (rs,
                               cb,
                               cb_cls,
                               TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                               "provider URL unknown");
        return NULL;
      }
      if ( (! p->have_config) ||
           (ANASTASIS_RPS_OK != p->status) ||
           (MHD_HTTP_OK != p->config.http_status) )
        continue; /* skip provider, disabled or down */
      cfg = &p->config;

      if (method_idx >= b->authentication_methods_len)
      {
        GNUNET_break (0);
        ANASTASIS_REDUX_policy_clear_ (&pol);
        ANASTASIS_REDUX_fail_ (rs,
                               cb,
                               cb_cls,
                               TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                               "authentication method unknown");
        return NULL;
      }
      method_type = b->authentication_methods[method_idx].type;

      {
        bool found = false;

        for (unsigned int j = 0; j < cfg->methods_len; j++)
          if (0 == strcmp (cfg->methods[j].type,
                           method_type))
          {
            found = true;
            break;
          }
        if (! found)
        {
          GNUNET_break (0);
          ANASTASIS_REDUX_policy_clear_ (&pol);
          ANASTASIS_REDUX_fail_ (
            rs,
            cb,
            cb_cls,
            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
            "selected provider does not support authentication method");
          return NULL;
        }
      }
      {
        struct ANASTASIS_ReduxPolicyMethod pm = {
          .authentication_method.idx = method_idx
        };

        ANASTASIS_REDUX_provider_url_set_ (&pm.provider,
                                           provider_url);
        GNUNET_array_append (pol.methods,
                             pol.methods_len,
                             pm);
      }
    } /* end of json_array_foreach (arg_array, mindex, method) */
  }

  /* add new policy to array of existing policies */
  {
    const json_t *idx;
    unsigned int at;

    idx = json_object_get (arguments,
                           "policy_index");
    if ( (NULL == idx) ||
         (! json_is_integer (idx)) )
      at = b->policies_len;
    else
      at = GNUNET_MIN ((unsigned int) json_integer_value (idx),
                       b->policies_len);
    GNUNET_array_append (b->policies,
                         b->policies_len,
                         pol);
    if (at != b->policies_len - 1)
    {
      /* shift the tail one to the right and drop the new entry in */
      memmove (&b->policies[at + 1],
               &b->policies[at],
               sizeof (struct ANASTASIS_ReduxPolicy)
               * (b->policies_len - at - 1));
      b->policies[at] = pol;
    }
  }

  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/* ******************** update_policy ******************* */


/**
 * DispatchHandler/Callback function which is called for a
 * "update_policy" action.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
update_policy (struct ANASTASIS_ReduxState *rs,
               const json_t *arguments,
               ANASTASIS_ActionCallback cb,
               void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  const json_t *idx;
  json_int_t index;

  if (NULL == arguments)
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "arguments missing");
    return NULL;
  }
  idx = json_object_get (arguments,
                         "policy_index");
  if (! json_is_integer (idx))
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'policy_index' must be an integer");
    return NULL;
  }
  index = json_integer_value (idx);
  if (! b->have_policies)
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'policies' must be an array");
    return NULL;
  }
  if ( (index < 0) ||
       (index >= (json_int_t) b->policies_len) )
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
                           "removal failed");
    return NULL;
  }
  ANASTASIS_REDUX_policy_clear_ (&b->policies[index]);
  memmove (&b->policies[index],
           &b->policies[index + 1],
           sizeof (struct ANASTASIS_ReduxPolicy)
           * (b->policies_len - index - 1));
  b->policies_len--;
  return add_policy (rs,
                     arguments,
                     cb,
                     cb_cls);
}


/* ******************** del_policy ******************* */


/**
 * DispatchHandler/Callback function which is called for a
 * "delete_policy" action.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
del_policy (struct ANASTASIS_ReduxState *rs,
            const json_t *arguments,
            ANASTASIS_ActionCallback cb,
            void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  const json_t *idx;
  json_int_t index;

  if (NULL == arguments)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "arguments missing");
    return NULL;
  }
  idx = json_object_get (arguments,
                         "policy_index");
  if (! json_is_integer (idx))
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'policy_index' must be an integer");
    return NULL;
  }
  index = json_integer_value (idx);
  if (! b->have_policies)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'policies' must be an array");
    return NULL;
  }
  if ( (index < 0) ||
       (index >= (json_int_t) b->policies_len) )
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
                           "removal failed");
    return NULL;
  }
  ANASTASIS_REDUX_policy_clear_ (&b->policies[index]);
  memmove (&b->policies[index],
           &b->policies[index + 1],
           sizeof (struct ANASTASIS_ReduxPolicy)
           * (b->policies_len - index - 1));
  b->policies_len--;
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/* ******************** del_challenge ******************* */


/**
 * DispatchHandler/Callback function which is called for a
 * "delete_challenge" action.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
del_challenge (struct ANASTASIS_ReduxState *rs,
               const json_t *arguments,
               ANASTASIS_ActionCallback cb,
               void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  const json_t *pidx;
  const json_t *cidx;
  json_int_t index;
  struct ANASTASIS_ReduxPolicy *policy;

  if (NULL == arguments)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "arguments missing");
    return NULL;
  }
  pidx = json_object_get (arguments,
                          "policy_index");
  cidx = json_object_get (arguments,
                          "challenge_index");
  if (! json_is_integer (pidx))
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'policy_index' must be an integer");
    return NULL;
  }
  if (! json_is_integer (cidx))
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'challenge_index' must be an integer");
    return NULL;
  }
  index = json_integer_value (pidx);
  if (! b->have_policies)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'policies' must be an array");
    return NULL;
  }
  if ( (index < 0) ||
       (index >= (json_int_t) b->policies_len) )
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'policy_index' out of range");
    return NULL;
  }
  policy = &b->policies[index];
  index = json_integer_value (cidx);
  if ( (index < 0) ||
       (index >= (json_int_t) policy->methods_len) )
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
                           "removal failed");
    return NULL;
  }
  {
    struct ANASTASIS_ReduxPolicyMethod *pm = &policy->methods[index];

    ANASTASIS_REDUX_provider_url_clear_ (&pm->provider);
    if (NULL != pm->truth)
      ANASTASIS_truth_free (pm->truth);
    memmove (pm,
             pm + 1,
             sizeof (*pm) * (policy->methods_len - index - 1));
    policy->methods_len--;
  }
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/* ********************** done_policy_review ***************** */


/**
 * Calculate how many years of service we need
 * from the desired @a expiration time,
 * rounding up.
 *
 * @param expiration desired expiration time
 * @return number of years of service to pay for
*/
static unsigned int
expiration_to_years (struct GNUNET_TIME_Timestamp expiration)
{
  struct GNUNET_TIME_Relative rem;
  unsigned int years;

  rem = GNUNET_TIME_absolute_get_remaining (expiration.abs_time);
  years = rem.rel_value_us / GNUNET_TIME_UNIT_YEARS.rel_value_us;
  if (0 != rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us)
    years++;
  return years;
}


/**
 * Update @a state such that the earliest expiration for
 * any truth or policy is @a expiration. Recalculate
 * the ``upload_fees`` array with the associated costs.
 *
 * @param[in,out] state our state to update
 * @param expiration new expiration to enforce
 * @return #GNUNET_OK on success,
 *         #GNUNET_SYSERR if the state is invalid
 */
static enum GNUNET_GenericReturnValue
update_expiration_cost (struct ANASTASIS_ReduxState *rs,
                        struct GNUNET_TIME_Timestamp expiration)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  struct Costs *costs = NULL;
  unsigned int years;
  bool is_free = true;

  if (! rs->common.have_providers)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }

  years = expiration_to_years (expiration);

  /* Publish the currencies in which the *whole* backup can be settled
     with one choice of currency, i.e. the intersection over the
     providers that will actually be used.  An empty intersection is not
     an error: it means the user will pay more than one order in more
     than one currency, which is exactly what `upload_fees' then shows. */
  {
    bool first = true;

    for (unsigned int i = 0; i < rs->common.currencies_len; i++)
      GNUNET_free (rs->common.currencies[i]);
    GNUNET_array_grow (rs->common.currencies,
                       rs->common.currencies_len,
                       0);
    rs->common.have_currencies = true;
    for (unsigned int i = 0; i < rs->common.providers_len; i++)
    {
      const struct ANASTASIS_ReduxProvider *p = &rs->common.providers[i];

      if ( (ANASTASIS_RPS_OK != p->status) ||
           (! p->have_config) ||
           (MHD_HTTP_OK != p->config.http_status) )
        continue;
      if (first)
      {
        for (unsigned int j = 0; j < p->config.currencies_len; j++)
          GNUNET_array_append (rs->common.currencies,
                               rs->common.currencies_len,
                               GNUNET_strdup (p->config.currencies[j]));
        first = false;
        continue;
      }
      for (unsigned int j = 0; j < rs->common.currencies_len;)
      {
        bool found = false;

        for (unsigned int k = 0; k < p->config.currencies_len; k++)
          if (0 == strcasecmp (rs->common.currencies[j],
                               p->config.currencies[k]))
            found = true;
        if (found)
        {
          j++;
          continue;
        }
        GNUNET_free (rs->common.currencies[j]);
        rs->common.currencies[j]
          = rs->common.currencies[rs->common.currencies_len - 1];
        GNUNET_array_grow (rs->common.currencies,
                           rs->common.currencies_len,
                           rs->common.currencies_len - 1);
      }
    }
  }

  /* go over all providers and add up cost */
  for (unsigned int i = 0; i < rs->common.providers_len; i++)
  {
    const struct ANASTASIS_ReduxProvider *p = &rs->common.providers[i];
    const struct TALER_Amount *af;
    struct TALER_Amount fee;

    if ( (ANASTASIS_RPS_OK != p->status) ||
         (! p->have_config) ||
         (MHD_HTTP_OK != p->config.http_status) )
      continue; /* skip providers that are down or disabled */
    af = preferred_price (&rs->common,
                          &p->config.annual_fees);
    if (NULL == af)
      continue; /* provider is free */
    if (0 >
        TALER_amount_multiply (&fee,
                               af,
                               years))
    {
      GNUNET_break (0);
      free_costs (costs);
      return GNUNET_SYSERR;
    }
    add_cost (&costs,
              &fee);
  }

  /* go over all truths and add up cost */
  {
    unsigned int off = 0;
    unsigned int len = 0;
    struct AlreadySeen
    {
      unsigned int method;
      const char *provider_url;
    } *seen = NULL;

    for (unsigned int pidx = 0; pidx < b->policies_len; pidx++)
    {
      const struct ANASTASIS_ReduxPolicy *policy = &b->policies[pidx];

      for (unsigned int midx = 0; midx < policy->methods_len; midx++)
      {
        const struct ANASTASIS_ReduxPolicyMethod *pm = &policy->methods[midx];
        const char *provider_url = pm->provider.url;
        unsigned int method_idx = pm->authentication_method.idx;
        const struct ANASTASIS_ReduxProvider *p;

        /* check if we have seen this one before */
        {
          bool found = false;

          for (unsigned int i = 0; i<off; i++)
            if ( (seen[i].method == method_idx) &&
                 (0 == strcmp (seen[i].provider_url,
                               provider_url)) )
              found = true;
          if (found)
            continue; /* skip */
        }
        if (off == len)
        {
          GNUNET_array_grow (seen,
                             len,
                             4 + len * 2);
        }
        seen[off].method = method_idx;
        seen[off].provider_url = provider_url;
        off++;
        p = ANASTASIS_REDUX_provider_find_ (&rs->common,
                                            provider_url);
        if ( (NULL == p) ||
             (! p->have_config) ||
             (ANASTASIS_RPS_OK != p->status) ||
             (MHD_HTTP_OK != p->config.http_status) )
        {
          GNUNET_break (0);
          GNUNET_array_grow (seen,
                             len,
                             0);
          free_costs (costs);
          return GNUNET_SYSERR;
        }
        {
          const struct TALER_Amount *tf;
          struct TALER_Amount fee;

          tf = preferred_price (&rs->common,
                                &p->config.truth_upload_fees);
          if (NULL == tf)
            continue; /* provider stores truths for free */
          if (0 >
              TALER_amount_multiply (&fee,
                                     tf,
                                     years))
          {
            GNUNET_break (0);
            GNUNET_array_grow (seen,
                               len,
                               0);
            free_costs (costs);
            return GNUNET_SYSERR;
          }
          add_cost (&costs,
                    &fee);
        }
      }
    }
    GNUNET_array_grow (seen,
                       len,
                       0);
  }

  /* convert 'costs' into state */
  GNUNET_free (b->upload_fees);
  b->upload_fees_len = 0;
  b->have_upload_fees = true;
  while (NULL != costs)
  {
    struct Costs *nxt = costs->next;

    if (! TALER_amount_is_zero (&costs->cost))
    {
      GNUNET_array_append (b->upload_fees,
                           b->upload_fees_len,
                           costs->cost);
      is_free = false;
    }
    GNUNET_free (costs);
    costs = nxt;
  }

  if (is_free)
    expiration = GNUNET_TIME_relative_to_timestamp (ANASTASIS_FREE_STORAGE);
  b->expiration = expiration;
  b->have_expiration = true;
  return GNUNET_OK;
}


/**
 * DispatchHandler/Callback function which is called for a
 * "done_policy_review" action.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
done_policy_review (struct ANASTASIS_ReduxState *rs,
                    const json_t *arguments,
                    ANASTASIS_ActionCallback cb,
                    void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  struct GNUNET_TIME_Timestamp exp;

  if (0 == b->policies_len)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
                           "no policies specified");
    return NULL;
  }
  exp = b->have_expiration
        ? b->expiration
        : GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_YEARS);
  if (GNUNET_TIME_absolute_is_zero (exp.abs_time))
    exp = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_YEARS);
  if (GNUNET_OK !=
      update_expiration_cost (rs,
                              exp))
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
                           "could not calculate expiration cost");
    return NULL;
  }
  set_state (rs,
             ANASTASIS_BACKUP_STATE_SECRET_EDITING);
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/**
 * Information we keep for an upload() operation.
 */
struct UploadContext;


/**
 * Maps a TruthUpload to a policy and recovery method where this
 * truth is used.
 */
struct PolicyMethodReference
{
  /**
   * Offset into the "policies" array.
   */
  unsigned int policy_index;

  /**
   * Offset into the "methods" array (of the policy selected
   * by @e policy_index).
   */
  unsigned int method_index;

};


/**
 * Entry we keep per truth upload.
 */
struct TruthUpload
{

  /**
   * Kept in a DLL.
   */
  struct TruthUpload *next;

  /**
   * Kept in a DLL.
   */
  struct TruthUpload *prev;

  /**
   * Handle to the actual upload operation.
   */
  struct ANASTASIS_TruthUpload *tu;

  /**
   * Upload context this operation is part of.
   */
  struct UploadContext *uc;

  /**
   * Truth resulting from the upload, if any.
   */
  struct ANASTASIS_Truth *t;

  /**
   * A taler://pay/-URI with a request to pay the annual fee for
   * the service.  Set if payment is required.
   */
  char *payment_request;

  /**
   * Which policies and methods does this truth affect?
   */
  struct PolicyMethodReference *policies;

  /**
   * Where are we uploading to?
   */
  char *provider_url;

  /**
   * Which challenge object are we uploading?
   */
  uint32_t am_idx;

  /**
   * Length of the @e policies array.
   */
  unsigned int policies_length;

  /**
   * Status of the upload.
   */
  enum ANASTASIS_UploadStatus us;

  /**
   * Taler error code of the upload.
   */
  enum TALER_ErrorCode ec;

};


/**
 * Information we keep for an upload() operation.
 */
struct UploadContext
{
  /**
   * Recovery action returned to caller for aborting the operation.
   */
  struct ANASTASIS_ReduxAction ra;

  /**
   * Function to call upon completion.
   */
  ANASTASIS_ActionCallback cb;

  /**
   * Closure for @e cb.
   */
  void *cb_cls;

  /**
   * Our state; we own it.
   */
  struct ANASTASIS_ReduxState *rs;

  /**
   * Master secret sharing operation, NULL if not yet running.
   */
  struct ANASTASIS_SecretShare *ss;

  /**
   * Head of DLL of truth uploads.
   */
  struct TruthUpload *tues_head;

  /**
   * Tail of DLL of truth uploads.
   */
  struct TruthUpload *tues_tail;

  /**
   * Timeout to use for the operation, from the arguments.
   */
  struct GNUNET_TIME_Relative timeout;

  /**
   * For how many years should we pay?
   */
  unsigned int years;

};


/**
 * Function called when the #upload transition is being aborted.
 *
 * @param cls a `struct UploadContext`
 */
static void
upload_cancel_cb (void *cls)
{
  struct UploadContext *uc = cls;
  struct TruthUpload *tue;

  while (NULL != (tue = uc->tues_head))
  {
    GNUNET_CONTAINER_DLL_remove (uc->tues_head,
                                 uc->tues_tail,
                                 tue);
    if (NULL != tue->tu)
    {
      ANASTASIS_truth_upload_cancel (tue->tu);
      tue->tu = NULL;
    }
    if (NULL != tue->t)
    {
      ANASTASIS_truth_free (tue->t);
      tue->t = NULL;
    }
    GNUNET_free (tue->provider_url);
    GNUNET_free (tue->payment_request);
    GNUNET_free (tue->policies);
    GNUNET_free (tue);
  }
  if (NULL != uc->ss)
  {
    ANASTASIS_secret_share_cancel (uc->ss);
    uc->ss = NULL;
  }
  ANASTASIS_REDUX_state_free_ (uc->rs);
  GNUNET_free (uc);
}


/**
 * Return the state of @a uc to the application and dispose of @a uc.
 *
 * @param[in] uc context to finish
 * @param ec error code to report alongside the state
 */
static void
uc_return (struct UploadContext *uc,
           enum TALER_ErrorCode ec)
{
  struct ANASTASIS_ReduxState *rs = uc->rs;
  ANASTASIS_ActionCallback cb = uc->cb;
  void *cb_cls = uc->cb_cls;

  uc->rs = NULL;
  upload_cancel_cb (uc);
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           ec);
}


/**
 * Report an error to the application and dispose of @a uc.
 *
 * @param[in] uc context to finish
 * @param ec error to report
 * @param detail human-readable detail, may be NULL
 */
static void
uc_fail (struct UploadContext *uc,
         enum TALER_ErrorCode ec,
         const char *detail)
{
  ANASTASIS_ActionCallback cb = uc->cb;
  void *cb_cls = uc->cb_cls;

  upload_cancel_cb (uc);
  ANASTASIS_redux_fail_ (cb,
                         cb_cls,
                         ec,
                         detail);
}


/**
 * Duplicate the truth @a t.  Truths are shared between the policies
 * that use them, but each `struct ANASTASIS_Truth` has a single owner,
 * so the state gets its own copy.
 *
 * @param t truth to copy
 * @return a copy of @a t
 */
static struct ANASTASIS_Truth *
truth_dup (const struct ANASTASIS_Truth *t)
{
  json_t *jt = ANASTASIS_truth_to_json (t);
  struct ANASTASIS_Truth *r;

  GNUNET_assert (NULL != jt);
  r = ANASTASIS_truth_from_json (jt);
  GNUNET_assert (NULL != r);
  json_decref (jt);
  return r;
}


/**
 * Take all of the ongoing truth uploads and store them in the @a uc
 * state, so that they survive a round-trip through the application.
 *
 * @param[in,out] uc context to take truth uploads from and to update state of
 */
static void
serialize_truth (struct UploadContext *uc)
{
  struct ANASTASIS_ReduxBackup *b = &uc->rs->details.backup;

  for (struct TruthUpload *tue = uc->tues_head;
       NULL != tue;
       tue = tue->next)
  {
    if (NULL == tue->t)
      continue;
    for (unsigned int i = 0; i<tue->policies_length; i++)
    {
      const struct PolicyMethodReference *pmr = &tue->policies[i];
      struct ANASTASIS_ReduxPolicy *policy;
      struct ANASTASIS_ReduxPolicyMethod *pm;

      GNUNET_assert (pmr->policy_index < b->policies_len);
      policy = &b->policies[pmr->policy_index];
      GNUNET_assert (pmr->method_index < policy->methods_len);
      pm = &policy->methods[pmr->method_index];
      if (NULL != pm->truth)
        ANASTASIS_truth_free (pm->truth);
      pm->truth = truth_dup (tue->t);
      pm->upload_status = tue->us;
    }
  }
}


/**
 * Test if the given @a provider_url is used by any of the
 * authentication methods and thus the provider should be
 * considered mandatory for storing the policy.
 *
 * @param state state to inspect
 * @param provider_url provider to test
 * @return false if the provider can be removed from policy
 *   upload considerations without causing a problem
 */
static bool
provider_required (const struct ANASTASIS_ReduxState *rs,
                   const char *provider_url)
{
  const struct ANASTASIS_ReduxBackup *b = &rs->details.backup;

  for (unsigned int pidx = 0; pidx < b->policies_len; pidx++)
  {
    const struct ANASTASIS_ReduxPolicy *policy = &b->policies[pidx];

    for (unsigned int midx = 0; midx < policy->methods_len; midx++)
      if (0 == strcmp (policy->methods[midx].provider.url,
                       provider_url))
        return true;
  }
  return false;
}


/**
 * All truth uploads are done, begin with uploading the policy.
 *
 * @param[in,out] uc context for the operation
 */
static void
share_secret (struct UploadContext *uc);


/**
 * Function called with the results of a #ANASTASIS_secret_share().
 *
 * @param cls closure with a `struct UploadContext *`
 * @param sr share result
 */
static void
secret_share_result_cb (void *cls,
                        const struct ANASTASIS_ShareResult *sr)
{
  struct UploadContext *uc = cls;
  struct ANASTASIS_ReduxBackup *b = &uc->rs->details.backup;

  uc->ss = NULL;
  switch (sr->ss)
  {
  case ANASTASIS_SHARE_STATUS_SUCCESS:
    /* Just to be safe, delete the "core_secret" so that it is not
       accidentally preserved anywhere */
    json_decref (b->core_secret);
    b->core_secret = NULL;
    for (unsigned int i = 0; i < b->success_details_len; i++)
      ANASTASIS_REDUX_provider_url_clear_ (
        &b->success_details[i].provider_url);
    GNUNET_free (b->success_details);
    b->success_details_len = 0;
    b->have_success_details = true;
    for (unsigned int i = 0; i<sr->details.success.num_providers; i++)
    {
      const struct ANASTASIS_ProviderSuccessStatus *pssi
        = &sr->details.success.pss[i];
      struct ANASTASIS_ReduxSuccessDetail d = {
        .policy_version = pssi->policy_version,
        .policy_expiration = pssi->policy_expiration
      };

      ANASTASIS_REDUX_provider_url_set_ (&d.provider_url,
                                         pssi->provider_url);
      GNUNET_array_append (b->success_details,
                           b->success_details_len,
                           d);
    }
    set_state (uc->rs,
               ANASTASIS_BACKUP_STATE_BACKUP_FINISHED);
    uc_return (uc,
               TALER_EC_NONE);
    return;
  case ANASTASIS_SHARE_STATUS_PAYMENT_REQUIRED:
    set_state (uc->rs,
               ANASTASIS_BACKUP_STATE_POLICIES_PAYING);
    serialize_truth (uc);
    ANASTASIS_REDUX_policy_payment_requests_clear_ (b);
    b->have_policy_payment_requests = true;
    for (unsigned int i = 0; i<
         sr->details.payment_required.payment_requests_length; i++)
    {
      const struct ANASTASIS_SharePaymentRequest *spr;
      struct ANASTASIS_ReduxPolicyPaymentRequest ppr = { 0 };

      spr = &sr->details.payment_required.payment_requests[i];
      ppr.payto = GNUNET_strdup (spr->payment_request_url);
      ANASTASIS_REDUX_provider_url_set_ (&ppr.provider,
                                         spr->provider_url);
      GNUNET_array_append (b->policy_payment_requests,
                           b->policy_payment_requests_len,
                           ppr);
      for (unsigned int off = 0; off < b->policy_providers_len; off++)
      {
        struct ANASTASIS_ReduxPolicyProvider *pp = &b->policy_providers[off];

        if (0 == strcmp (pp->provider_url.url,
                         spr->provider_url))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                      "Remembering payment secret for provider `%s'\n",
                      spr->provider_url);
          pp->payment_secret = spr->payment_secret;
          pp->have_payment_secret = true;
        }
      }
    }
    uc_return (uc,
               TALER_EC_NONE);
    return;
  case ANASTASIS_SHARE_STATUS_PROVIDER_FAILED:
    {
      json_t *details;
      ANASTASIS_ActionCallback cb;
      void *cb_cls;

      if (! provider_required (uc->rs,
                               sr->details.provider_failure.provider_url))
      {
        /* try again without that provider */
        struct ANASTASIS_ReduxProvider *p;

        p = ANASTASIS_REDUX_provider_find_ (
          &uc->rs->common,
          sr->details.provider_failure.provider_url);
        if (NULL == p)
          GNUNET_break (0);
        else
          p->status = ANASTASIS_RPS_DISABLED;
        for (unsigned int idx = 0; idx < b->policy_providers_len; idx++)
        {
          struct ANASTASIS_ReduxPolicyProvider *pp
            = &b->policy_providers[idx];

          if (0 == strcmp (sr->details.provider_failure.provider_url,
                           pp->provider_url.url))
          {
            ANASTASIS_REDUX_provider_url_clear_ (&pp->provider_url);
            memmove (pp,
                     pp + 1,
                     sizeof (*pp) * (b->policy_providers_len - idx - 1));
            b->policy_providers_len--;
            break;
          }
        }
        share_secret (uc);
        return;
      }
      details = GNUNET_JSON_PACK (
        GNUNET_JSON_pack_uint64 ("http_status",
                                 sr->details.provider_failure.http_status),
        GNUNET_JSON_pack_uint64 ("code",
                                 sr->details.provider_failure.ec),
        GNUNET_JSON_pack_string ("hint",
                                 TALER_ErrorCode_get_hint (
                                   sr->details.provider_failure.ec)),
        GNUNET_JSON_pack_string ("provider_url",
                                 sr->details.provider_failure.provider_url));
      cb = uc->cb;
      cb_cls = uc->cb_cls;
      upload_cancel_cb (uc);
      cb (cb_cls,
          TALER_EC_ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED,
          details);
      json_decref (details);
    }
    return;
  default:
    GNUNET_break (0);
    uc_fail (uc,
             TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
             "unexpected share result");
    return;
  }
}


/**
 * All truth uploads are done, begin with uploading the policy.
 *
 * @param[in,out] uc context for the operation
 */
static void
share_secret (struct UploadContext *uc)
{
  struct ANASTASIS_ReduxBackup *b = &uc->rs->details.backup;
  unsigned int policies_len = b->policies_len;
  unsigned int pds_len = b->policy_providers_len;
  struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;

  if ( (NULL == uc->rs->common.identity_attributes) ||
       (NULL == b->core_secret) ||
       (! b->have_policies) )
  {
    uc_fail (uc,
             TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
             "State parsing failed when preparing to share secret");
    return;
  }
  if (b->have_pay_arguments &&
      b->pay_arguments.have_timeout)
    timeout = b->pay_arguments.timeout;
  if (0 == policies_len)
  {
    uc_fail (uc,
             TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
             "State parsing failed checks when preparing to share secret");
    return;
  }
  if (0 == pds_len)
  {
    uc_fail (uc,
             TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
             "no workable providers in state");
    return;
  }

  {
    struct ANASTASIS_Policy *vpolicies[policies_len];
    const struct ANASTASIS_Policy *policies[policies_len];
    struct ANASTASIS_ProviderDetails pds[pds_len];

    /* initialize policies/vpolicies arrays */
    memset (pds,
            0,
            sizeof (pds));
    for (unsigned int i = 0; i<policies_len; i++)
    {
      const struct ANASTASIS_ReduxPolicy *policy = &b->policies[i];
      unsigned int methods_len = policy->methods_len;

      if (0 == methods_len)
      {
        GNUNET_break (0);
        for (unsigned int k = 0; k<i; k++)
          ANASTASIS_policy_destroy (vpolicies[k]);
        uc_fail (uc,
                 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                 "'methods' must be an array of sane length");
        return;
      }
      {
        struct ANASTASIS_Policy *p;
        struct ANASTASIS_Truth *truths[methods_len];
        const struct ANASTASIS_Truth *ctruths[methods_len];

        for (unsigned int j = 0; j<methods_len; j++)
        {
          const struct ANASTASIS_ReduxPolicyMethod *pm = &policy->methods[j];

          if (NULL != pm->truth)
          {
            /* Truth we already have in the state */
            truths[j] = truth_dup (pm->truth);
          }
          else
          {
            bool found = false;
            /* Maybe we never stored the truth; find it in our DLL */
            for (struct TruthUpload *tue = uc->tues_head;
                 NULL != tue;
                 tue = tue->next)
            {
              GNUNET_break (NULL != tue->t);
              if ( (tue->am_idx == pm->authentication_method.idx) &&
                   (0 == strcmp (pm->provider.url,
                                 tue->provider_url)) )
              {
                truths[j] = truth_dup (tue->t);
                found = true;
                break;
              }
            }
            if (! found)
            {
              GNUNET_break (0);
              for (unsigned int k = 0; k<j; k++)
                ANASTASIS_truth_free (truths[k]);
              for (unsigned int k = 0; k<i; k++)
                ANASTASIS_policy_destroy (vpolicies[k]);
              uc_fail (uc,
                       TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                       "'truth' failed to decode");
              return;
            }
          }
          ctruths[j] = truths[j];
        }
        p = ANASTASIS_policy_create (ctruths,
                                     methods_len);
        vpolicies[i] = p;
        policies[i] = p;
        for (unsigned int k = 0; k<methods_len; k++)
          ANASTASIS_truth_free (truths[k]);
      }
    }

    /* initialize 'pds' array */
    for (unsigned int i = 0; i<pds_len; i++)
    {
      const struct ANASTASIS_ReduxPolicyProvider *pp
        = &b->policy_providers[i];

      pds[i].provider_url = pp->provider_url.url;
      if (pp->have_payment_secret)
        pds[i].payment_secret = pp->payment_secret;
      if (GNUNET_OK !=
          ANASTASIS_REDUX_lookup_salt_ (&uc->rs->common,
                                        pds[i].provider_url,
                                        &pds[i].provider_salt))
      {
        GNUNET_break (0);
        for (unsigned int p = 0; p<policies_len; p++)
          ANASTASIS_policy_destroy (vpolicies[p]);
        uc_fail (uc,
                 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                 "'providers' entry malformed");
        return;
      }
    }

    {
      char *secret;
      size_t secret_size;

      secret = json_dumps (b->core_secret,
                           JSON_COMPACT | JSON_SORT_KEYS);
      GNUNET_assert (NULL != secret);
      secret_size = strlen (secret);
      uc->ss = ANASTASIS_secret_share (ANASTASIS_REDUX_ctx_,
                                       uc->rs->common.identity_attributes,
                                       pds,
                                       pds_len,
                                       policies,
                                       policies_len,
                                       uc->years,
                                       timeout,
                                       &secret_share_result_cb,
                                       uc,
                                       b->secret_name,
                                       secret,
                                       secret_size);
      GNUNET_free (secret);
    }
    for (unsigned int i = 0; i<policies_len; i++)
      ANASTASIS_policy_destroy (vpolicies[i]);
  }
  if (NULL == uc->ss)
  {
    GNUNET_break (0);
    uc_fail (uc,
             TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
             "Failed to begin secret sharing");
    return;
  }
}


/**
 * Some truth uploads require payment, serialize state and
 * request payment to be executed by the application.
 *
 * @param[in,out] uc context for the operation
 */
static void
request_truth_payment (struct UploadContext *uc)
{
  struct ANASTASIS_ReduxBackup *b = &uc->rs->details.backup;

  serialize_truth (uc);
  ANASTASIS_REDUX_payments_clear_ (b);
  b->have_payments = true;
  for (struct TruthUpload *tue = uc->tues_head;
       NULL != tue;
       tue = tue->next)
  {
    char *pr;

    if (NULL == tue->payment_request)
      continue;
    pr = GNUNET_strdup (tue->payment_request);
    GNUNET_array_append (b->payments,
                         b->payments_len,
                         pr);
  }
  set_state (uc->rs,
             ANASTASIS_BACKUP_STATE_TRUTHS_PAYING);
  uc_return (uc,
             TALER_EC_NONE);
}


/**
 * We may be finished with all (active) asynchronous operations.
 * Check if any are pending and continue accordingly.
 *
 * @param[in,out] uc context for the operation
 */
static void
check_upload_finished (struct UploadContext *uc)
{
  bool pay = false;
  bool active = false;

  for (struct TruthUpload *tue = uc->tues_head;
       NULL != tue;
       tue = tue->next)
  {
    if (TALER_EC_NONE != tue->ec)
    {
      ANASTASIS_ActionCallback cb = uc->cb;
      void *cb_cls = uc->cb_cls;
      enum TALER_ErrorCode ec = tue->ec;

      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Truth upload failed with error %d\n",
                  (int) ec);
      upload_cancel_cb (uc);
      cb (cb_cls,
          ec,
          NULL);
      return;
    }
    if (NULL != tue->tu)
      active = true;
    if (NULL != tue->payment_request)
      pay = true;
  }
  if (active)
    return;
  if (pay)
  {
    request_truth_payment (uc);
    return;
  }
  share_secret (uc);
}


/**
 * Upload result information.  The resulting truth object can be used
 * to create policies.  If payment is required, the @a taler_pay_url
 * is returned and the operation must be retried after payment.
 * Callee MUST free @a t using ANASTASIS_truth_free().
 *
 * @param cls closure with a `struct TruthUpload`
 * @param t truth object to create policies, NULL on failure
 * @param ud upload details
 */
static void
truth_upload_cb (void *cls,
                 struct ANASTASIS_Truth *t,
                 const struct ANASTASIS_UploadDetails *ud)
{
  struct TruthUpload *tue = cls;

  tue->tu = NULL;
  tue->t = t;
  tue->ec = ud->ec;
  tue->us = ud->us;
  if (ANASTASIS_US_PAYMENT_REQUIRED == ud->us)
  {
    tue->payment_request = GNUNET_strdup (
      ud->details.payment.payment_request);
  }
  check_upload_finished (tue->uc);
}


/**
 * Check if we still need to create a new truth object for the truth
 * identified by @a provider_url and @a am_idx. If so, create it from
 * @a truth for policy reference @a pmr. If such a truth object
 * already exists, append @a pmr to its list of reasons.
 *
 * @param[in,out] uc our upload context
 * @param pmr policy method combination that requires the truth
 * @param provider_url the URL of the Anastasis provider to upload
 *                     the truth to, used to check for existing entries
 * @param am_idx index of the authentication method, used to check for existing entries
 * @param[in] truth object representing already uploaded truth, reference captured!
 * @param[in,out] async_truth pointer to counter with the number of ongoing uploads,
 *                updated
 * @param auth_method object with the challenge details, to generate the truth
 * @return #GNUNET_SYSERR error requiring abort,
 *         #GNUNET_OK on success
 */
static int
add_truth_object (struct UploadContext *uc,
                  const struct PolicyMethodReference *pmr,
                  const char *provider_url,
                  uint32_t am_idx,
                  const struct ANASTASIS_ReduxPolicyMethod *pm,
                  unsigned int *async_truth,
                  const struct ANASTASIS_ReduxAuthMethod *auth_method)
{
  /* check if we are already uploading this truth */
  struct TruthUpload *tue;
  bool must_upload;

  for (tue = uc->tues_head;
       NULL != tue;
       tue = tue->next)
  {
    if ( (0 == strcmp (tue->provider_url,
                       provider_url)) &&
         (am_idx == tue->am_idx) )
    {
      GNUNET_array_append (tue->policies,
                           tue->policies_length,
                           *pmr);
      break;
    }
  }

  if (NULL == tue)
  {
    /* Create new entry */
    tue = GNUNET_new (struct TruthUpload);

    GNUNET_CONTAINER_DLL_insert (uc->tues_head,
                                 uc->tues_tail,
                                 tue);
    tue->uc = uc;
    tue->policies = GNUNET_new (struct PolicyMethodReference);
    *tue->policies = *pmr;
    tue->provider_url = GNUNET_strdup (provider_url);
    tue->am_idx = am_idx;
    tue->policies_length = 1;
  }

  must_upload = (ANASTASIS_US_SUCCESS != pm->upload_status);

  if (NULL == tue->t)
    tue->t = truth_dup (pm->truth);

  if ( (NULL != tue->tu) &&
       (! must_upload) )
  {
    ANASTASIS_truth_upload_cancel (tue->tu);
    (*async_truth)--;
    tue->tu = NULL;
    return GNUNET_OK;
  }

  if ( (NULL == tue->tu) &&
       (must_upload) )
  {
    struct ANASTASIS_CRYPTO_ProviderSaltP salt;
    struct ANASTASIS_CRYPTO_UserIdentifierP id;

    if (GNUNET_OK !=
        ANASTASIS_REDUX_lookup_salt_ (&uc->rs->common,
                                      provider_url,
                                      &salt))
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
    if (NULL == uc->rs->common.identity_attributes)
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
    ANASTASIS_CRYPTO_user_identifier_derive (
      uc->rs->common.identity_attributes,
      &salt,
      &id);
    tue->tu = ANASTASIS_truth_upload3 (ANASTASIS_REDUX_ctx_,
                                       &id,
                                       tue->t,
                                       auth_method->challenge,
                                       auth_method->challenge_size,
                                       uc->years,
                                       uc->timeout,
                                       &truth_upload_cb,
                                       tue);
    tue->t = NULL;
    (*async_truth)++;
  }

  if ( (NULL != tue->tu) &&
       (NULL != tue->t) )
  {
    /* no point in having both */
    ANASTASIS_truth_free (tue->t);
    tue->t = NULL;
  }
  return GNUNET_OK;
}


/**
 * Check if we still need to upload the truth identified by
 * @a provider_url and @a am_idx. If so, upload it for
 * policy reference @a pmr. If the upload is already queued,
 * append @a pmr to its list of reasons.
 *
 * @param[in,out] uc our upload context
 * @param pmr policy method combination that requires the truth
 * @param provider_url the URL of the Anastasis provider to upload
 *                     the truth to, used to check for existing entries
 * @param am_idx index of the authentication method, used to check for existing entries
 * @param auth_method object with the challenge details, to generate the truth
 * @return #GNUNET_SYSERR on error requiring abort, in which case @a uc
 *           has already been disposed of,
 *         #GNUNET_NO if no new truth upload was generated (@a pmr was appended)
 *         #GNUNET_OK if a new truth upload was initiated
 */
static int
check_truth_upload (struct UploadContext *uc,
                    const struct PolicyMethodReference *pmr,
                    const char *provider_url,
                    uint32_t am_idx,
                    const struct ANASTASIS_ReduxAuthMethod *auth_method)
{
  const json_t *user_id = uc->rs->common.identity_attributes;
  struct TruthUpload *tue;

  if (NULL == user_id)
  {
    GNUNET_break (0);
    upload_cancel_cb (uc);
    return GNUNET_SYSERR;
  }

  /* check if we are already uploading this truth */
  for (tue = uc->tues_head;
       NULL != tue;
       tue = tue->next)
  {
    if ( (0 == strcmp (tue->provider_url,
                       provider_url)) &&
         (am_idx == tue->am_idx) )
    {
      GNUNET_array_append (tue->policies,
                           tue->policies_length,
                           *pmr);
      return GNUNET_NO;
    }
  }

  /* need new upload */
  tue = GNUNET_new (struct TruthUpload);
  {
    struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
    struct ANASTASIS_CRYPTO_UserIdentifierP id;

    GNUNET_CONTAINER_DLL_insert (uc->tues_head,
                                 uc->tues_tail,
                                 tue);
    tue->uc = uc;
    tue->policies = GNUNET_new (struct PolicyMethodReference);
    *tue->policies = *pmr;
    tue->provider_url = GNUNET_strdup (provider_url);
    tue->am_idx = am_idx;
    tue->policies_length = 1;
    if (GNUNET_OK !=
        ANASTASIS_REDUX_lookup_salt_ (&uc->rs->common,
                                      provider_url,
                                      &provider_salt))
    {
      GNUNET_break (0);
      upload_cancel_cb (uc);
      return GNUNET_SYSERR;
    }
    ANASTASIS_CRYPTO_user_identifier_derive (user_id,
                                             &provider_salt,
                                             &id);
    tue->tu = ANASTASIS_truth_upload (ANASTASIS_REDUX_ctx_,
                                      &id,
                                      provider_url,
                                      auth_method->type,
                                      auth_method->instructions,
                                      auth_method->mime_type,
                                      &provider_salt,
                                      auth_method->challenge,
                                      auth_method->challenge_size,
                                      uc->years,
                                      uc->timeout,
                                      &truth_upload_cb,
                                      tue);
    if (NULL == tue->tu)
    {
      GNUNET_break (0);
      upload_cancel_cb (uc);
      return GNUNET_SYSERR;
    }
    return GNUNET_OK;
  }
}


/**
 * Function to upload truths and recovery document policies.
 * Ultimately transitions to failed state (allowing user to go back
 * and change providers/policies), or payment, or finished.
 *
 * @param state state to operate on
 * @param cb callback (#ANASTASIS_ActionCallback) to call after upload
 * @param cb_cls callback closure
 */
static struct ANASTASIS_ReduxAction *
upload (struct ANASTASIS_ReduxState *rs,
        ANASTASIS_ActionCallback cb,
        void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  struct UploadContext *uc;

  if (! b->have_expiration)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'expiration' missing");
    return NULL;
  }
  if (0 == b->authentication_methods_len)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'authentication_methods' must be non-empty array");
    return NULL;
  }
  if (0 == b->policies_len)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'policies' must be non-empty array");
    return NULL;
  }

  uc = GNUNET_new (struct UploadContext);
  uc->ra.cleanup = &upload_cancel_cb;
  uc->ra.cleanup_cls = uc;
  uc->cb = cb;
  uc->cb_cls = cb_cls;
  uc->rs = rs;
  uc->years = expiration_to_years (b->expiration);
  if (b->have_pay_arguments &&
      b->pay_arguments.have_timeout)
    uc->timeout = b->pay_arguments.timeout;

  {
    unsigned int async_truth = 0;

    for (unsigned int pindex = 0; pindex < b->policies_len; pindex++)
    {
      const struct ANASTASIS_ReduxPolicy *policy = &b->policies[pindex];

      if (0 == policy->methods_len)
      {
        uc_fail (uc,
                 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                 "'policies' must be non-empty array");
        return NULL;
      }
      for (unsigned int mindex = 0; mindex < policy->methods_len; mindex++)
      {
        const struct ANASTASIS_ReduxPolicyMethod *pm
          = &policy->methods[mindex];
        unsigned int am_idx = pm->authentication_method.idx;
        struct PolicyMethodReference pmr = {
          .policy_index = pindex,
          .method_index = mindex
        };
        const struct ANASTASIS_ReduxAuthMethod *amj;
        int ret;

        if (am_idx >= b->authentication_methods_len)
        {
          uc_fail (
            uc,
            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
            "'authentication_method' refers to invalid authorization index malformed");
          return NULL;
        }
        amj = &b->authentication_methods[am_idx];
        if (NULL == pm->truth)
        {
          ret = check_truth_upload (uc,
                                    &pmr,
                                    pm->provider.url,
                                    am_idx,
                                    amj);
          if (GNUNET_SYSERR == ret)
          {
            /* check_truth_upload() already disposed of @a uc */
            ANASTASIS_redux_fail_ (cb,
                                   cb_cls,
                                   TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                                   NULL);
            return NULL;
          }
          if (GNUNET_OK == ret)
            async_truth++;
        }
        else
        {
          ret = add_truth_object (uc,
                                  &pmr,
                                  pm->provider.url,
                                  am_idx,
                                  pm,
                                  &async_truth,
                                  amj);
          if (GNUNET_SYSERR == ret)
          {
            uc_fail (uc,
                     TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                     NULL);
            return NULL;
          }
        }
      } /* end for all methods of policy */
    } /* end for all policies */
    if (async_truth > 0)
      return &uc->ra;
  }
  share_secret (uc);
  if (NULL == uc->ss)
    return NULL;
  return &uc->ra;
}


/**
 * Test if the core secret @a secret_size is small enough to be stored
 * at all providers, which have a minimum upload limit of @a min_limit_in_mb.
 *
 * For now, we do not precisely calculate the size of the recovery document,
 * and simply assume that the instructions (i.e. security questions) are all
 * relatively small (aka sane), and that the number of authentication methods
 * and recovery policies is similarly small so that all of this meta data
 * fits in 512 kb (which is VERY big).
 *
 * Even with the minimum permitted upload limit of 1 MB (which is likely,
 * given that there is hardly a reason for providers to offer more), this
 * leaves 512 kb for the @a secret_size, which should be plenty (given
 * that this is supposed to be for a master key, and not the actual data).
 *
 * @param rs our state, could be used in the future to calculate the
 *        size of the recovery document without the core secret
 * @param secret_size size of the core secret
 * @param min_limit_in_mb minimum upload size of all providers
 */
static bool
core_secret_fits (const struct ANASTASIS_ReduxState *rs,
                  size_t secret_size,
                  uint32_t min_limit_in_mb)
{
  (void) rs;
  return (min_limit_in_mb * 1024LL * 1024LL >
          512LLU * 1024LLU + secret_size);
}


/**
 * Check if the upload size limit is satisfied.
 *
 * @param rs our state
 * @param jsecret the uploaded secret
 * @return #GNUNET_OK if @a secret_size works for all providers,
 *     #GNUNET_NO if the @a secret_size is too big,
 *     #GNUNET_SYSERR if a provider has a limit of 0
 */
static enum GNUNET_GenericReturnValue
check_upload_size_limit (const struct ANASTASIS_ReduxState *rs,
                         const json_t *jsecret)
{
  uint32_t min_limit = UINT32_MAX;
  size_t secret_size;

  {
    char *secret;

    secret = json_dumps (jsecret,
                         JSON_COMPACT | JSON_SORT_KEYS);
    GNUNET_assert (NULL != secret);
    secret_size = strlen (secret);
    GNUNET_free (secret);
  }

  /* We calculate the minimum upload limit of all possible providers;
     this is under the (simplified) assumption that we store the
     recovery document at all providers; this may be changed later,
     see #6760. */
  for (unsigned int i = 0; i < rs->common.providers_len; i++)
  {
    const struct ANASTASIS_ReduxProvider *p = &rs->common.providers[i];

    if ( (ANASTASIS_RPS_OK != p->status) ||
         (! p->have_config) ||
         (MHD_HTTP_OK != p->config.http_status) )
      continue;
    if (0 == p->config.storage_limit_in_megabytes)
      return GNUNET_SYSERR;
    min_limit = GNUNET_MIN (min_limit,
                            p->config.storage_limit_in_megabytes);
  }
  if (! core_secret_fits (rs,
                          secret_size,
                          min_limit))
    return GNUNET_NO;
  return GNUNET_OK;
}


/**
 * DispatchHandler/Callback function which is called for a
 * "enter_secret" action.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
enter_secret (struct ANASTASIS_ReduxState *rs,
              const json_t *arguments,
              ANASTASIS_ActionCallback cb,
              void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  const json_t *jsecret;
  struct GNUNET_TIME_Timestamp expiration
    = GNUNET_TIME_UNIT_ZERO_TS;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_object_const ("secret",
                                   &jsecret),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_timestamp ("expiration",
                                  &expiration),
      NULL),
    GNUNET_JSON_spec_end ()
  };

  if (NULL == arguments)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "arguments missing");
    return NULL;
  }
  if (GNUNET_OK !=
      GNUNET_JSON_parse (arguments,
                         spec,
                         NULL, NULL))
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'secret' argument required");
    return NULL;
  }

  /* check upload size limit */
  switch (check_upload_size_limit (rs,
                                   jsecret))
  {
  case GNUNET_SYSERR:
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "provider has an upload limit of 0");
    return NULL;
  case GNUNET_NO:
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_SECRET_TOO_BIG,
                           NULL);
    return NULL;
  default:
    break;
  }
  if (! GNUNET_TIME_absolute_is_zero (expiration.abs_time))
  {
    if (GNUNET_OK !=
        update_expiration_cost (rs,
                                expiration))
    {
      ANASTASIS_REDUX_fail_ (rs,
                             cb,
                             cb_cls,
                             TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
                             "could not calculate expiration cost");
      return NULL;
    }
  }
  json_decref (b->core_secret);
  b->core_secret = json_incref ((json_t *) jsecret);
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/**
 * DispatchHandler/Callback function which is called for a
 * "clear_secret" action.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
clear_secret (struct ANASTASIS_ReduxState *rs,
              const json_t *arguments,
              ANASTASIS_ActionCallback cb,
              void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;

  if (NULL == b->core_secret)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'core_secret' not set");
    return NULL;
  }
  json_decref (b->core_secret);
  b->core_secret = NULL;
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/**
 * DispatchHandler/Callback function which is called for an
 * "enter_secret_name" action.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
enter_secret_name (struct ANASTASIS_ReduxState *rs,
                   const json_t *arguments,
                   ANASTASIS_ActionCallback cb,
                   void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  const char *secret_name = NULL;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("name",
                             &secret_name),
    GNUNET_JSON_spec_end ()
  };

  if (NULL == arguments)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "arguments missing");
    return NULL;
  }
  if (GNUNET_OK !=
      GNUNET_JSON_parse (arguments,
                         spec,
                         NULL, NULL))
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'name' argument required");
    return NULL;
  }
  GNUNET_free (b->secret_name);
  b->secret_name = GNUNET_strdup (secret_name);
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/**
 * DispatchHandler/Callback function which is called for the
 * "update_expiration" action in the "secret editing" state.
 * Updates how long we are to store the truth and policies
 * and computes the new cost.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL (synchronous operation)
 */
static struct ANASTASIS_ReduxAction *
update_expiration (struct ANASTASIS_ReduxState *rs,
                   const json_t *arguments,
                   ANASTASIS_ActionCallback cb,
                   void *cb_cls)
{
  struct GNUNET_TIME_Timestamp expiration;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_timestamp ("expiration",
                                &expiration),
    GNUNET_JSON_spec_end ()
  };

  if (NULL == arguments)
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "arguments missing");
    return NULL;
  }
  if (GNUNET_OK !=
      GNUNET_JSON_parse (arguments,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'expiration' argument required");
    return NULL;
  }
  if (GNUNET_OK !=
      update_expiration_cost (rs,
                              expiration))
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
                           "could not calculate expiration cost");
    return NULL;
  }
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/**
 * DispatchHandler/Callback function which is called for the
 * "next" action in the "secret editing" state.
 * Returns an #ANASTASIS_ReduxAction as operation is async.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 */
static struct ANASTASIS_ReduxAction *
finish_secret (struct ANASTASIS_ReduxState *rs,
               const json_t *arguments,
               ANASTASIS_ActionCallback cb,
               void *cb_cls)
{
  const struct ANASTASIS_ReduxBackup *b = &rs->details.backup;

  if (NULL == b->core_secret)
  {
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "State parsing failed: 'core_secret' is missing");
    return NULL;
  }

  /* check upload size limit */
  switch (check_upload_size_limit (rs,
                                   b->core_secret))
  {
  case GNUNET_SYSERR:
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "provider has an upload limit of 0");
    return NULL;
  case GNUNET_NO:
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_SECRET_TOO_BIG,
                           NULL);
    return NULL;
  default:
    break;
  }
  return upload (rs,
                 cb,
                 cb_cls);
}


/**
 * Remember the arguments of a "pay" action in @a rs, so that they are
 * still around when the upload is retried after the payment.
 *
 * @param[in,out] rs state to update
 * @param arguments arguments of the action, may be NULL
 * @param cb callback to report a failure to
 * @param cb_cls closure for @a cb
 * @return false if @a arguments were malformed, in which case @a cb has
 *         been invoked and @a rs freed
 */
static bool
set_pay_arguments (struct ANASTASIS_ReduxState *rs,
                   const json_t *arguments,
                   ANASTASIS_ActionCallback cb,
                   void *cb_cls)
{
  struct ANASTASIS_ReduxBackup *b = &rs->details.backup;
  bool no_timeout;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_relative_time ("timeout",
                                      &b->pay_arguments.timeout),
      &no_timeout),
    GNUNET_JSON_spec_end ()
  };

  if (NULL == arguments)
    return true;
  if (GNUNET_OK !=
      GNUNET_JSON_parse (arguments,
                         spec,
                         NULL, NULL))
  {
    json_dumpf ((json_t *) arguments,
                stderr,
                JSON_INDENT (2));
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
                           "'timeout' must be valid delay");
    return false;
  }
  b->have_pay_arguments = true;
  b->pay_arguments.have_timeout = ! no_timeout;
  return true;
}


/**
 * DispatchHandler/Callback function which is called for a
 * "pay" action.
 * Returns an #ANASTASIS_ReduxAction as operation is async.
 *
 * @param[in] rs state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 */
static struct ANASTASIS_ReduxAction *
pay_truths_backup (struct ANASTASIS_ReduxState *rs,
                   const json_t *arguments,
                   ANASTASIS_ActionCallback cb,
                   void *cb_cls)
{
  /* Clear 'payments' if it exists */
  ANASTASIS_REDUX_payments_clear_ (&rs->details.backup);
  if (! set_pay_arguments (rs,
                           arguments,
                           cb,
                           cb_cls))
    return NULL;
  return upload (rs,
                 cb,
                 cb_cls);
}


/**
 * DispatchHandler/Callback function which is called for a
 * "pay" action.
 * Returns an #ANASTASIS_ReduxAction as operation is async.
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 */
static struct ANASTASIS_ReduxAction *
pay_policies_backup (struct ANASTASIS_ReduxState *rs,
                     const json_t *arguments,
                     ANASTASIS_ActionCallback cb,
                     void *cb_cls)
{
  /* Clear 'policy_payment_requests' if it exists */
  ANASTASIS_REDUX_policy_payment_requests_clear_ (&rs->details.backup);
  if (! set_pay_arguments (rs,
                           arguments,
                           cb,
                           cb_cls))
    return NULL;
  return upload (rs,
                 cb,
                 cb_cls);
}


/**
 * DispatchHandler/Callback function which is called for a
 * "back" action if state is "FINISHED".
 *
 * @param state state to operate on
 * @param arguments arguments to use for operation on state
 * @param cb callback to call during/after operation
 * @param cb_cls callback closure
 * @return NULL
 */
static struct ANASTASIS_ReduxAction *
back_finished (struct ANASTASIS_ReduxState *rs,
               const json_t *arguments,
               ANASTASIS_ActionCallback cb,
               void *cb_cls)
{
  set_state (rs,
             ANASTASIS_BACKUP_STATE_SECRET_EDITING);
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
  return NULL;
}


/**
 * Signature of callback function that implements a state transition.
 *
 *  @param[in] rs current state
 *  @param arguments arguments for the state transition
 *  @param cb function to call when done
 *  @param cb_cls closure for @a cb
 */
typedef struct ANASTASIS_ReduxAction *
(*DispatchHandler)(struct ANASTASIS_ReduxState *rs,
                   const json_t *arguments,
                   ANASTASIS_ActionCallback cb,
                   void *cb_cls);


struct ANASTASIS_ReduxAction *
ANASTASIS_backup_action_ (struct ANASTASIS_ReduxState *rs,
                          const char *action,
                          const json_t *arguments,
                          ANASTASIS_ActionCallback cb,
                          void *cb_cls)
{
  struct Dispatcher
  {
    enum ANASTASIS_BackupState backup_state;
    const char *backup_action;
    DispatchHandler fun;
  } dispatchers[] = {
    {
      ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
      "add_authentication",
      &add_authentication
    },
    {
      ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
      "delete_authentication",
      &del_authentication
    },
    {
      ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
      "next",
      &done_authentication
    },
    {
      ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
      "add_provider",
      &add_provider
    },
    {
      ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
      "poll_providers",
      &ANASTASIS_REDUX_poll_providers_
    },
    {
      ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
      "back",
      &ANASTASIS_back_generic_decrement_
    },
    {
      ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
      "add_policy",
      &add_policy
    },
    {
      ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
      "update_policy",
      &update_policy
    },
    {
      ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
      "delete_policy",
      &del_policy
    },
    {
      ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
      "delete_challenge",
      &del_challenge
    },
    {
      ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
      "next",
      &done_policy_review
    },
    {
      ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
      "back",
      &ANASTASIS_back_generic_decrement_
    },
    {
      ANASTASIS_BACKUP_STATE_SECRET_EDITING,
      "enter_secret",
      &enter_secret
    },
    {
      ANASTASIS_BACKUP_STATE_SECRET_EDITING,
      "clear_secret",
      &clear_secret
    },
    {
      ANASTASIS_BACKUP_STATE_SECRET_EDITING,
      "enter_secret_name",
      &enter_secret_name
    },
    {
      ANASTASIS_BACKUP_STATE_SECRET_EDITING,
      "back",
      &ANASTASIS_back_generic_decrement_
    },
    {
      ANASTASIS_BACKUP_STATE_SECRET_EDITING,
      "update_expiration",
      &update_expiration
    },
    {
      ANASTASIS_BACKUP_STATE_SECRET_EDITING,
      "next",
      &finish_secret
    },
    {
      ANASTASIS_BACKUP_STATE_TRUTHS_PAYING,
      "pay",
      &pay_truths_backup
    },
    {
      ANASTASIS_BACKUP_STATE_POLICIES_PAYING,
      "pay",
      &pay_policies_backup
    },
    {
      ANASTASIS_BACKUP_STATE_BACKUP_FINISHED,
      "back",
      &back_finished
    },
    { ANASTASIS_BACKUP_STATE_INVALID, NULL, NULL }
  };
  enum ANASTASIS_BackupState bs = rs->details.backup.state;

  GNUNET_assert (ANASTASIS_RT_BACKUP == rs->type);
  for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
  {
    if ( (bs == dispatchers[i].backup_state) &&
         (0 == strcmp (action,
                       dispatchers[i].backup_action)) )
    {
      return dispatchers[i].fun (rs,
                                 arguments,
                                 cb,
                                 cb_cls);
    }
  }
  ANASTASIS_REDUX_fail_ (rs,
                         cb,
                         cb_cls,
                         TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
                         action);
  return NULL;
}


/**
 * State for a #ANASTASIS_REDUX_backup_begin_() operation.
 */
struct BackupStartState;


/**
 * Entry in the list of all known applicable Anastasis providers.
 * Used to wait for it to complete downloading /config.
 */
struct BackupStartStateProviderEntry
{
  /**
   * Kept in a DLL.
   */
  struct BackupStartStateProviderEntry *next;

  /**
   * Kept in a DLL.
   */
  struct BackupStartStateProviderEntry *prev;

  /**
   * Main operation this entry is part of.
   */
  struct BackupStartState *bss;

  /**
   * Ongoing reducer action to obtain /config, NULL if completed.
   */
  struct ANASTASIS_ReduxAction *ra;

  /**
   * Final result of the operation (once completed).
   */
  enum TALER_ErrorCode ec;
};


struct BackupStartState
{
  /**
   * Head of list of provider /config operations we are doing.
   */
  struct BackupStartStateProviderEntry *pe_head;

  /**
   * Tail of list of provider /config operations we are doing.
   */
  struct BackupStartStateProviderEntry *pe_tail;

  /**
   * State we are updating; we own it.  All our entries update it in
   * place, so there is nothing to merge once they are done.
   */
  struct ANASTASIS_ReduxState *rs;

  /**
   * Function to call when we are done.
   */
  ANASTASIS_ActionCallback cb;

  /**
   * Closure for @e cb.
   */
  void *cb_cls;

  /**
   * Redux action we returned to our controller.
   */
  struct ANASTASIS_ReduxAction ra;

  /**
   * Number of provider /config operations in @e ba_head that
   * are still awaiting completion.
   */
  unsigned int pending;
};


/**
 * The backup start operation is being aborted, terminate.
 *
 * @param cls a `struct BackupStartState *`
 */
static void
abort_backup_begin_cb (void *cls)
{
  struct BackupStartState *bss = cls;
  struct BackupStartStateProviderEntry *pe;

  while (NULL != (pe = bss->pe_head))
  {
    GNUNET_CONTAINER_DLL_remove (bss->pe_head,
                                 bss->pe_tail,
                                 pe);
    if (NULL != pe->ra)
      pe->ra->cleanup (pe->ra->cleanup_cls);
    GNUNET_free (pe);
  }
  ANASTASIS_REDUX_state_free_ (bss->rs);
  GNUNET_free (bss);
}


/**
 * We finished downloading /config from all providers, trigger the
 * continuation and free our state.
 *
 * @param[in] bss main state to return
 */
static void
providers_complete (struct BackupStartState *bss)
{
  struct ANASTASIS_ReduxState *rs = bss->rs;
  ANASTASIS_ActionCallback cb = bss->cb;
  void *cb_cls = bss->cb_cls;

  bss->rs = NULL;
  abort_backup_begin_cb (bss);
  ANASTASIS_REDUX_return_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_NONE);
}


/**
 * Function called when the complete information about a provider
 * was added to the state.
 *
 * @param cls a `struct BackupStartStateProviderEntry`
 * @param error error code
 * @param rs the state, updated in place
 */
static void
provider_added_cb (void *cls,
                   enum TALER_ErrorCode error,
                   struct ANASTASIS_ReduxState *rs)
{
  struct BackupStartStateProviderEntry *pe = cls;
  struct BackupStartState *bss = pe->bss;

  GNUNET_assert (rs == bss->rs);
  /* our waiter unlinks and frees itself right after this call */
  pe->ra = NULL;
  GNUNET_CONTAINER_DLL_remove (bss->pe_head,
                               bss->pe_tail,
                               pe);
  pe->ec = error;
  GNUNET_free (pe);
  bss->pending--;
  if (0 == bss->pending)
    providers_complete (bss);
}


struct ANASTASIS_ReduxAction *
ANASTASIS_REDUX_backup_begin_ (struct ANASTASIS_ReduxState *rs,
                               const json_t *arguments,
                               ANASTASIS_ActionCallback cb,
                               void *cb_cls)
{
  struct BackupStartState *bss;

  if (! rs->common.have_providers)
  {
    GNUNET_break (0);
    ANASTASIS_REDUX_fail_ (rs,
                           cb,
                           cb_cls,
                           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
                           "'authentication_providers' missing");
    return NULL;
  }
  bss = GNUNET_new (struct BackupStartState);
  bss->rs = rs;
  bss->cb = cb;
  bss->cb_cls = cb_cls;
  bss->ra.cleanup_cls = bss;
  bss->ra.cleanup = &abort_backup_begin_cb;
  bss->pending = 1; /* decremented after initialization loop */

  {
    /* The /config replies add providers to the state, so take a
       snapshot of the URLs to query before starting any of them. */
    unsigned int len = rs->common.providers_len;
    char *urls[GNUNET_NZL (len)];

    for (unsigned int i = 0; i < len; i++)
      urls[i] = (ANASTASIS_RPS_DISABLED == rs->common.providers[i].status)
                ? NULL
                : GNUNET_strdup (rs->common.providers[i].url.url);
    for (unsigned int i = 0; i < len; i++)
    {
      struct BackupStartStateProviderEntry *pe;

      if (NULL == urls[i])
        continue;
      pe = GNUNET_new (struct BackupStartStateProviderEntry);
      pe->bss = bss;
      GNUNET_CONTAINER_DLL_insert (bss->pe_head,
                                   bss->pe_tail,
                                   pe);
      bss->pending++;
      pe->ra = ANASTASIS_REDUX_add_provider_to_state_ (urls[i],
                                                       rs,
                                                       &provider_added_cb,
                                                       pe);
      GNUNET_assert (NULL != pe->ra);
    }
    for (unsigned int i = 0; i < len; i++)
      GNUNET_free (urls[i]);
  }
  bss->pending--;
  if (0 == bss->pending)
  {
    providers_complete (bss);
    return NULL;
  }
  return &bss->ra;
}
