/*
  This file is part of Anastasis
  Copyright (C) 2020, 2021, 2022 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_redux_parse.c
 * @brief parse a reducer state from its JSON encoding
 * @author Christian Grothoff
 *
 * Parsing is strict: every object is checked against the closed set of
 * fields declared for it, so a typo or a field from a foreign reducer
 * implementation is an error rather than something silently carried along.
 *
 * On any failure the partially built state is handed to
 * ANASTASIS_REDUX_state_free_(), which is safe because every array length is
 * assigned before the array is populated.
 */
#include "platform.h"
#include "anastasis_redux.h"
#include "anastasis_api_redux.h"
#include "anastasis_api_redux_state.h"
#include "validation.h"
#include <taler/taler_json_lib.h>


/**
 * Buffer for the error detail returned by
 * ANASTASIS_REDUX_state_parse_().  The reducer is single-threaded (it
 * runs off the GNUnet scheduler), and the caller copies the detail into
 * its error state immediately, so one static buffer is enough.
 */
static char parse_detail[256];


/**
 * Record @a field as the location of a parse error.
 *
 * @param where name of the enclosing object
 * @param field offending field, may be NULL
 * @return pointer to the formatted detail
 */
static const char *
set_detail (const char *where,
            const char *field)
{
  if (NULL == field)
    GNUNET_snprintf (parse_detail,
                     sizeof (parse_detail),
                     "%s",
                     where);
  else
    GNUNET_snprintf (parse_detail,
                     sizeof (parse_detail),
                     "%s.%s",
                     where,
                     field);
  return parse_detail;
}


/**
 * Check that @a obj is an object and has no members beyond those listed
 * in @a allowed.
 *
 * @param obj object to check
 * @param where name of @a obj, for error reporting
 * @param allowed NULL-terminated array of permitted field names
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK if @a obj is acceptable
 */
static enum GNUNET_GenericReturnValue
check_fields (const json_t *obj,
              const char *where,
              const char *const*allowed,
              const char **detail)
{
  const char *key;
  json_t *val;

  if (! json_is_object (obj))
  {
    *detail = set_detail (where,
                          NULL);
    return GNUNET_SYSERR;
  }
  json_object_foreach ((json_t *) obj, key, val)
  {
    bool found = false;

    for (unsigned int i = 0; NULL != allowed[i]; i++)
      if (0 == strcmp (key,
                       allowed[i]))
      {
        found = true;
        break;
      }
    if (! found)
    {
      *detail = set_detail (where,
                            key);
      return GNUNET_SYSERR;
    }
  }
  return GNUNET_OK;
}


/**
 * Duplicate @a s, or return NULL if @a s is NULL.
 *
 * @param s string to duplicate
 * @return copy of @a s
 */
static char *
dup_or_null (const char *s)
{
  if (NULL == s)
    return NULL;
  return GNUNET_strdup (s);
}


/**
 * Take a reference to the i18n map @a obj, or return NULL.
 *
 * The reducer never looks inside a translation map; it only ever carries
 * it from the resource files to the user interface.  So there is nothing
 * to type here, and the object is kept verbatim.
 *
 * @param obj object mapping language tags to strings, may be NULL
 * @return new reference to @a obj, or NULL
 */
static json_t *
incref_or_null (const json_t *obj)
{
  if (NULL == obj)
    return NULL;
  return json_incref ((json_t *) obj);
}


/**
 * Parse the `continents` array.
 *
 * @param arr the array, may be NULL
 * @param[out] common state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_continents (const json_t *arr,
                  struct ANASTASIS_ReduxCommon *common,
                  const char **detail)
{
  static const char *const allowed[] = {
    "name",
    "name_i18n",
    NULL
  };
  size_t index;
  json_t *val;

  if (NULL == arr)
    return GNUNET_OK;
  if (! json_is_array (arr))
  {
    *detail = set_detail ("continents",
                          NULL);
    return GNUNET_SYSERR;
  }
  common->have_continents = true;
  common->continents_len = (unsigned int) json_array_size (arr);
  common->continents = GNUNET_new_array (common->continents_len,
                                         struct ANASTASIS_ReduxContinent);
  json_array_foreach (arr, index, val)
  {
    struct ANASTASIS_ReduxContinent *c = &common->continents[index];
    const char *name;
    const json_t *i18n = NULL;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_string ("name",
                               &name),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_object_const ("name_i18n",
                                       &i18n),
        NULL),
      GNUNET_JSON_spec_end ()
    };

    if (json_is_string (val))
    {
      /* hand-written states name continents directly */
      c->name = GNUNET_strdup (json_string_value (val));
      c->bare = true;
      continue;
    }
    if (GNUNET_OK !=
        check_fields (val,
                      "continents",
                      allowed,
                      detail))
      return GNUNET_SYSERR;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           spec,
                           NULL, NULL))
    {
      *detail = set_detail ("continents",
                            "name");
      return GNUNET_SYSERR;
    }
    c->name = GNUNET_strdup (name);
    c->name_i18n = incref_or_null (i18n);
  }
  return GNUNET_OK;
}


/**
 * Parse the `countries` array.
 *
 * @param arr the array, may be NULL
 * @param[out] common state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_countries (const json_t *arr,
                 struct ANASTASIS_ReduxCommon *common,
                 const char **detail)
{
  static const char *const allowed[] = {
    "code",
    "name",
    "continent",
    "call_code",
    "name_i18n",
    "continent_i18n",
    "currency",
    NULL
  };
  size_t index;
  json_t *val;

  if (NULL == arr)
    return GNUNET_OK;
  if (! json_is_array (arr))
  {
    *detail = set_detail ("countries",
                          NULL);
    return GNUNET_SYSERR;
  }
  common->have_countries = true;
  common->countries_len = (unsigned int) json_array_size (arr);
  common->countries = GNUNET_new_array (common->countries_len,
                                        struct ANASTASIS_ReduxCountry);
  json_array_foreach (arr, index, val)
  {
    struct ANASTASIS_ReduxCountry *c = &common->countries[index];
    const char *code;
    const char *name;
    const char *continent;
    const char *call_code = NULL;
    const char *currency = NULL;
    const json_t *name_i18n = NULL;
    const json_t *continent_i18n = NULL;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_string ("code",
                               &code),
      GNUNET_JSON_spec_string ("name",
                               &name),
      GNUNET_JSON_spec_string ("continent",
                               &continent),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("call_code",
                                 &call_code),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_object_const ("name_i18n",
                                       &name_i18n),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_object_const ("continent_i18n",
                                       &continent_i18n),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("currency",
                                 &currency),
        NULL),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        check_fields (val,
                      "countries",
                      allowed,
                      detail))
      return GNUNET_SYSERR;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           spec,
                           NULL, NULL))
    {
      *detail = set_detail ("countries",
                            NULL);
      return GNUNET_SYSERR;
    }
    c->code = GNUNET_strdup (code);
    c->name = GNUNET_strdup (name);
    c->continent = GNUNET_strdup (continent);
    c->call_code = dup_or_null (call_code);
    c->currency = dup_or_null (currency);
    c->name_i18n = incref_or_null (name_i18n);
    c->continent_i18n = incref_or_null (continent_i18n);
  }
  return GNUNET_OK;
}


/**
 * Parse the `required_attributes` array.
 *
 * @param arr the array, may be NULL
 * @param[out] common state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_required_attributes (const json_t *arr,
                           struct ANASTASIS_ReduxCommon *common,
                           const char **detail)
{
  static const char *const allowed[] = {
    "type",
    "name",
    "label",
    "widget",
    "uuid",
    "tooltip",
    "label_i18n",
    "validation-regex",
    "validation-logic",
    "autocomplete",
    "optional",
    NULL
  };
  size_t index;
  json_t *val;

  if (NULL == arr)
    return GNUNET_OK;
  if (! json_is_array (arr))
  {
    *detail = set_detail ("required_attributes",
                          NULL);
    return GNUNET_SYSERR;
  }
  common->have_required_attributes = true;
  common->required_attributes_len = (unsigned int) json_array_size (arr);
  common->required_attributes
    = GNUNET_new_array (common->required_attributes_len,
                        struct ANASTASIS_ReduxAttributeSpec);
  json_array_foreach (arr, index, val)
  {
    struct ANASTASIS_ReduxAttributeSpec *a
      = &common->required_attributes[index];
    const char *type;
    const char *name;
    const char *label;
    const char *widget;
    const char *uuid = NULL;
    const char *tooltip = NULL;
    const char *regex = NULL;
    const char *logic = NULL;
    const char *autocomplete = NULL;
    const json_t *label_i18n = NULL;
    bool no_optional;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_string ("type",
                               &type),
      GNUNET_JSON_spec_string ("name",
                               &name),
      GNUNET_JSON_spec_string ("label",
                               &label),
      GNUNET_JSON_spec_string ("widget",
                               &widget),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("uuid",
                                 &uuid),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("tooltip",
                                 &tooltip),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("validation-regex",
                                 &regex),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("validation-logic",
                                 &logic),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("autocomplete",
                                 &autocomplete),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_object_const ("label_i18n",
                                       &label_i18n),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_bool ("optional",
                               &a->optional),
        &no_optional),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        check_fields (val,
                      "required_attributes",
                      allowed,
                      detail))
      return GNUNET_SYSERR;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           spec,
                           NULL, NULL))
    {
      *detail = set_detail ("required_attributes",
                            NULL);
      return GNUNET_SYSERR;
    }
    a->type = GNUNET_strdup (type);
    a->name = GNUNET_strdup (name);
    a->label = GNUNET_strdup (label);
    a->widget = GNUNET_strdup (widget);
    a->uuid = dup_or_null (uuid);
    a->tooltip = dup_or_null (tooltip);
    a->validation_regex = dup_or_null (regex);
    if ( (NULL != logic) &&
         (NULL == ANASTASIS_REDUX_validation_lookup_ (logic)) )
    {
      /* reject unknown validation logic already here, so that it cannot even
         be round-tripped through a state we hand back to the application */
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Validation function `%s' is not known\n",
                  logic);
      *detail = set_detail ("required_attributes",
                            "validation-logic");
      return GNUNET_SYSERR;
    }
    a->validation_logic = dup_or_null (logic);
    a->autocomplete = dup_or_null (autocomplete);
    a->have_optional = ! no_optional;
    a->label_i18n = incref_or_null (label_i18n);
  }
  return GNUNET_OK;
}


/**
 * Parse one entry of the `authentication_providers` object.
 *
 * @param url the provider URL (the key)
 * @param val the provider details (the value)
 * @param[out] p provider entry to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_provider (const char *url,
                const json_t *val,
                struct ANASTASIS_ReduxProvider *p,
                const char **detail)
{
  static const char *const allowed[] = {
    "status",
    "methods",
    "annual_fee",
    "truth_upload_fee",
    "liability_limit",
    "business_name",
    "storage_limit_in_megabytes",
    "provider_salt",
    "http_status",
    "error_code",
    "currency",
    NULL
  };
  const char *status;
  struct GNUNET_JSON_Specification sspec[] = {
    GNUNET_JSON_spec_string ("status",
                             &status),
    GNUNET_JSON_spec_end ()
  };

  ANASTASIS_REDUX_provider_url_set_ (&p->url,
                                     url);
  if (GNUNET_OK !=
      check_fields (val,
                    "authentication_providers",
                    allowed,
                    detail))
    return GNUNET_SYSERR;
  if (GNUNET_OK !=
      GNUNET_JSON_parse (val,
                         sspec,
                         NULL, NULL))
  {
    *detail = set_detail ("authentication_providers",
                          "status");
    return GNUNET_SYSERR;
  }
  if (0 == strcmp (status,
                   "not-contacted"))
  {
    p->status = ANASTASIS_RPS_NOT_CONTACTED;
    return GNUNET_OK;
  }
  if (0 == strcmp (status,
                   "error"))
  {
    uint64_t ec = 0;
    struct GNUNET_JSON_Specification espec[] = {
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_uint64 ("error_code",
                                 &ec),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_uint32 ("http_status",
                                 &p->error.http_status),
        NULL),
      GNUNET_JSON_spec_end ()
    };

    p->status = ANASTASIS_RPS_ERROR;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           espec,
                           NULL, NULL))
    {
      *detail = set_detail ("authentication_providers",
                            "error_code");
      return GNUNET_SYSERR;
    }
    p->error.ec = (enum TALER_ErrorCode) ec;
    return GNUNET_OK;
  }
  if (0 == strcmp (status,
                   "ok"))
    p->status = ANASTASIS_RPS_OK;
  else if (0 == strcmp (status,
                        "disabled"))
    p->status = ANASTASIS_RPS_DISABLED;
  else
  {
    *detail = set_detail ("authentication_providers",
                          "status");
    return GNUNET_SYSERR;
  }

  /* Both "ok" and (usually) "disabled" carry the /config we obtained
     earlier; a provider that was disabled before we ever talked to it
     does not. */
  {
    struct ANASTASIS_ReduxProviderConfig *cfg = &p->config;
    const char *business_name;
    const char *currency = NULL;
    const json_t *methods;
    bool no_config = false;
    struct GNUNET_JSON_Specification cspec[] = {
      GNUNET_JSON_spec_array_const ("methods",
                                    &methods),
      TALER_JSON_spec_amount_any ("annual_fee",
                                  &cfg->annual_fee),
      TALER_JSON_spec_amount_any ("truth_upload_fee",
                                  &cfg->truth_upload_fee),
      TALER_JSON_spec_amount_any ("liability_limit",
                                  &cfg->liability_limit),
      GNUNET_JSON_spec_string ("business_name",
                               &business_name),
      GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
                               &cfg->storage_limit_in_megabytes),
      GNUNET_JSON_spec_fixed_auto ("provider_salt",
                                   &cfg->provider_salt),
      GNUNET_JSON_spec_uint32 ("http_status",
                               &cfg->http_status),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("currency",
                                 &currency),
        NULL),
      GNUNET_JSON_spec_end ()
    };
    size_t index;
    json_t *m;

    if (NULL == json_object_get (val,
                                 "methods"))
      no_config = true;
    if (no_config)
    {
      if (ANASTASIS_RPS_OK == p->status)
      {
        *detail = set_detail ("authentication_providers",
                              "methods");
        return GNUNET_SYSERR;
      }
      return GNUNET_OK;
    }
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           cspec,
                           NULL, NULL))
    {
      *detail = set_detail ("authentication_providers",
                            NULL);
      return GNUNET_SYSERR;
    }
    p->have_config = true;
    cfg->business_name = GNUNET_strdup (business_name);
    cfg->currency = dup_or_null (currency);
    cfg->methods_len = (unsigned int) json_array_size (methods);
    cfg->methods = GNUNET_new_array (cfg->methods_len,
                                     struct ANASTASIS_ReduxMethodSpec);
    json_array_foreach (methods, index, m)
    {
      static const char *const mallowed[] = {
        "type",
        "usage_fee",
        NULL
      };
      struct ANASTASIS_ReduxMethodSpec *ms = &cfg->methods[index];
      const char *type;
      struct GNUNET_JSON_Specification mspec[] = {
        GNUNET_JSON_spec_string ("type",
                                 &type),
        TALER_JSON_spec_amount_any ("usage_fee",
                                    &ms->usage_fee),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          check_fields (m,
                        "authentication_providers.methods",
                        mallowed,
                        detail))
        return GNUNET_SYSERR;
      if (GNUNET_OK !=
          GNUNET_JSON_parse (m,
                             mspec,
                             NULL, NULL))
      {
        *detail = set_detail ("authentication_providers.methods",
                              NULL);
        return GNUNET_SYSERR;
      }
      ms->type = GNUNET_strdup (type);
    }
  }
  return GNUNET_OK;
}


/**
 * Parse the `authentication_providers` object.
 *
 * @param obj the object, may be NULL
 * @param[out] common state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_providers (const json_t *obj,
                 struct ANASTASIS_ReduxCommon *common,
                 const char **detail)
{
  const char *url;
  json_t *val;
  unsigned int off = 0;

  if (NULL == obj)
    return GNUNET_OK;
  if (! json_is_object (obj))
  {
    *detail = set_detail ("authentication_providers",
                          NULL);
    return GNUNET_SYSERR;
  }
  common->have_providers = true;
  common->providers_len = (unsigned int) json_object_size (obj);
  common->providers = GNUNET_new_array (common->providers_len,
                                        struct ANASTASIS_ReduxProvider);
  json_object_foreach ((json_t *) obj, url, val)
  {
    if (GNUNET_OK !=
        parse_provider (url,
                        val,
                        &common->providers[off],
                        detail))
      return GNUNET_SYSERR;
    off++;
  }
  return GNUNET_OK;
}


/**
 * Parse the fields shared by the backup and recovery variants.
 *
 * @param json the state
 * @param[out] common state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_common (const json_t *json,
              struct ANASTASIS_ReduxCommon *common,
              const char **detail)
{
  const json_t *continents = NULL;
  const json_t *countries = NULL;
  const json_t *required_attributes = NULL;
  const json_t *providers = NULL;
  const json_t *identity_attributes = NULL;
  const json_t *currencies = NULL;
  const char *selected_continent = NULL;
  const char *selected_country = NULL;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("continents",
                                    &continents),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("countries",
                                    &countries),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("required_attributes",
                                    &required_attributes),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("authentication_providers",
                                     &providers),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("identity_attributes",
                                     &identity_attributes),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("currencies",
                                    &currencies),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("selected_continent",
                               &selected_continent),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("selected_country",
                               &selected_country),
      NULL),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (json,
                         spec,
                         NULL, NULL))
  {
    *detail = set_detail ("state",
                          NULL);
    return GNUNET_SYSERR;
  }
  common->selected_continent = dup_or_null (selected_continent);
  common->selected_country = dup_or_null (selected_country);
  common->identity_attributes = json_incref ((json_t *) identity_attributes);
  if (NULL != currencies)
  {
    size_t index;
    json_t *val;

    common->have_currencies = true;
    common->currencies_len = (unsigned int) json_array_size (currencies);
    common->currencies = GNUNET_new_array (common->currencies_len,
                                           char *);
    json_array_foreach (currencies, index, val)
    {
      if (! json_is_string (val))
      {
        *detail = set_detail ("currencies",
                              NULL);
        return GNUNET_SYSERR;
      }
      common->currencies[index] = GNUNET_strdup (json_string_value (val));
    }
  }
  if ( (GNUNET_OK !=
        parse_continents (continents,
                          common,
                          detail)) ||
       (GNUNET_OK !=
        parse_countries (countries,
                         common,
                         detail)) ||
       (GNUNET_OK !=
        parse_required_attributes (required_attributes,
                                   common,
                                   detail)) ||
       (GNUNET_OK !=
        parse_providers (providers,
                         common,
                         detail)) )
    return GNUNET_SYSERR;
  return GNUNET_OK;
}


/**
 * Parse the `authentication_methods` array.
 *
 * @param arr the array, may be NULL
 * @param[out] b backup state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_auth_methods (const json_t *arr,
                    struct ANASTASIS_ReduxBackup *b,
                    const char **detail)
{
  static const char *const allowed[] = {
    "type",
    "instructions",
    "challenge",
    "mime_type",
    NULL
  };
  size_t index;
  json_t *val;

  if (NULL == arr)
    return GNUNET_OK;
  b->have_authentication_methods = true;
  b->authentication_methods_len = (unsigned int) json_array_size (arr);
  b->authentication_methods
    = GNUNET_new_array (b->authentication_methods_len,
                        struct ANASTASIS_ReduxAuthMethod);
  json_array_foreach (arr, index, val)
  {
    struct ANASTASIS_ReduxAuthMethod *am = &b->authentication_methods[index];
    const char *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",
                               &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 (GNUNET_OK !=
        check_fields (val,
                      "authentication_methods",
                      allowed,
                      detail))
      return GNUNET_SYSERR;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           spec,
                           NULL, NULL))
    {
      *detail = set_detail ("authentication_methods",
                            NULL);
      return GNUNET_SYSERR;
    }
    am->type = GNUNET_strdup (type);
    am->instructions = GNUNET_strdup (instructions);
    am->mime_type = dup_or_null (mime_type);
    am->challenge = GNUNET_memdup (challenge,
                                   challenge_size);
    am->challenge_size = challenge_size;
    GNUNET_JSON_parse_free (spec);
  }
  return GNUNET_OK;
}


/**
 * Parse the `policies` array.
 *
 * @param arr the array, may be NULL
 * @param[out] b backup state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_policies (const json_t *arr,
                struct ANASTASIS_ReduxBackup *b,
                const char **detail)
{
  static const char *const pallowed[] = {
    "methods",
    "recovery_cost",
    NULL
  };
  static const char *const mallowed[] = {
    "authentication_method",
    "provider",
    "truth",
    NULL
  };
  size_t index;
  json_t *val;

  if (NULL == arr)
    return GNUNET_OK;
  b->have_policies = true;
  b->policies_len = (unsigned int) json_array_size (arr);
  b->policies = GNUNET_new_array (b->policies_len,
                                  struct ANASTASIS_ReduxPolicy);
  json_array_foreach (arr, index, val)
  {
    struct ANASTASIS_ReduxPolicy *p = &b->policies[index];
    const json_t *methods;
    size_t mindex;
    json_t *m;
    bool no_cost;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_array_const ("methods",
                                    &methods),
      GNUNET_JSON_spec_mark_optional (
        TALER_JSON_spec_amount_any ("recovery_cost",
                                    &p->recovery_cost),
        &no_cost),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        check_fields (val,
                      "policies",
                      pallowed,
                      detail))
      return GNUNET_SYSERR;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           spec,
                           NULL, NULL))
    {
      *detail = set_detail ("policies",
                            "methods");
      return GNUNET_SYSERR;
    }
    p->have_recovery_cost = ! no_cost;
    p->methods_len = (unsigned int) json_array_size (methods);
    p->methods = GNUNET_new_array (p->methods_len,
                                   struct ANASTASIS_ReduxPolicyMethod);
    json_array_foreach (methods, mindex, m)
    {
      struct ANASTASIS_ReduxPolicyMethod *pm = &p->methods[mindex];
      const char *provider;
      const json_t *truth = NULL;
      struct GNUNET_JSON_Specification mspec[] = {
        GNUNET_JSON_spec_uint32 ("authentication_method",
                                 &pm->authentication_method.idx),
        GNUNET_JSON_spec_string ("provider",
                                 &provider),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_object_const ("truth",
                                         &truth),
          NULL),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          check_fields (m,
                        "policies.methods",
                        mallowed,
                        detail))
        return GNUNET_SYSERR;
      if (GNUNET_OK !=
          GNUNET_JSON_parse (m,
                             mspec,
                             NULL, NULL))
      {
        *detail = set_detail ("policies.methods",
                              NULL);
        return GNUNET_SYSERR;
      }
      ANASTASIS_REDUX_provider_url_set_ (&pm->provider,
                                         provider);
      if (NULL != truth)
      {
        uint32_t us;
        struct GNUNET_JSON_Specification tspec[] = {
          GNUNET_JSON_spec_uint32 ("upload_status",
                                   &us),
          GNUNET_JSON_spec_end ()
        };

        if (GNUNET_OK !=
            GNUNET_JSON_parse (truth,
                               tspec,
                               NULL, NULL))
        {
          *detail = set_detail ("policies.methods.truth",
                                "upload_status");
          return GNUNET_SYSERR;
        }
        pm->upload_status = (enum ANASTASIS_UploadStatus) us;
        pm->truth = ANASTASIS_truth_from_json (truth);
        if (NULL == pm->truth)
        {
          *detail = set_detail ("policies.methods",
                                "truth");
          return GNUNET_SYSERR;
        }
      }
    }
  }
  return GNUNET_OK;
}


/**
 * Parse the `policy_providers` array.
 *
 * @param arr the array, may be NULL
 * @param[out] b backup state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_policy_providers (const json_t *arr,
                        struct ANASTASIS_ReduxBackup *b,
                        const char **detail)
{
  static const char *const allowed[] = {
    "provider_url",
    "payment_secret",
    NULL
  };
  size_t index;
  json_t *val;

  if (NULL == arr)
    return GNUNET_OK;
  b->have_policy_providers = true;
  b->policy_providers_len = (unsigned int) json_array_size (arr);
  b->policy_providers = GNUNET_new_array (b->policy_providers_len,
                                          struct
                                          ANASTASIS_ReduxPolicyProvider);
  json_array_foreach (arr, index, val)
  {
    struct ANASTASIS_ReduxPolicyProvider *pp = &b->policy_providers[index];
    const char *url;
    bool no_ps;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_string ("provider_url",
                               &url),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_fixed_auto ("payment_secret",
                                     &pp->payment_secret),
        &no_ps),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        check_fields (val,
                      "policy_providers",
                      allowed,
                      detail))
      return GNUNET_SYSERR;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           spec,
                           NULL, NULL))
    {
      *detail = set_detail ("policy_providers",
                            NULL);
      return GNUNET_SYSERR;
    }
    ANASTASIS_REDUX_provider_url_set_ (&pp->provider_url,
                                       url);
    pp->have_payment_secret = ! no_ps;
  }
  return GNUNET_OK;
}


/**
 * Parse the `success_details` object.
 *
 * @param obj the object, may be NULL
 * @param[out] b backup state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_success_details (const json_t *obj,
                       struct ANASTASIS_ReduxBackup *b,
                       const char **detail)
{
  static const char *const allowed[] = {
    "policy_version",
    "policy_expiration",
    NULL
  };
  const char *url;
  json_t *val;
  unsigned int off = 0;

  if (NULL == obj)
    return GNUNET_OK;
  b->have_success_details = true;
  b->success_details_len = (unsigned int) json_object_size (obj);
  b->success_details = GNUNET_new_array (b->success_details_len,
                                         struct ANASTASIS_ReduxSuccessDetail);
  json_object_foreach ((json_t *) obj, url, val)
  {
    struct ANASTASIS_ReduxSuccessDetail *sd = &b->success_details[off];
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_uint32 ("policy_version",
                               &sd->policy_version),
      GNUNET_JSON_spec_timestamp ("policy_expiration",
                                  &sd->policy_expiration),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        check_fields (val,
                      "success_details",
                      allowed,
                      detail))
      return GNUNET_SYSERR;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           spec,
                           NULL, NULL))
    {
      *detail = set_detail ("success_details",
                            NULL);
      return GNUNET_SYSERR;
    }
    ANASTASIS_REDUX_provider_url_set_ (&sd->provider_url,
                                       url);
    off++;
  }
  return GNUNET_OK;
}


/**
 * Parse the backup-specific parts of @a json.
 *
 * @param json the state
 * @param[out] b backup state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_backup (const json_t *json,
              struct ANASTASIS_ReduxBackup *b,
              const char **detail)
{
  const char *state_str;
  const json_t *auth_methods = NULL;
  const json_t *policies = NULL;
  const json_t *policy_providers = NULL;
  const json_t *upload_fees = NULL;
  const json_t *payments = NULL;
  const json_t *policy_payment_requests = NULL;
  const json_t *success_details = NULL;
  const json_t *core_secret = NULL;
  const json_t *pay_arguments = NULL;
  const char *secret_name = NULL;
  bool no_expiration;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("backup_state",
                             &state_str),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("authentication_methods",
                                    &auth_methods),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("policies",
                                    &policies),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("policy_providers",
                                    &policy_providers),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("upload_fees",
                                    &upload_fees),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("payments",
                                    &payments),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("policy_payment_requests",
                                    &policy_payment_requests),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("success_details",
                                     &success_details),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("core_secret",
                                     &core_secret),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("pay_arguments",
                                     &pay_arguments),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("secret_name",
                               &secret_name),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_timestamp ("expiration",
                                  &b->expiration),
      &no_expiration),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (json,
                         spec,
                         NULL, NULL))
  {
    *detail = set_detail ("state",
                          "backup_state");
    return GNUNET_SYSERR;
  }
  b->state = ANASTASIS_backup_state_from_string_ (state_str);
  if (ANASTASIS_BACKUP_STATE_INVALID == b->state)
  {
    *detail = set_detail ("state",
                          "backup_state");
    return GNUNET_SYSERR;
  }
  b->have_expiration = ! no_expiration;
  b->secret_name = dup_or_null (secret_name);
  b->core_secret = json_incref ((json_t *) core_secret);
  if ( (GNUNET_OK !=
        parse_auth_methods (auth_methods,
                            b,
                            detail)) ||
       (GNUNET_OK !=
        parse_policies (policies,
                        b,
                        detail)) ||
       (GNUNET_OK !=
        parse_policy_providers (policy_providers,
                                b,
                                detail)) ||
       (GNUNET_OK !=
        parse_success_details (success_details,
                               b,
                               detail)) )
    return GNUNET_SYSERR;
  if (NULL != upload_fees)
  {
    static const char *const allowed[] = {
      "fee",
      NULL
    };
    size_t index;
    json_t *val;

    b->have_upload_fees = true;
    b->upload_fees_len = (unsigned int) json_array_size (upload_fees);
    b->upload_fees = GNUNET_new_array (b->upload_fees_len,
                                       struct TALER_Amount);
    json_array_foreach (upload_fees, index, val)
    {
      struct GNUNET_JSON_Specification fspec[] = {
        TALER_JSON_spec_amount_any ("fee",
                                    &b->upload_fees[index]),
        GNUNET_JSON_spec_end ()
      };

      if ( (GNUNET_OK !=
            check_fields (val,
                          "upload_fees",
                          allowed,
                          detail)) ||
           (GNUNET_OK !=
            GNUNET_JSON_parse (val,
                               fspec,
                               NULL, NULL)) )
      {
        *detail = set_detail ("upload_fees",
                              "fee");
        return GNUNET_SYSERR;
      }
    }
  }
  if (NULL != payments)
  {
    size_t index;
    json_t *val;

    b->have_payments = true;
    b->payments_len = (unsigned int) json_array_size (payments);
    b->payments = GNUNET_new_array (b->payments_len,
                                    char *);
    json_array_foreach (payments, index, val)
    {
      if (! json_is_string (val))
      {
        *detail = set_detail ("payments",
                              NULL);
        return GNUNET_SYSERR;
      }
      b->payments[index] = GNUNET_strdup (json_string_value (val));
    }
  }
  if (NULL != policy_payment_requests)
  {
    static const char *const allowed[] = {
      "payto",
      "provider",
      NULL
    };
    size_t index;
    json_t *val;

    b->have_policy_payment_requests = true;
    b->policy_payment_requests_len
      = (unsigned int) json_array_size (policy_payment_requests);
    b->policy_payment_requests
      = GNUNET_new_array (b->policy_payment_requests_len,
                          struct ANASTASIS_ReduxPolicyPaymentRequest);
    json_array_foreach (policy_payment_requests, index, val)
    {
      struct ANASTASIS_ReduxPolicyPaymentRequest *ppr
        = &b->policy_payment_requests[index];
      const char *payto;
      const char *provider;
      struct GNUNET_JSON_Specification pspec[] = {
        GNUNET_JSON_spec_string ("payto",
                                 &payto),
        GNUNET_JSON_spec_string ("provider",
                                 &provider),
        GNUNET_JSON_spec_end ()
      };

      if ( (GNUNET_OK !=
            check_fields (val,
                          "policy_payment_requests",
                          allowed,
                          detail)) ||
           (GNUNET_OK !=
            GNUNET_JSON_parse (val,
                               pspec,
                               NULL, NULL)) )
      {
        *detail = set_detail ("policy_payment_requests",
                              NULL);
        return GNUNET_SYSERR;
      }
      ppr->payto = GNUNET_strdup (payto);
      ANASTASIS_REDUX_provider_url_set_ (&ppr->provider,
                                         provider);
    }
  }
  if (NULL != pay_arguments)
  {
    static const char *const allowed[] = {
      "timeout",
      NULL
    };
    bool no_timeout;
    struct GNUNET_JSON_Specification pspec[] = {
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_relative_time ("timeout",
                                        &b->pay_arguments.timeout),
        &no_timeout),
      GNUNET_JSON_spec_end ()
    };

    if ( (GNUNET_OK !=
          check_fields (pay_arguments,
                        "pay_arguments",
                        allowed,
                        detail)) ||
         (GNUNET_OK !=
          GNUNET_JSON_parse (pay_arguments,
                             pspec,
                             NULL, NULL)) )
    {
      *detail = set_detail ("pay_arguments",
                            "timeout");
      return GNUNET_SYSERR;
    }
    b->have_pay_arguments = true;
    b->pay_arguments.have_timeout = ! no_timeout;
  }
  return GNUNET_OK;
}


/**
 * Parse the `recovery_information` object.
 *
 * @param obj the object
 * @param[out] rr recovery state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_recovery_info (const json_t *obj,
                     struct ANASTASIS_ReduxRecovery *rr,
                     const char **detail)
{
  static const char *const allowed[] = {
    "challenges",
    "policies",
    "secret_name",
    "provider_url",
    "version",
    NULL
  };
  static const char *const callowed[] = {
    "uuid",
    "uuid-display",
    "type",
    "instructions",
    "answer",
    "payment_secret",
    NULL
  };
  static const char *const pallowed[] = {
    "uuid",
    NULL
  };
  struct ANASTASIS_ReduxRecoveryInfo *ri;
  const json_t *challenges;
  const json_t *policies;
  const char *secret_name = NULL;
  const char *provider_url;
  size_t index;
  json_t *val;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_array_const ("challenges",
                                  &challenges),
    GNUNET_JSON_spec_array_const ("policies",
                                  &policies),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("secret_name",
                               &secret_name),
      NULL),
    GNUNET_JSON_spec_string ("provider_url",
                             &provider_url),
    GNUNET_JSON_spec_end ()
  };

  ri = GNUNET_new (struct ANASTASIS_ReduxRecoveryInfo);
  rr->ri = ri;
  if (GNUNET_OK !=
      check_fields (obj,
                    "recovery_information",
                    allowed,
                    detail))
    return GNUNET_SYSERR;
  {
    struct GNUNET_JSON_Specification vspec[] = {
      GNUNET_JSON_spec_uint32 ("version",
                               &ri->version),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        GNUNET_JSON_parse (obj,
                           vspec,
                           NULL, NULL))
    {
      *detail = set_detail ("recovery_information",
                            "version");
      return GNUNET_SYSERR;
    }
  }
  if (GNUNET_OK !=
      GNUNET_JSON_parse (obj,
                         spec,
                         NULL, NULL))
  {
    *detail = set_detail ("recovery_information",
                          NULL);
    return GNUNET_SYSERR;
  }
  ri->secret_name = dup_or_null (secret_name);
  ANASTASIS_REDUX_provider_url_set_ (&ri->provider_url,
                                     provider_url);
  ri->challenges_len = (unsigned int) json_array_size (challenges);
  ri->challenges = GNUNET_new_array (ri->challenges_len,
                                     struct ANASTASIS_ReduxChallengeInfo);
  json_array_foreach (challenges, index, val)
  {
    struct ANASTASIS_ReduxChallengeInfo *ci = &ri->challenges[index];
    const char *type;
    const char *instructions;
    const char *answer = NULL;
    bool no_ps;
    struct GNUNET_JSON_Specification cspec[] = {
      GNUNET_JSON_spec_fixed_auto ("uuid",
                                   &ci->uuid),
      GNUNET_JSON_spec_string ("type",
                               &type),
      GNUNET_JSON_spec_string ("instructions",
                               &instructions),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("answer",
                                 &answer),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_fixed_auto ("payment_secret",
                                     &ci->payment_secret),
        &no_ps),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        check_fields (val,
                      "recovery_information.challenges",
                      callowed,
                      detail))
      return GNUNET_SYSERR;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           cspec,
                           NULL, NULL))
    {
      *detail = set_detail ("recovery_information.challenges",
                            NULL);
      return GNUNET_SYSERR;
    }
    ci->type = GNUNET_strdup (type);
    ci->instructions = GNUNET_strdup (instructions);
    ci->answer = dup_or_null (answer);
    ci->have_payment_secret = ! no_ps;
  }
  ri->policies_len = (unsigned int) json_array_size (policies);
  ri->policies = GNUNET_new_array (ri->policies_len,
                                   struct ANASTASIS_ReduxRecoveryPolicy);
  json_array_foreach (policies, index, val)
  {
    struct ANASTASIS_ReduxRecoveryPolicy *p = &ri->policies[index];
    size_t uindex;
    json_t *u;

    if (! json_is_array (val))
    {
      *detail = set_detail ("recovery_information",
                            "policies");
      return GNUNET_SYSERR;
    }
    p->uuids_len = (unsigned int) json_array_size (val);
    p->uuids = GNUNET_new_array (p->uuids_len,
                                 struct ANASTASIS_CRYPTO_TruthUUIDP);
    json_array_foreach (val, uindex, u)
    {
      struct GNUNET_JSON_Specification uspec[] = {
        GNUNET_JSON_spec_fixed_auto ("uuid",
                                     &p->uuids[uindex]),
        GNUNET_JSON_spec_end ()
      };

      if ( (GNUNET_OK !=
            check_fields (u,
                          "recovery_information.policies",
                          pallowed,
                          detail)) ||
           (GNUNET_OK !=
            GNUNET_JSON_parse (u,
                               uspec,
                               NULL, NULL)) )
      {
        *detail = set_detail ("recovery_information.policies",
                              "uuid");
        return GNUNET_SYSERR;
      }
    }
  }
  return GNUNET_OK;
}


/**
 * Parse one entry of the `challenge_feedback` object.
 *
 * @param val the feedback details
 * @param[out] fb feedback entry to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_feedback_details (const json_t *val,
                        struct ANASTASIS_ReduxFeedback *fb,
                        const char **detail)
{
  static const char *const allowed[] = {
    "state",
    "display_hint",
    "filename",
    "address_hint",
    "taler_pay_uri",
    "provider",
    "payment_secret",
    "http_status",
    "error_code",
    "target_iban",
    "target_business_name",
    "wire_transfer_subject",
    "challenge_amount",
    "request_limit",
    "request_frequency",
    NULL
  };
  const char *state;
  const char *display_hint = NULL;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("state",
                             &state),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("display_hint",
                               &display_hint),
      NULL),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      check_fields (val,
                    "challenge_feedback",
                    allowed,
                    detail))
    return GNUNET_SYSERR;
  if (GNUNET_OK !=
      GNUNET_JSON_parse (val,
                         spec,
                         NULL, NULL))
  {
    *detail = set_detail ("challenge_feedback",
                          "state");
    return GNUNET_SYSERR;
  }
  fb->display_hint = dup_or_null (display_hint);
  if (0 == strcmp (state,
                   "code-in-file"))
  {
    const char *filename;
    struct GNUNET_JSON_Specification dspec[] = {
      GNUNET_JSON_spec_string ("filename",
                               &filename),
      GNUNET_JSON_spec_end ()
    };

    fb->status = ANASTASIS_RFS_CODE_IN_FILE;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           dspec,
                           NULL, NULL))
    {
      *detail = set_detail ("challenge_feedback",
                            "filename");
      return GNUNET_SYSERR;
    }
    fb->details.code_in_file.filename = GNUNET_strdup (filename);
    return GNUNET_OK;
  }
  if (0 == strcmp (state,
                   "send-to-address"))
  {
    const char *address_hint = NULL;
    struct GNUNET_JSON_Specification dspec[] = {
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("address_hint",
                                 &address_hint),
        NULL),
      GNUNET_JSON_spec_end ()
    };

    fb->status = ANASTASIS_RFS_SEND_TO_ADDRESS;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           dspec,
                           NULL, NULL))
    {
      *detail = set_detail ("challenge_feedback",
                            "address_hint");
      return GNUNET_SYSERR;
    }
    fb->details.send_to_address.address_hint = dup_or_null (address_hint);
    return GNUNET_OK;
  }
  if (0 == strcmp (state,
                   "taler-payment"))
  {
    const char *uri;
    const char *provider;
    struct GNUNET_JSON_Specification dspec[] = {
      GNUNET_JSON_spec_string ("taler_pay_uri",
                               &uri),
      GNUNET_JSON_spec_string ("provider",
                               &provider),
      GNUNET_JSON_spec_fixed_auto ("payment_secret",
                                   &fb->details.taler_payment.payment_secret),
      GNUNET_JSON_spec_end ()
    };

    fb->status = ANASTASIS_RFS_TALER_PAYMENT;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           dspec,
                           NULL, NULL))
    {
      *detail = set_detail ("challenge_feedback",
                            "taler_pay_uri");
      return GNUNET_SYSERR;
    }
    fb->details.taler_payment.taler_pay_uri = GNUNET_strdup (uri);
    ANASTASIS_REDUX_provider_url_set_ (&fb->details.taler_payment.provider,
                                       provider);
    return GNUNET_OK;
  }
  if ( (0 == strcmp (state,
                     "server-failure")) ||
       (0 == strcmp (state,
                     "truth-unknown")) )
  {
    uint64_t ec;
    struct GNUNET_JSON_Specification dspec[] = {
      GNUNET_JSON_spec_uint64 ("error_code",
                               &ec),
      GNUNET_JSON_spec_uint32 ("http_status",
                               &fb->details.server_failure.http_status),
      GNUNET_JSON_spec_end ()
    };

    fb->status = (0 == strcmp (state,
                               "server-failure"))
      ? ANASTASIS_RFS_SERVER_FAILURE
      : ANASTASIS_RFS_TRUTH_UNKNOWN;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           dspec,
                           NULL, NULL))
    {
      *detail = set_detail ("challenge_feedback",
                            "error_code");
      return GNUNET_SYSERR;
    }
    fb->details.server_failure.ec = (enum TALER_ErrorCode) ec;
    return GNUNET_OK;
  }
  if (0 == strcmp (state,
                   "iban-instructions"))
  {
    const char *iban;
    const char *business_name;
    const char *subject;
    struct GNUNET_JSON_Specification dspec[] = {
      GNUNET_JSON_spec_string ("target_iban",
                               &iban),
      GNUNET_JSON_spec_string ("target_business_name",
                               &business_name),
      GNUNET_JSON_spec_string ("wire_transfer_subject",
                               &subject),
      TALER_JSON_spec_amount_any ("challenge_amount",
                                  &fb->details.iban_instructions.amount),
      GNUNET_JSON_spec_end ()
    };

    fb->status = ANASTASIS_RFS_IBAN_INSTRUCTIONS;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           dspec,
                           NULL, NULL))
    {
      *detail = set_detail ("challenge_feedback",
                            "target_iban");
      return GNUNET_SYSERR;
    }
    fb->details.iban_instructions.target_iban = GNUNET_strdup (iban);
    fb->details.iban_instructions.target_business_name
      = GNUNET_strdup (business_name);
    fb->details.iban_instructions.wire_transfer_subject
      = GNUNET_strdup (subject);
    return GNUNET_OK;
  }
  if (0 == strcmp (state,
                   "solved"))
  {
    fb->status = ANASTASIS_RFS_SOLVED;
    return GNUNET_OK;
  }
  if (0 == strcmp (state,
                   "incorrect-answer"))
  {
    uint64_t ec;
    struct GNUNET_JSON_Specification dspec[] = {
      GNUNET_JSON_spec_uint64 ("error_code",
                               &ec),
      GNUNET_JSON_spec_end ()
    };

    fb->status = ANASTASIS_RFS_INCORRECT_ANSWER;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           dspec,
                           NULL, NULL))
    {
      *detail = set_detail ("challenge_feedback",
                            "error_code");
      return GNUNET_SYSERR;
    }
    fb->details.incorrect_answer.ec = (enum TALER_ErrorCode) ec;
    return GNUNET_OK;
  }
  if (0 == strcmp (state,
                   "rate-limit-exceeded"))
  {
    uint64_t ec;
    struct GNUNET_JSON_Specification dspec[] = {
      GNUNET_JSON_spec_uint64 ("error_code",
                               &ec),
      GNUNET_JSON_spec_uint64 ("request_limit",
                               &fb->details.rate_limit_exceeded.request_limit),
      GNUNET_JSON_spec_relative_time (
        "request_frequency",
        &fb->details.rate_limit_exceeded.request_frequency),
      GNUNET_JSON_spec_end ()
    };

    fb->status = ANASTASIS_RFS_RATE_LIMIT_EXCEEDED;
    if (GNUNET_OK !=
        GNUNET_JSON_parse (val,
                           dspec,
                           NULL, NULL))
    {
      *detail = set_detail ("challenge_feedback",
                            "request_limit");
      return GNUNET_SYSERR;
    }
    fb->details.rate_limit_exceeded.ec = (enum TALER_ErrorCode) ec;
    return GNUNET_OK;
  }
  *detail = set_detail ("challenge_feedback",
                        "state");
  return GNUNET_SYSERR;
}


/**
 * Parse the recovery-specific parts of @a json.
 *
 * @param json the state
 * @param[out] rr recovery state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_recovery (const json_t *json,
                struct ANASTASIS_ReduxRecovery *rr,
                const char **detail)
{
  const char *state_str;
  const json_t *recovery_information = NULL;
  const json_t *recovery_document = NULL;
  const json_t *challenge_feedback = NULL;
  const json_t *core_secret = NULL;
  bool no_selected;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("recovery_state",
                             &state_str),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("recovery_information",
                                     &recovery_information),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("recovery_document",
                                     &recovery_document),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("challenge_feedback",
                                     &challenge_feedback),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("core_secret",
                                     &core_secret),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_fixed_auto ("selected_challenge_uuid",
                                   &rr->selected_challenge),
      &no_selected),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (json,
                         spec,
                         NULL, NULL))
  {
    *detail = set_detail ("state",
                          "recovery_state");
    return GNUNET_SYSERR;
  }
  rr->state = ANASTASIS_recovery_state_from_string_ (state_str);
  if (ANASTASIS_RECOVERY_STATE_INVALID == rr->state)
  {
    *detail = set_detail ("state",
                          "recovery_state");
    return GNUNET_SYSERR;
  }
  rr->have_selected_challenge = ! no_selected;
  rr->core_secret = json_incref ((json_t *) core_secret);
  if (NULL != recovery_information)
  {
    if (GNUNET_OK !=
        parse_recovery_info (recovery_information,
                             rr,
                             detail))
      return GNUNET_SYSERR;
  }
  if (NULL != recovery_document)
  {
    /* Hand the blob straight to src/lib: it owns that schema.  The
       handle comes back inert -- no callbacks, no network activity --
       until a handler calls ANASTASIS_recovery_resume(). */
    rr->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
                                            recovery_document);
    if (NULL == rr->r)
    {
      *detail = set_detail ("state",
                            "recovery_document");
      return GNUNET_SYSERR;
    }
  }
  if (NULL != challenge_feedback)
  {
    const char *uuid_str;
    json_t *val;
    unsigned int off = 0;

    rr->have_challenge_feedback = true;
    rr->challenge_feedback_len
      = (unsigned int) json_object_size (challenge_feedback);
    rr->challenge_feedback
      = GNUNET_new_array (rr->challenge_feedback_len,
                          struct ANASTASIS_ReduxFeedback);
    json_object_foreach ((json_t *) challenge_feedback, uuid_str, val)
    {
      struct ANASTASIS_ReduxFeedback *fb = &rr->challenge_feedback[off];

      if (GNUNET_OK !=
          GNUNET_STRINGS_string_to_data (uuid_str,
                                         strlen (uuid_str),
                                         &fb->uuid,
                                         sizeof (fb->uuid)))
      {
        *detail = set_detail ("challenge_feedback",
                              uuid_str);
        return GNUNET_SYSERR;
      }
      if (GNUNET_OK !=
          parse_feedback_details (val,
                                  fb,
                                  detail))
        return GNUNET_SYSERR;
      off++;
    }
  }
  return GNUNET_OK;
}


/**
 * Parse an `error` state.
 *
 * @param json the state
 * @param[out] e error state to fill in
 * @param[out] detail set to the offending field on failure
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_error (const json_t *json,
             struct ANASTASIS_ReduxError *e,
             const char **detail)
{
  static const char *const allowed[] = {
    "reducer_type",
    "code",
    "hint",
    "detail",
    NULL
  };
  uint64_t code;
  const char *hint = NULL;
  const char *dtl = NULL;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_uint64 ("code",
                             &code),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("hint",
                               &hint),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("detail",
                               &dtl),
      NULL),
    GNUNET_JSON_spec_end ()
  };

  if ( (GNUNET_OK !=
        check_fields (json,
                      "state",
                      allowed,
                      detail)) ||
       (GNUNET_OK !=
        GNUNET_JSON_parse (json,
                           spec,
                           NULL, NULL)) )
  {
    *detail = set_detail ("state",
                          "code");
    return GNUNET_SYSERR;
  }
  e->code = (enum TALER_ErrorCode) code;
  e->hint = dup_or_null (hint);
  e->detail = dup_or_null (dtl);
  return GNUNET_OK;
}


/**
 * Field names permitted at the top level of a backup state.
 */
static const char *const backup_allowed[] = {
  "reducer_type",
  "backup_state",
  "continents",
  "countries",
  "required_attributes",
  "authentication_providers",
  "identity_attributes",
  "currencies",
  "selected_continent",
  "selected_country",
  "authentication_methods",
  "policies",
  "policy_providers",
  "upload_fees",
  "payments",
  "policy_payment_requests",
  "success_details",
  "core_secret",
  "secret_name",
  "expiration",
  "pay_arguments",
  NULL
};


/**
 * Field names permitted at the top level of a recovery state.
 */
static const char *const recovery_allowed[] = {
  "reducer_type",
  "recovery_state",
  "continents",
  "countries",
  "required_attributes",
  "authentication_providers",
  "identity_attributes",
  "currencies",
  "selected_continent",
  "selected_country",
  "recovery_information",
  "recovery_document",
  "challenge_feedback",
  "selected_challenge_uuid",
  "core_secret",
  NULL
};


struct ANASTASIS_ReduxState *
ANASTASIS_REDUX_state_parse_ (const json_t *json,
                              enum TALER_ErrorCode *ec,
                              const char **detail)
{
  struct ANASTASIS_ReduxState *rs;
  const char *type;

  *ec = TALER_EC_ANASTASIS_REDUCER_STATE_INVALID;
  *detail = NULL;
  if (! json_is_object (json))
  {
    *detail = set_detail ("state",
                          NULL);
    return NULL;
  }
  /* The reducer type is not always given explicitly: states written by
     older versions (and the initial states of the CLI tests) are
     identified by which of the two state fields is present. */
  type = json_string_value (json_object_get (json,
                                             "reducer_type"));
  if (NULL == type)
  {
    if (NULL != json_object_get (json,
                                 "backup_state"))
      type = "backup";
    else if (NULL != json_object_get (json,
                                      "recovery_state"))
      type = "recovery";
    else
    {
      *detail = set_detail ("state",
                            "reducer_type");
      return NULL;
    }
  }
  rs = GNUNET_new (struct ANASTASIS_ReduxState);
  if (0 == strcmp (type,
                   "backup"))
  {
    rs->type = ANASTASIS_RT_BACKUP;
    if ( (GNUNET_OK !=
          check_fields (json,
                        "state",
                        backup_allowed,
                        detail)) ||
         (GNUNET_OK !=
          parse_common (json,
                        &rs->common,
                        detail)) ||
         (GNUNET_OK !=
          parse_backup (json,
                        &rs->details.backup,
                        detail)) )
    {
      ANASTASIS_REDUX_state_free_ (rs);
      return NULL;
    }
    return rs;
  }
  if (0 == strcmp (type,
                   "recovery"))
  {
    rs->type = ANASTASIS_RT_RECOVERY;
    if ( (GNUNET_OK !=
          check_fields (json,
                        "state",
                        recovery_allowed,
                        detail)) ||
         (GNUNET_OK !=
          parse_common (json,
                        &rs->common,
                        detail)) ||
         (GNUNET_OK !=
          parse_recovery (json,
                          &rs->details.recovery,
                          detail)) )
    {
      ANASTASIS_REDUX_state_free_ (rs);
      return NULL;
    }
    return rs;
  }
  if (0 == strcmp (type,
                   "error"))
  {
    rs->type = ANASTASIS_RT_ERROR;
    if (GNUNET_OK !=
        parse_error (json,
                     &rs->details.error,
                     detail))
    {
      ANASTASIS_REDUX_state_free_ (rs);
      return NULL;
    }
    return rs;
  }
  *detail = set_detail ("state",
                        "reducer_type");
  ANASTASIS_REDUX_state_free_ (rs);
  return NULL;
}


enum GNUNET_GenericReturnValue
ANASTASIS_REDUX_countries_set_ (struct ANASTASIS_ReduxCommon *common,
                                const json_t *arr,
                                const char **detail)
{
  ANASTASIS_REDUX_countries_clear_ (common);
  if (GNUNET_OK !=
      parse_countries (arr,
                       common,
                       detail))
  {
    ANASTASIS_REDUX_countries_clear_ (common);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
ANASTASIS_REDUX_required_attributes_set_ (struct ANASTASIS_ReduxCommon *common,
                                          const json_t *arr,
                                          const char **detail)
{
  ANASTASIS_REDUX_required_attributes_clear_ (common);
  if (GNUNET_OK !=
      parse_required_attributes (arr,
                                 common,
                                 detail))
  {
    ANASTASIS_REDUX_required_attributes_clear_ (common);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
ANASTASIS_REDUX_provider_set_ (struct ANASTASIS_ReduxCommon *common,
                               const char *url,
                               const json_t *val,
                               const char **detail)
{
  struct ANASTASIS_ReduxProvider *p;

  p = ANASTASIS_REDUX_provider_get_ (common,
                                     url);
  if (p->have_config)
    ANASTASIS_REDUX_provider_config_clear_ (&p->config);
  p->have_config = false;
  p->status = ANASTASIS_RPS_NOT_CONTACTED;
  memset (&p->error,
          0,
          sizeof (p->error));
  if (GNUNET_OK !=
      parse_provider (url,
                      val,
                      p,
                      detail))
  {
    /* Leave a well-defined, if uninformative, entry behind: the caller
       reports the error, but the state must stay consistent. */
    if (p->have_config)
      ANASTASIS_REDUX_provider_config_clear_ (&p->config);
    p->have_config = false;
    p->status = ANASTASIS_RPS_NOT_CONTACTED;
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/* end of anastasis_api_redux_parse.c */
