commit 7615eedae52510f3d56058350b62934f7e720a31
parent aa9e1d0e40684eda203846981f2b24e17043f003
Author: Christian Grothoff <christian@grothoff.org>
Date: Wed, 29 Jul 2026 11:01:11 +0200
add parser/serializer for redux state, remove dead truth_lifetime
Diffstat:
12 files changed, 5316 insertions(+), 56 deletions(-)
diff --git a/doc/anastasis.texi b/doc/anastasis.texi
@@ -3411,7 +3411,6 @@ providers that accept payments in the selected currency:
"truth_upload_fee": "EUR:4.99",
"liability_limit": "EUR:1",
"currency": "EUR",
- "truth_lifetime": @{ "d_ms" : 50000000 @},
"storage_limit_in_megabytes": 1,
"provider_name": "Anastasis 4",
"salt": "CXAPCKSH9D3MYJTS9536RHJHCW"
@@ -3428,7 +3427,6 @@ providers that accept payments in the selected currency:
"truth_upload_fee": "EUR:3.99",
"liability_limit": "EUR:1",
"currency": "EUR",
- "truth_lifetime": @{ "d_ms" : 50000000 @},
"storage_limit_in_megabytes": 1,
"provider_name": "Anastasis 4",
"salt": "CXAPCKSH9D3MYJTS9536RHJHCW"
@@ -3606,7 +3604,6 @@ port 8088 was now added, and on 8090 is disabled:
"truth_upload_fee": "EUR:3.99",
"liability_limit": "EUR:1",
"currency": "EUR",
- "truth_lifetime": @{ "d_ms" : 50000000 @},
"storage_limit_in_megabytes": 1,
"provider_name": "Anastasis 4",
"salt": "CXAPCKSH9D3MYJTS9536RHJHCW"
@@ -3622,7 +3619,6 @@ port 8088 was now added, and on 8090 is disabled:
"truth_upload_fee": "EUR:3.99",
"liability_limit": "EUR:1",
"currency": "EUR",
- "truth_lifetime": @{ "d_ms" : 50000000 @},
"storage_limit_in_megabytes": 1,
"provider_name": "Anastasis 42",
"salt": "BXAPCKSH9D3MYJTS9536RHJHCX"
diff --git a/doc/sphinx/reducer.rst b/doc/sphinx/reducer.rst
@@ -409,7 +409,6 @@ providers that accept payments in the selected currency:
"truth_upload_fee": "EUR:4.99",
"liability_limit": "EUR:1",
"currency": "EUR",
- "truth_lifetime": { "d_ms" : 50000000 },
"storage_limit_in_megabytes": 1,
"provider_name": "Anastasis 4",
"salt": "CXAPCKSH9D3MYJTS9536RHJHCW"
@@ -426,7 +425,6 @@ providers that accept payments in the selected currency:
"truth_upload_fee": "EUR:3.99",
"liability_limit": "EUR:1",
"currency": "EUR",
- "truth_lifetime": { "d_ms" : 50000000 },
"storage_limit_in_megabytes": 1,
"provider_name": "Anastasis 4",
"salt": "CXAPCKSH9D3MYJTS9536RHJHCW"
@@ -549,7 +547,6 @@ port 8088 was now added, and on 8090 is disabled:
"truth_upload_fee": "EUR:3.99",
"liability_limit": "EUR:1",
"currency": "EUR",
- "truth_lifetime": { "d_ms" : 50000000 },
"storage_limit_in_megabytes": 1,
"provider_name": "Anastasis 4",
"salt": "CXAPCKSH9D3MYJTS9536RHJHCW"
@@ -565,7 +562,6 @@ port 8088 was now added, and on 8090 is disabled:
"truth_upload_fee": "EUR:3.99",
"liability_limit": "EUR:1",
"currency": "EUR",
- "truth_lifetime": { "d_ms" : 50000000 },
"storage_limit_in_megabytes": 1,
"provider_name": "Anastasis 42",
"salt": "BXAPCKSH9D3MYJTS9536RHJHCX"
diff --git a/src/cli/resources/03-backup.json b/src/cli/resources/03-backup.json
@@ -32,9 +32,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"business_name": "Data loss #1 Inc.",
"storage_limit_in_megabytes": 1,
"provider_salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0",
@@ -51,9 +48,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"business_name": "Data loss #2 Inc.",
"storage_limit_in_megabytes": 1,
"provider_salt": "D378FWXHJB8JHPQFQRZGGV9PWG",
@@ -70,9 +64,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"business_name": "Data loss #3 Inc.",
"storage_limit_in_megabytes": 1,
"provider_salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR",
@@ -89,9 +80,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"business_name": "Data loss #4 Inc.",
"storage_limit_in_megabytes": 1,
"provider_salt": "PN0VJF6KDSBYN40SGRCEXPB07M",
diff --git a/src/cli/resources/04-backup.json b/src/cli/resources/04-backup.json
@@ -32,9 +32,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"business_name": "Data loss #1 Inc.",
"storage_limit_in_megabytes": 1,
"provider_salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0",
@@ -51,9 +48,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"business_name": "Data loss #2 Inc.",
"storage_limit_in_megabytes": 1,
"provider_salt": "D378FWXHJB8JHPQFQRZGGV9PWG",
@@ -70,9 +64,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"business_name": "Data loss #3 Inc.",
"storage_limit_in_megabytes": 1,
"provider_salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR",
@@ -89,9 +80,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"business_name": "Data loss #4 Inc.",
"storage_limit_in_megabytes": 1,
"provider_salt": "PN0VJF6KDSBYN40SGRCEXPB07M",
diff --git a/src/cli/resources/05-backup.json b/src/cli/resources/05-backup.json
@@ -51,9 +51,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"currency": "TESTKUDOS",
"business_name": "Data loss #1 Inc.",
"storage_limit_in_megabytes": 1,
@@ -71,9 +68,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"currency": "TESTKUDOS",
"business_name": "Data loss #2 Inc.",
"storage_limit_in_megabytes": 1,
@@ -91,9 +85,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"currency": "TESTKUDOS",
"business_name": "Data loss #3 Inc.",
"storage_limit_in_megabytes": 1,
@@ -111,9 +102,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"currency": "TESTKUDOS",
"business_name": "Data loss #4 Inc.",
"storage_limit_in_megabytes": 1,
diff --git a/src/cli/resources/06-backup.json b/src/cli/resources/06-backup.json
@@ -51,9 +51,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"currency": "TESTKUDOS",
"business_name": "Data loss #1 Inc.",
"storage_limit_in_megabytes": 1,
@@ -71,9 +68,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"currency": "TESTKUDOS",
"business_name": "Data loss #2 Inc.",
"storage_limit_in_megabytes": 1,
@@ -91,9 +85,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"currency": "TESTKUDOS",
"business_name": "Data loss #3 Inc.",
"storage_limit_in_megabytes": 1,
@@ -111,9 +102,6 @@
"annual_fee": "TESTKUDOS:4.99",
"truth_upload_fee": "TESTKUDOS:0.01",
"liability_limit": "TESTKUDOS:1",
- "truth_lifetime": {
- "d_ms": 63115200000
- },
"currency": "TESTKUDOS",
"business_name": "Data loss #4 Inc.",
"storage_limit_in_megabytes": 1,
diff --git a/src/reducer/anastasis_api_redux_free.c b/src/reducer/anastasis_api_redux_free.c
@@ -0,0 +1,284 @@
+/*
+ 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_free.c
+ * @brief release a parsed reducer state
+ * @author Christian Grothoff
+ *
+ * Every structure here is allocated zeroed (GNUNET_new(_array)) and every
+ * array's length field is set before the array is filled, so this code also
+ * has to cope with partially initialized states: the parser simply hands a
+ * half-built state to ANASTASIS_REDUX_state_free_() when it gives up.
+ */
+#include "platform.h"
+#include "anastasis_redux.h"
+#include "anastasis_api_redux.h"
+#include "anastasis_api_redux_state.h"
+
+
+void
+ANASTASIS_REDUX_provider_url_clear_ (struct ANASTASIS_ProviderUrl *pu)
+{
+ GNUNET_free (pu->url);
+}
+
+
+void
+ANASTASIS_REDUX_providers_clear_ (struct ANASTASIS_ReduxCommon *common)
+{
+ for (unsigned int i = 0; i < common->providers_len; i++)
+ {
+ struct ANASTASIS_ReduxProvider *p = &common->providers[i];
+
+ ANASTASIS_REDUX_provider_url_clear_ (&p->url);
+ if (p->have_config)
+ {
+ for (unsigned int j = 0; j < p->config.methods_len; j++)
+ GNUNET_free (p->config.methods[j].type);
+ GNUNET_free (p->config.methods);
+ GNUNET_free (p->config.business_name);
+ GNUNET_free (p->config.currency);
+ }
+ }
+ GNUNET_free (common->providers);
+ common->providers = NULL;
+ common->providers_len = 0;
+}
+
+
+/**
+ * Free the contents of @a common.
+ *
+ * @param[in,out] common state to free
+ */
+static void
+free_common (struct ANASTASIS_ReduxCommon *common)
+{
+ for (unsigned int i = 0; i < common->continents_len; i++)
+ {
+ struct ANASTASIS_ReduxContinent *c = &common->continents[i];
+
+ GNUNET_free (c->name);
+ json_decref (c->name_i18n);
+ }
+ GNUNET_free (common->continents);
+ for (unsigned int i = 0; i < common->countries_len; i++)
+ {
+ struct ANASTASIS_ReduxCountry *c = &common->countries[i];
+
+ GNUNET_free (c->code);
+ GNUNET_free (c->name);
+ GNUNET_free (c->continent);
+ GNUNET_free (c->call_code);
+ GNUNET_free (c->currency);
+ json_decref (c->name_i18n);
+ json_decref (c->continent_i18n);
+ }
+ GNUNET_free (common->countries);
+ for (unsigned int i = 0; i < common->required_attributes_len; i++)
+ {
+ struct ANASTASIS_ReduxAttributeSpec *a = &common->required_attributes[i];
+
+ GNUNET_free (a->type);
+ GNUNET_free (a->name);
+ GNUNET_free (a->label);
+ GNUNET_free (a->widget);
+ GNUNET_free (a->uuid);
+ GNUNET_free (a->tooltip);
+ GNUNET_free (a->validation_regex);
+ GNUNET_free (a->validation_logic);
+ GNUNET_free (a->autocomplete);
+ json_decref (a->label_i18n);
+ }
+ GNUNET_free (common->required_attributes);
+ ANASTASIS_REDUX_providers_clear_ (common);
+ for (unsigned int i = 0; i < common->currencies_len; i++)
+ GNUNET_free (common->currencies[i]);
+ GNUNET_free (common->currencies);
+ json_decref (common->identity_attributes);
+ GNUNET_free (common->selected_continent);
+ GNUNET_free (common->selected_country);
+}
+
+
+/**
+ * Free the contents of @a backup.
+ *
+ * @param[in,out] backup state to free
+ */
+static void
+free_backup (struct ANASTASIS_ReduxBackup *backup)
+{
+ for (unsigned int i = 0; i < backup->authentication_methods_len; i++)
+ {
+ struct ANASTASIS_ReduxAuthMethod *am = &backup->authentication_methods[i];
+
+ GNUNET_free (am->type);
+ GNUNET_free (am->instructions);
+ GNUNET_free (am->challenge);
+ GNUNET_free (am->mime_type);
+ }
+ GNUNET_free (backup->authentication_methods);
+ for (unsigned int i = 0; i < backup->policies_len; i++)
+ {
+ struct ANASTASIS_ReduxPolicy *p = &backup->policies[i];
+
+ for (unsigned int j = 0; j < p->methods_len; j++)
+ {
+ struct ANASTASIS_ReduxPolicyMethod *pm = &p->methods[j];
+
+ ANASTASIS_REDUX_provider_url_clear_ (&pm->provider);
+ if (NULL != pm->truth)
+ ANASTASIS_truth_free (pm->truth);
+ }
+ GNUNET_free (p->methods);
+ }
+ GNUNET_free (backup->policies);
+ for (unsigned int i = 0; i < backup->policy_providers_len; i++)
+ ANASTASIS_REDUX_provider_url_clear_ (
+ &backup->policy_providers[i].provider_url);
+ GNUNET_free (backup->policy_providers);
+ GNUNET_free (backup->upload_fees);
+ for (unsigned int i = 0; i < backup->payments_len; i++)
+ GNUNET_free (backup->payments[i]);
+ GNUNET_free (backup->payments);
+ for (unsigned int i = 0; i < backup->policy_payment_requests_len; i++)
+ {
+ struct ANASTASIS_ReduxPolicyPaymentRequest *ppr
+ = &backup->policy_payment_requests[i];
+
+ GNUNET_free (ppr->payto);
+ ANASTASIS_REDUX_provider_url_clear_ (&ppr->provider);
+ }
+ GNUNET_free (backup->policy_payment_requests);
+ for (unsigned int i = 0; i < backup->success_details_len; i++)
+ ANASTASIS_REDUX_provider_url_clear_ (
+ &backup->success_details[i].provider_url);
+ GNUNET_free (backup->success_details);
+ json_decref (backup->core_secret);
+ GNUNET_free (backup->secret_name);
+}
+
+
+/**
+ * Free the variant-specific contents of @a fb, but not @a fb itself.
+ *
+ * @param[in,out] fb feedback to reset
+ */
+void
+ANASTASIS_REDUX_feedback_clear_ (struct ANASTASIS_ReduxFeedback *fb)
+{
+ GNUNET_free (fb->display_hint);
+ switch (fb->status)
+ {
+ case ANASTASIS_RFS_CODE_IN_FILE:
+ GNUNET_free (fb->details.code_in_file.filename);
+ break;
+ case ANASTASIS_RFS_SEND_TO_ADDRESS:
+ GNUNET_free (fb->details.send_to_address.address_hint);
+ break;
+ case ANASTASIS_RFS_TALER_PAYMENT:
+ GNUNET_free (fb->details.taler_payment.taler_pay_uri);
+ ANASTASIS_REDUX_provider_url_clear_ (&fb->details.taler_payment.provider);
+ break;
+ case ANASTASIS_RFS_IBAN_INSTRUCTIONS:
+ GNUNET_free (fb->details.iban_instructions.target_iban);
+ GNUNET_free (fb->details.iban_instructions.target_business_name);
+ GNUNET_free (fb->details.iban_instructions.wire_transfer_subject);
+ break;
+ case ANASTASIS_RFS_SERVER_FAILURE:
+ case ANASTASIS_RFS_TRUTH_UNKNOWN:
+ case ANASTASIS_RFS_SOLVED:
+ case ANASTASIS_RFS_INCORRECT_ANSWER:
+ case ANASTASIS_RFS_RATE_LIMIT_EXCEEDED:
+ /* nothing heap-allocated beyond the display hint */
+ break;
+ }
+ memset (&fb->details,
+ 0,
+ sizeof (fb->details));
+ fb->display_hint = NULL;
+}
+
+
+/**
+ * Free the recovery information @a ri.
+ *
+ * @param[in] ri recovery information to free, may be NULL
+ */
+static void
+free_recovery_info (struct ANASTASIS_ReduxRecoveryInfo *ri)
+{
+ if (NULL == ri)
+ return;
+ for (unsigned int i = 0; i < ri->challenges_len; i++)
+ {
+ GNUNET_free (ri->challenges[i].type);
+ GNUNET_free (ri->challenges[i].instructions);
+ }
+ GNUNET_free (ri->challenges);
+ for (unsigned int i = 0; i < ri->policies_len; i++)
+ GNUNET_free (ri->policies[i].uuids);
+ GNUNET_free (ri->policies);
+ GNUNET_free (ri->secret_name);
+ ANASTASIS_REDUX_provider_url_clear_ (&ri->provider_url);
+ GNUNET_free (ri);
+}
+
+
+/**
+ * Free the contents of @a recovery.
+ *
+ * @param[in,out] recovery state to free
+ */
+static void
+free_recovery (struct ANASTASIS_ReduxRecovery *recovery)
+{
+ if (NULL != recovery->r)
+ ANASTASIS_recovery_abort (recovery->r);
+ free_recovery_info (recovery->ri);
+ for (unsigned int i = 0; i < recovery->challenge_feedback_len; i++)
+ ANASTASIS_REDUX_feedback_clear_ (&recovery->challenge_feedback[i]);
+ GNUNET_free (recovery->challenge_feedback);
+ json_decref (recovery->core_secret);
+}
+
+
+void
+ANASTASIS_REDUX_state_free_ (struct ANASTASIS_ReduxState *rs)
+{
+ if (NULL == rs)
+ return;
+ switch (rs->type)
+ {
+ case ANASTASIS_RT_BACKUP:
+ free_backup (&rs->details.backup);
+ free_common (&rs->common);
+ break;
+ case ANASTASIS_RT_RECOVERY:
+ free_recovery (&rs->details.recovery);
+ free_common (&rs->common);
+ break;
+ case ANASTASIS_RT_ERROR:
+ GNUNET_free (rs->details.error.hint);
+ GNUNET_free (rs->details.error.detail);
+ break;
+ }
+ GNUNET_free (rs);
+}
+
+
+/* end of anastasis_api_redux_free.c */
diff --git a/src/reducer/anastasis_api_redux_parse.c b/src/reducer/anastasis_api_redux_parse.c
@@ -0,0 +1,2193 @@
+/*
+ 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 <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",
+ ¤cy),
+ 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",
+ ®ex),
+ 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);
+ 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",
+ ¤cy),
+ 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",
+ ¤cies),
+ 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",
+ "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;
+ 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_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->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;
+}
+
+
+/* end of anastasis_api_redux_parse.c */
diff --git a/src/reducer/anastasis_api_redux_serialize.c b/src/reducer/anastasis_api_redux_serialize.c
@@ -0,0 +1,881 @@
+/*
+ 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_serialize.c
+ * @brief write a reducer state back out as JSON
+ * @author Christian Grothoff
+ *
+ * The inverse of anastasis_api_redux_parse.c. Fields that are not set are
+ * omitted rather than serialized as null, matching what the JSON-based
+ * implementation this replaced produced.
+ */
+#include "platform.h"
+#include "anastasis_redux.h"
+#include "anastasis_api_redux.h"
+#include "anastasis_api_redux_state.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Add @a val to @a obj under @a key, unless @a val is NULL.
+ *
+ * @param[in,out] obj object to extend
+ * @param key field name
+ * @param[in] val value to add, reference is consumed
+ */
+static void
+set_opt (json_t *obj,
+ const char *key,
+ json_t *val)
+{
+ if (NULL == val)
+ return;
+ GNUNET_assert (0 ==
+ json_object_set_new (obj,
+ key,
+ val));
+}
+
+
+/**
+ * Serialize the `continents` array.
+ *
+ * @param common state to serialize
+ * @return JSON array
+ */
+static json_t *
+serialize_continents (const struct ANASTASIS_ReduxCommon *common)
+{
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ for (unsigned int i = 0; i < common->continents_len; i++)
+ {
+ const struct ANASTASIS_ReduxContinent *c = &common->continents[i];
+ json_t *co;
+
+ if (c->bare)
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ json_string (c->name)));
+ continue;
+ }
+ co = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ c->name));
+ set_opt (co,
+ "name_i18n",
+ json_incref (c->name_i18n));
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ co));
+ }
+ return arr;
+}
+
+
+/**
+ * Serialize the `countries` array.
+ *
+ * @param common state to serialize
+ * @return JSON array
+ */
+static json_t *
+serialize_countries (const struct ANASTASIS_ReduxCommon *common)
+{
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ for (unsigned int i = 0; i < common->countries_len; i++)
+ {
+ const struct ANASTASIS_ReduxCountry *c = &common->countries[i];
+ json_t *co;
+
+ co = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("code",
+ c->code),
+ GNUNET_JSON_pack_string ("name",
+ c->name),
+ GNUNET_JSON_pack_string ("continent",
+ c->continent),
+ GNUNET_JSON_pack_conditional (
+ NULL != c->call_code,
+ GNUNET_JSON_pack_string ("call_code",
+ c->call_code)));
+ set_opt (co,
+ "name_i18n",
+ json_incref (c->name_i18n));
+ set_opt (co,
+ "continent_i18n",
+ json_incref (c->continent_i18n));
+ if (NULL != c->currency)
+ set_opt (co,
+ "currency",
+ json_string (c->currency));
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ co));
+ }
+ return arr;
+}
+
+
+/**
+ * Serialize the `required_attributes` array.
+ *
+ * @param common state to serialize
+ * @return JSON array
+ */
+static json_t *
+serialize_required_attributes (const struct ANASTASIS_ReduxCommon *common)
+{
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ for (unsigned int i = 0; i < common->required_attributes_len; i++)
+ {
+ const struct ANASTASIS_ReduxAttributeSpec *a
+ = &common->required_attributes[i];
+ json_t *ao;
+
+ ao = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ a->type),
+ GNUNET_JSON_pack_string ("name",
+ a->name),
+ GNUNET_JSON_pack_string ("label",
+ a->label),
+ GNUNET_JSON_pack_string ("widget",
+ a->widget),
+ GNUNET_JSON_pack_conditional (
+ NULL != a->uuid,
+ GNUNET_JSON_pack_string ("uuid",
+ a->uuid)),
+ GNUNET_JSON_pack_conditional (
+ NULL != a->tooltip,
+ GNUNET_JSON_pack_string ("tooltip",
+ a->tooltip)),
+ GNUNET_JSON_pack_conditional (
+ NULL != a->validation_regex,
+ GNUNET_JSON_pack_string ("validation-regex",
+ a->validation_regex)),
+ GNUNET_JSON_pack_conditional (
+ NULL != a->validation_logic,
+ GNUNET_JSON_pack_string ("validation-logic",
+ a->validation_logic)),
+ GNUNET_JSON_pack_conditional (
+ NULL != a->autocomplete,
+ GNUNET_JSON_pack_string ("autocomplete",
+ a->autocomplete)),
+ GNUNET_JSON_pack_conditional (
+ a->have_optional,
+ GNUNET_JSON_pack_bool ("optional",
+ a->optional)));
+ set_opt (ao,
+ "label_i18n",
+ json_incref (a->label_i18n));
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ ao));
+ }
+ return arr;
+}
+
+
+/**
+ * Serialize the `authentication_providers` object.
+ *
+ * @param common state to serialize
+ * @return JSON object
+ */
+static json_t *
+serialize_providers (const struct ANASTASIS_ReduxCommon *common)
+{
+ json_t *obj;
+
+ obj = json_object ();
+ GNUNET_assert (NULL != obj);
+ for (unsigned int i = 0; i < common->providers_len; i++)
+ {
+ const struct ANASTASIS_ReduxProvider *p = &common->providers[i];
+ const char *status;
+ json_t *po;
+
+ switch (p->status)
+ {
+ case ANASTASIS_RPS_NOT_CONTACTED:
+ status = "not-contacted";
+ break;
+ case ANASTASIS_RPS_OK:
+ status = "ok";
+ break;
+ case ANASTASIS_RPS_DISABLED:
+ status = "disabled";
+ break;
+ case ANASTASIS_RPS_ERROR:
+ status = "error";
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ if (ANASTASIS_RPS_ERROR == p->status)
+ {
+ po = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("status",
+ status),
+ GNUNET_JSON_pack_uint64 ("error_code",
+ p->error.ec),
+ GNUNET_JSON_pack_uint64 ("http_status",
+ p->error.http_status));
+ }
+ else if (! p->have_config)
+ {
+ po = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("status",
+ status));
+ }
+ else
+ {
+ const struct ANASTASIS_ReduxProviderConfig *cfg = &p->config;
+ json_t *methods;
+
+ methods = json_array ();
+ GNUNET_assert (NULL != methods);
+ for (unsigned int j = 0; j < cfg->methods_len; j++)
+ {
+ const struct ANASTASIS_ReduxMethodSpec *ms = &cfg->methods[j];
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ methods,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ ms->type),
+ TALER_JSON_pack_amount ("usage_fee",
+ &ms->usage_fee))));
+ }
+ po = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("status",
+ status),
+ GNUNET_JSON_pack_array_steal ("methods",
+ methods),
+ TALER_JSON_pack_amount ("annual_fee",
+ &cfg->annual_fee),
+ TALER_JSON_pack_amount ("truth_upload_fee",
+ &cfg->truth_upload_fee),
+ TALER_JSON_pack_amount ("liability_limit",
+ &cfg->liability_limit),
+ GNUNET_JSON_pack_string ("business_name",
+ cfg->business_name),
+ GNUNET_JSON_pack_uint64 ("storage_limit_in_megabytes",
+ cfg->storage_limit_in_megabytes),
+ GNUNET_JSON_pack_data_auto ("provider_salt",
+ &cfg->provider_salt),
+ GNUNET_JSON_pack_uint64 ("http_status",
+ cfg->http_status),
+ GNUNET_JSON_pack_conditional (
+ NULL != cfg->currency,
+ GNUNET_JSON_pack_string ("currency",
+ cfg->currency)));
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (obj,
+ p->url.url,
+ po));
+ }
+ return obj;
+}
+
+
+/**
+ * Add the fields shared by the backup and recovery variants to @a obj.
+ *
+ * @param[in,out] obj object to extend
+ * @param common state to serialize
+ */
+static void
+serialize_common (json_t *obj,
+ const struct ANASTASIS_ReduxCommon *common)
+{
+ if (common->have_continents)
+ set_opt (obj,
+ "continents",
+ serialize_continents (common));
+ if (common->have_countries)
+ set_opt (obj,
+ "countries",
+ serialize_countries (common));
+ if (common->have_required_attributes)
+ set_opt (obj,
+ "required_attributes",
+ serialize_required_attributes (common));
+ if (common->have_providers)
+ set_opt (obj,
+ "authentication_providers",
+ serialize_providers (common));
+ if (NULL != common->identity_attributes)
+ set_opt (obj,
+ "identity_attributes",
+ json_incref (common->identity_attributes));
+ if (common->have_currencies)
+ {
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ for (unsigned int i = 0; i < common->currencies_len; i++)
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ arr,
+ json_string (common->currencies[i])));
+ set_opt (obj,
+ "currencies",
+ arr);
+ }
+ if (NULL != common->selected_continent)
+ set_opt (obj,
+ "selected_continent",
+ json_string (common->selected_continent));
+ if (NULL != common->selected_country)
+ set_opt (obj,
+ "selected_country",
+ json_string (common->selected_country));
+}
+
+
+/**
+ * Serialize the backup-specific fields into @a obj.
+ *
+ * @param[in,out] obj object to extend
+ * @param b backup state to serialize
+ */
+static void
+serialize_backup (json_t *obj,
+ const struct ANASTASIS_ReduxBackup *b)
+{
+ if (b->have_authentication_methods)
+ {
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ for (unsigned int i = 0; i < b->authentication_methods_len; i++)
+ {
+ const struct ANASTASIS_ReduxAuthMethod *am
+ = &b->authentication_methods[i];
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ arr,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ am->type),
+ GNUNET_JSON_pack_string ("instructions",
+ am->instructions),
+ GNUNET_JSON_pack_data_varsize ("challenge",
+ am->challenge,
+ am->challenge_size),
+ GNUNET_JSON_pack_conditional (
+ NULL != am->mime_type,
+ GNUNET_JSON_pack_string ("mime_type",
+ am->mime_type)))));
+ }
+ set_opt (obj,
+ "authentication_methods",
+ arr);
+ }
+ if (b->have_policies)
+ {
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ for (unsigned int i = 0; i < b->policies_len; i++)
+ {
+ const struct ANASTASIS_ReduxPolicy *p = &b->policies[i];
+ json_t *methods;
+
+ methods = json_array ();
+ GNUNET_assert (NULL != methods);
+ for (unsigned int j = 0; j < p->methods_len; j++)
+ {
+ const struct ANASTASIS_ReduxPolicyMethod *pm = &p->methods[j];
+ json_t *mo;
+
+ mo = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("authentication_method",
+ pm->authentication_method.idx),
+ GNUNET_JSON_pack_string ("provider",
+ pm->provider.url));
+ if (NULL != pm->truth)
+ {
+ json_t *truth = ANASTASIS_truth_to_json (pm->truth);
+
+ GNUNET_assert (NULL != truth);
+ GNUNET_assert (0 ==
+ json_object_set_new (truth,
+ "upload_status",
+ json_integer (
+ pm->upload_status)));
+ GNUNET_assert (0 ==
+ json_object_set_new (mo,
+ "truth",
+ truth));
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (methods,
+ mo));
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ arr,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_conditional (
+ p->have_recovery_cost,
+ TALER_JSON_pack_amount ("recovery_cost",
+ &p->recovery_cost)),
+ GNUNET_JSON_pack_array_steal ("methods",
+ methods))));
+ }
+ set_opt (obj,
+ "policies",
+ arr);
+ }
+ if (b->have_policy_providers)
+ {
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ for (unsigned int i = 0; i < b->policy_providers_len; i++)
+ {
+ const struct ANASTASIS_ReduxPolicyProvider *pp
+ = &b->policy_providers[i];
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ arr,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("provider_url",
+ pp->provider_url.url),
+ GNUNET_JSON_pack_conditional (
+ pp->have_payment_secret,
+ GNUNET_JSON_pack_data_auto (
+ "payment_secret",
+ &pp->payment_secret)))));
+ }
+ set_opt (obj,
+ "policy_providers",
+ arr);
+ }
+ if (b->have_upload_fees)
+ {
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ for (unsigned int i = 0; i < b->upload_fees_len; i++)
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ arr,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("fee",
+ &b->upload_fees[i]))));
+ set_opt (obj,
+ "upload_fees",
+ arr);
+ }
+ if (b->have_payments)
+ {
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ for (unsigned int i = 0; i < b->payments_len; i++)
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ json_string (b->payments[i])));
+ set_opt (obj,
+ "payments",
+ arr);
+ }
+ if (b->have_policy_payment_requests)
+ {
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ for (unsigned int i = 0; i < b->policy_payment_requests_len; i++)
+ {
+ const struct ANASTASIS_ReduxPolicyPaymentRequest *ppr
+ = &b->policy_payment_requests[i];
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ arr,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto",
+ ppr->payto),
+ GNUNET_JSON_pack_string ("provider",
+ ppr->provider.url))));
+ }
+ set_opt (obj,
+ "policy_payment_requests",
+ arr);
+ }
+ if (b->have_success_details)
+ {
+ json_t *sd;
+
+ sd = json_object ();
+ GNUNET_assert (NULL != sd);
+ for (unsigned int i = 0; i < b->success_details_len; i++)
+ {
+ const struct ANASTASIS_ReduxSuccessDetail *d = &b->success_details[i];
+
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ sd,
+ d->provider_url.url,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("policy_version",
+ d->policy_version),
+ GNUNET_JSON_pack_timestamp ("policy_expiration",
+ d->policy_expiration))));
+ }
+ set_opt (obj,
+ "success_details",
+ sd);
+ }
+ if (NULL != b->core_secret)
+ set_opt (obj,
+ "core_secret",
+ json_incref (b->core_secret));
+ if (NULL != b->secret_name)
+ set_opt (obj,
+ "secret_name",
+ json_string (b->secret_name));
+ if (b->have_expiration)
+ set_opt (obj,
+ "expiration",
+ GNUNET_JSON_from_timestamp (b->expiration));
+ if (b->have_pay_arguments)
+ {
+ json_t *pa;
+
+ pa = json_object ();
+ GNUNET_assert (NULL != pa);
+ if (b->pay_arguments.have_timeout)
+ set_opt (pa,
+ "timeout",
+ GNUNET_JSON_from_time_rel (b->pay_arguments.timeout));
+ set_opt (obj,
+ "pay_arguments",
+ pa);
+ }
+}
+
+
+/**
+ * Serialize the recovery information @a ri.
+ *
+ * @param ri recovery information to serialize
+ * @return JSON object
+ */
+static json_t *
+serialize_recovery_info (const struct ANASTASIS_ReduxRecoveryInfo *ri)
+{
+ json_t *challenges;
+ json_t *policies;
+
+ challenges = json_array ();
+ GNUNET_assert (NULL != challenges);
+ for (unsigned int i = 0; i < ri->challenges_len; i++)
+ {
+ const struct ANASTASIS_ReduxChallengeInfo *ci = &ri->challenges[i];
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ challenges,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("uuid",
+ &ci->uuid),
+ GNUNET_JSON_pack_string ("type",
+ ci->type),
+ GNUNET_JSON_pack_string ("uuid-display",
+ ANASTASIS_CRYPTO_uuid2s (
+ &ci->uuid)),
+ GNUNET_JSON_pack_string ("instructions",
+ ci->instructions),
+ GNUNET_JSON_pack_conditional (
+ ci->have_payment_secret,
+ GNUNET_JSON_pack_data_auto ("payment_secret",
+ &ci->payment_secret)))));
+ }
+ policies = json_array ();
+ GNUNET_assert (NULL != policies);
+ for (unsigned int i = 0; i < ri->policies_len; i++)
+ {
+ const struct ANASTASIS_ReduxRecoveryPolicy *p = &ri->policies[i];
+ json_t *pa;
+
+ pa = json_array ();
+ GNUNET_assert (NULL != pa);
+ for (unsigned int j = 0; j < p->uuids_len; j++)
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ pa,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("uuid",
+ &p->uuids[j]))));
+ GNUNET_assert (0 ==
+ json_array_append_new (policies,
+ pa));
+ }
+ return GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("challenges",
+ challenges),
+ GNUNET_JSON_pack_array_steal ("policies",
+ policies),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("secret_name",
+ ri->secret_name)),
+ GNUNET_JSON_pack_string ("provider_url",
+ ri->provider_url.url),
+ GNUNET_JSON_pack_uint64 ("version",
+ ri->version));
+}
+
+
+/**
+ * Serialize one challenge feedback entry.
+ *
+ * @param fb feedback to serialize
+ * @return JSON object
+ */
+static json_t *
+serialize_feedback (const struct ANASTASIS_ReduxFeedback *fb)
+{
+ json_t *fo;
+
+ switch (fb->status)
+ {
+ case ANASTASIS_RFS_CODE_IN_FILE:
+ fo = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "code-in-file"),
+ GNUNET_JSON_pack_string ("filename",
+ fb->details.code_in_file.filename));
+ break;
+ case ANASTASIS_RFS_SEND_TO_ADDRESS:
+ fo = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "send-to-address"),
+ GNUNET_JSON_pack_conditional (
+ NULL != fb->details.send_to_address.address_hint,
+ GNUNET_JSON_pack_string ("address_hint",
+ fb->details.send_to_address.address_hint)));
+ break;
+ case ANASTASIS_RFS_TALER_PAYMENT:
+ fo = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "taler-payment"),
+ GNUNET_JSON_pack_string ("taler_pay_uri",
+ fb->details.taler_payment.taler_pay_uri),
+ GNUNET_JSON_pack_string ("provider",
+ fb->details.taler_payment.provider.url),
+ GNUNET_JSON_pack_data_auto ("payment_secret",
+ &fb->details.taler_payment.payment_secret));
+ break;
+ case ANASTASIS_RFS_SERVER_FAILURE:
+ fo = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "server-failure"),
+ GNUNET_JSON_pack_uint64 ("http_status",
+ fb->details.server_failure.http_status),
+ GNUNET_JSON_pack_uint64 ("error_code",
+ fb->details.server_failure.ec));
+ break;
+ case ANASTASIS_RFS_TRUTH_UNKNOWN:
+ fo = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "truth-unknown"),
+ GNUNET_JSON_pack_uint64 ("http_status",
+ fb->details.server_failure.http_status),
+ GNUNET_JSON_pack_uint64 ("error_code",
+ fb->details.server_failure.ec));
+ break;
+ case ANASTASIS_RFS_IBAN_INSTRUCTIONS:
+ fo = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "iban-instructions"),
+ GNUNET_JSON_pack_string ("target_iban",
+ fb->details.iban_instructions.target_iban),
+ GNUNET_JSON_pack_string (
+ "target_business_name",
+ fb->details.iban_instructions.target_business_name),
+ GNUNET_JSON_pack_string (
+ "wire_transfer_subject",
+ fb->details.iban_instructions.wire_transfer_subject),
+ TALER_JSON_pack_amount ("challenge_amount",
+ &fb->details.iban_instructions.amount));
+ break;
+ case ANASTASIS_RFS_SOLVED:
+ fo = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "solved"));
+ break;
+ case ANASTASIS_RFS_INCORRECT_ANSWER:
+ fo = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "incorrect-answer"),
+ GNUNET_JSON_pack_uint64 ("error_code",
+ fb->details.incorrect_answer.ec));
+ break;
+ case ANASTASIS_RFS_RATE_LIMIT_EXCEEDED:
+ fo = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "rate-limit-exceeded"),
+ GNUNET_JSON_pack_uint64 (
+ "request_limit",
+ fb->details.rate_limit_exceeded.request_limit),
+ GNUNET_JSON_pack_time_rel (
+ "request_frequency",
+ fb->details.rate_limit_exceeded.request_frequency),
+ GNUNET_JSON_pack_uint64 ("error_code",
+ fb->details.rate_limit_exceeded.ec));
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ if (NULL != fb->display_hint)
+ set_opt (fo,
+ "display_hint",
+ json_string (fb->display_hint));
+ return fo;
+}
+
+
+/**
+ * Serialize the recovery-specific fields into @a obj.
+ *
+ * @param[in,out] obj object to extend
+ * @param rr recovery state to serialize
+ */
+static void
+serialize_recovery (json_t *obj,
+ const struct ANASTASIS_ReduxRecovery *rr)
+{
+ if (NULL != rr->ri)
+ set_opt (obj,
+ "recovery_information",
+ serialize_recovery_info (rr->ri));
+ if (NULL != rr->r)
+ set_opt (obj,
+ "recovery_document",
+ ANASTASIS_recovery_serialize (rr->r));
+ if (rr->have_challenge_feedback)
+ {
+ json_t *cf;
+
+ cf = json_object ();
+ GNUNET_assert (NULL != cf);
+ for (unsigned int i = 0; i < rr->challenge_feedback_len; i++)
+ {
+ const struct ANASTASIS_ReduxFeedback *fb = &rr->challenge_feedback[i];
+ char uuid[sizeof (fb->uuid) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (&fb->uuid,
+ sizeof (fb->uuid),
+ uuid,
+ sizeof (uuid));
+ GNUNET_assert (NULL != end);
+ *end = '\0';
+ GNUNET_assert (0 ==
+ json_object_set_new (cf,
+ uuid,
+ serialize_feedback (fb)));
+ }
+ set_opt (obj,
+ "challenge_feedback",
+ cf);
+ }
+ if (rr->have_selected_challenge)
+ set_opt (obj,
+ "selected_challenge_uuid",
+ GNUNET_JSON_from_data_auto (&rr->selected_challenge));
+ if (NULL != rr->core_secret)
+ set_opt (obj,
+ "core_secret",
+ json_incref (rr->core_secret));
+}
+
+
+json_t *
+ANASTASIS_REDUX_state_serialize_ (const struct ANASTASIS_ReduxState *rs)
+{
+ json_t *obj;
+
+ switch (rs->type)
+ {
+ case ANASTASIS_RT_BACKUP:
+ obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("reducer_type",
+ "backup"),
+ GNUNET_JSON_pack_string ("backup_state",
+ ANASTASIS_backup_state_to_string_ (
+ rs->details.backup.state)));
+ serialize_common (obj,
+ &rs->common);
+ serialize_backup (obj,
+ &rs->details.backup);
+ return obj;
+ case ANASTASIS_RT_RECOVERY:
+ obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("reducer_type",
+ "recovery"),
+ GNUNET_JSON_pack_string ("recovery_state",
+ ANASTASIS_recovery_state_to_string_ (
+ rs->details.recovery.state)));
+ serialize_common (obj,
+ &rs->common);
+ serialize_recovery (obj,
+ &rs->details.recovery);
+ return obj;
+ case ANASTASIS_RT_ERROR:
+ return GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("reducer_type",
+ "error"),
+ GNUNET_JSON_pack_uint64 ("code",
+ rs->details.error.code),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("hint",
+ rs->details.error.hint)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("detail",
+ rs->details.error.detail)));
+ }
+ GNUNET_assert (0);
+ return NULL;
+}
+
+
+/* end of anastasis_api_redux_serialize.c */
diff --git a/src/reducer/anastasis_api_redux_state.c b/src/reducer/anastasis_api_redux_state.c
@@ -0,0 +1,147 @@
+/*
+ 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_state.c
+ * @brief lookup helpers for the typed reducer state
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis_redux.h"
+#include "anastasis_api_redux.h"
+#include "anastasis_api_redux_state.h"
+
+
+void
+ANASTASIS_REDUX_provider_url_set_ (struct ANASTASIS_ProviderUrl *pu,
+ const char *url)
+{
+ GNUNET_free (pu->url);
+ pu->url = GNUNET_strdup (url);
+}
+
+
+struct ANASTASIS_ReduxProvider *
+ANASTASIS_REDUX_provider_find_ (const struct ANASTASIS_ReduxCommon *common,
+ const char *url)
+{
+ for (unsigned int i = 0; i < common->providers_len; i++)
+ if (0 == strcmp (common->providers[i].url.url,
+ url))
+ return &common->providers[i];
+ return NULL;
+}
+
+
+struct ANASTASIS_ReduxProvider *
+ANASTASIS_REDUX_provider_get_ (struct ANASTASIS_ReduxCommon *common,
+ const char *url)
+{
+ struct ANASTASIS_ReduxProvider *p;
+ unsigned int off;
+
+ p = ANASTASIS_REDUX_provider_find_ (common,
+ url);
+ if (NULL != p)
+ return p;
+ off = common->providers_len;
+ GNUNET_array_append (common->providers,
+ common->providers_len,
+ (struct ANASTASIS_ReduxProvider) {
+ .status = ANASTASIS_RPS_NOT_CONTACTED
+ });
+ common->have_providers = true;
+ p = &common->providers[off];
+ ANASTASIS_REDUX_provider_url_set_ (&p->url,
+ url);
+ return p;
+}
+
+
+struct ANASTASIS_ReduxFeedback *
+ANASTASIS_REDUX_feedback_find_ (const struct ANASTASIS_ReduxRecovery *rr,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid)
+{
+ for (unsigned int i = 0; i < rr->challenge_feedback_len; i++)
+ if (0 ==
+ GNUNET_memcmp (&rr->challenge_feedback[i].uuid,
+ uuid))
+ return &rr->challenge_feedback[i];
+ return NULL;
+}
+
+
+struct ANASTASIS_ReduxFeedback *
+ANASTASIS_REDUX_feedback_get_ (struct ANASTASIS_ReduxRecovery *rr,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid)
+{
+ struct ANASTASIS_ReduxFeedback *fb;
+ unsigned int off;
+
+ fb = ANASTASIS_REDUX_feedback_find_ (rr,
+ uuid);
+ if (NULL != fb)
+ {
+ /* Overwriting an earlier verdict: drop whatever the old variant
+ owned so the caller can fill in a fresh one. */
+ ANASTASIS_REDUX_feedback_clear_ (fb);
+ return fb;
+ }
+ off = rr->challenge_feedback_len;
+ GNUNET_array_append (rr->challenge_feedback,
+ rr->challenge_feedback_len,
+ (struct ANASTASIS_ReduxFeedback) {
+ .uuid = *uuid
+ });
+ rr->have_challenge_feedback = true;
+ return &rr->challenge_feedback[off];
+}
+
+
+struct ANASTASIS_ReduxChallengeInfo *
+ANASTASIS_REDUX_challenge_find_ (const struct ANASTASIS_ReduxRecovery *rr,
+ const struct
+ ANASTASIS_CRYPTO_TruthUUIDP *uuid)
+{
+ if (NULL == rr->ri)
+ return NULL;
+ for (unsigned int i = 0; i < rr->ri->challenges_len; i++)
+ if (0 ==
+ GNUNET_memcmp (&rr->ri->challenges[i].uuid,
+ uuid))
+ return &rr->ri->challenges[i];
+ return NULL;
+}
+
+
+void
+ANASTASIS_REDUX_return_ (struct ANASTASIS_ReduxState *rs,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls,
+ enum TALER_ErrorCode ec)
+{
+ json_t *json;
+
+ json = ANASTASIS_REDUX_state_serialize_ (rs);
+ GNUNET_assert (NULL != json);
+ cb (cb_cls,
+ ec,
+ json);
+ json_decref (json);
+ ANASTASIS_REDUX_state_free_ (rs);
+}
+
+
+/* end of anastasis_api_redux_state.c */
diff --git a/src/reducer/anastasis_api_redux_state.h b/src/reducer/anastasis_api_redux_state.h
@@ -0,0 +1,1446 @@
+/*
+ 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_state.h
+ * @brief typed representation of the reducer state
+ * @author Christian Grothoff
+ *
+ * The reducer's public API (`anastasis_redux.h`) speaks JSON, but the reducer
+ * logic itself does not: every entry point parses the incoming JSON into a
+ * `struct ANASTASIS_ReduxState` exactly once, operates on that, and serializes
+ * it back exactly once on the way out. The three generic routines at the
+ * bottom of this file are the only places that know the JSON encoding.
+ *
+ * Parsing is strict: a field that is not described here is an error
+ * (#TALER_EC_ANASTASIS_REDUCER_STATE_INVALID). There are two deliberate
+ * exceptions, both marked `json_t *` below, where the exact bytes the user
+ * supplied are cryptographically load-bearing and must not be normalized:
+ * @e identity_attributes (PoW-hashed to derive the account key) and
+ * @e core_secret (its serialization *is* the uploaded secret).
+ */
+#ifndef ANASTASIS_API_REDUX_STATE_H
+#define ANASTASIS_API_REDUX_STATE_H
+
+#include "anastasis_redux.h"
+#include "anastasis_api_redux.h"
+
+
+/* ************************ Wrapped primitives *********************** */
+
+/**
+ * URL of an Anastasis provider. Wrapped so that it cannot be confused
+ * with the many other strings floating around the reducer (business
+ * names, payto URIs, display hints, ...).
+ */
+struct ANASTASIS_ProviderUrl
+{
+ /**
+ * The URL itself. Owned by the containing structure.
+ */
+ char *url;
+};
+
+
+/**
+ * Index into the @e policies array of a backup state.
+ */
+struct ANASTASIS_PolicyIndex
+{
+ /**
+ * The index.
+ */
+ unsigned int idx;
+};
+
+
+/**
+ * Index into the @e methods array of a policy, or into the
+ * @e authentication_methods array of a backup state.
+ */
+struct ANASTASIS_MethodIndex
+{
+ /**
+ * The index.
+ */
+ unsigned int idx;
+};
+
+
+/**
+ * Bitmask selecting which optional identity attributes were used.
+ */
+struct ANASTASIS_AttributeMask
+{
+ /**
+ * The mask.
+ */
+ uint64_t mask;
+};
+
+
+/* ************************ Geography ******************************** */
+
+/**
+ * A continent the user may select.
+ */
+struct ANASTASIS_ReduxContinent
+{
+ /**
+ * Name of the continent, e.g. "Europe".
+ */
+ char *name;
+
+ /**
+ * Translations of @e name, NULL if not given. An object mapping
+ * language tags to strings, kept verbatim: the reducer only carries
+ * translations from the resource files to the user interface and
+ * never interprets them.
+ */
+ json_t *name_i18n;
+
+ /**
+ * True if this continent was given as a bare string rather than as
+ * an object. Both forms occur: the reducer itself emits objects,
+ * but hand-written states use plain names.
+ */
+ bool bare;
+};
+
+
+/**
+ * A country the user may select. Copied verbatim from
+ * `contrib/redux.countries.json`.
+ */
+struct ANASTASIS_ReduxCountry
+{
+ /**
+ * Two-letter country code, e.g. "ch".
+ */
+ char *code;
+
+ /**
+ * Name of the country.
+ */
+ char *name;
+
+ /**
+ * Continent the country is on.
+ */
+ char *continent;
+
+ /**
+ * International dialling prefix, e.g. "+41".
+ */
+ char *call_code;
+
+ /**
+ * Translations of @e name, NULL if not given. Kept verbatim, see
+ * #ANASTASIS_ReduxContinent.
+ */
+ json_t *name_i18n;
+
+ /**
+ * Translations of @e continent, NULL if not given. Kept verbatim,
+ * see #ANASTASIS_ReduxContinent.
+ */
+ json_t *continent_i18n;
+
+ /**
+ * Currency used in this country, NULL if not given. Carried through
+ * for the benefit of user interfaces; the reducer does not use it.
+ */
+ char *currency;
+};
+
+
+/**
+ * Specification of one identity attribute we ask the user for.
+ * Copied verbatim from `contrib/redux.$CC.json`.
+ */
+struct ANASTASIS_ReduxAttributeSpec
+{
+ /**
+ * Type of the attribute, e.g. "string" or "date".
+ */
+ char *type;
+
+ /**
+ * Name of the attribute; the key under which the user's answer
+ * appears in @e identity_attributes.
+ */
+ char *name;
+
+ /**
+ * Human-readable label.
+ */
+ char *label;
+
+ /**
+ * Widget the GTK UI should use to ask for this attribute.
+ */
+ char *widget;
+
+ /**
+ * UUID identifying this attribute.
+ */
+ char *uuid;
+
+ /**
+ * Tooltip for the UI, NULL if not given.
+ */
+ char *tooltip;
+
+ /**
+ * Regular expression the value must match, NULL if not given.
+ */
+ char *validation_regex;
+
+ /**
+ * Name of a validation function to `dlsym()`, NULL if not given.
+ */
+ char *validation_logic;
+
+ /**
+ * Input mask for autocompletion, NULL if not given.
+ */
+ char *autocomplete;
+
+ /**
+ * Translations of @e label, NULL if not given. Kept verbatim, see
+ * #ANASTASIS_ReduxContinent.
+ */
+ json_t *label_i18n;
+
+ /**
+ * True if the user may leave this attribute blank.
+ */
+ bool optional;
+
+ /**
+ * True if @e optional was explicitly present in the JSON. Needed so
+ * that serialization does not invent an `"optional": false` field
+ * where the resource file had none.
+ */
+ bool have_optional;
+};
+
+
+/* ************************ Providers ******************************** */
+
+/**
+ * Status of an Anastasis provider in the reducer state.
+ */
+enum ANASTASIS_ReduxProviderStatus
+{
+ /**
+ * We have not (yet) talked to this provider.
+ */
+ ANASTASIS_RPS_NOT_CONTACTED = 0,
+
+ /**
+ * We obtained the provider's /config successfully.
+ */
+ ANASTASIS_RPS_OK = 1,
+
+ /**
+ * The provider was taken out of consideration, typically because an
+ * upload to it failed. Note that any configuration we obtained
+ * earlier is retained (and re-serialized), matching the behaviour of
+ * the JSON-based implementation this replaced.
+ */
+ ANASTASIS_RPS_DISABLED = 2,
+
+ /**
+ * Talking to the provider failed.
+ */
+ ANASTASIS_RPS_ERROR = 3
+};
+
+
+/**
+ * One authentication method offered by a provider, with its price.
+ */
+struct ANASTASIS_ReduxMethodSpec
+{
+ /**
+ * Type of the method, e.g. "question" or "sms".
+ */
+ char *type;
+
+ /**
+ * Fee charged for using this method during recovery.
+ */
+ struct TALER_Amount usage_fee;
+};
+
+
+/**
+ * Configuration of a provider, as obtained from its /config endpoint.
+ */
+struct ANASTASIS_ReduxProviderConfig
+{
+ /**
+ * Methods the provider supports.
+ */
+ struct ANASTASIS_ReduxMethodSpec *methods;
+
+ /**
+ * Length of the @e methods array.
+ */
+ unsigned int methods_len;
+
+ /**
+ * Fee for storing a policy for a year.
+ */
+ struct TALER_Amount annual_fee;
+
+ /**
+ * Fee for uploading one truth.
+ */
+ struct TALER_Amount truth_upload_fee;
+
+ /**
+ * Maximum liability the provider accepts for data loss.
+ */
+ struct TALER_Amount liability_limit;
+
+ /**
+ * Salt of this provider.
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
+
+ /**
+ * Business name of the provider.
+ */
+ char *business_name;
+
+ /**
+ * Currency the provider charges in, NULL if not given. Carried
+ * through for the benefit of user interfaces.
+ */
+ char *currency;
+
+ /**
+ * Maximum upload size the provider accepts, in megabytes.
+ */
+ uint32_t storage_limit_in_megabytes;
+
+ /**
+ * HTTP status of the /config request.
+ */
+ uint32_t http_status;
+};
+
+
+/**
+ * Why we could not use a provider.
+ */
+struct ANASTASIS_ReduxProviderError
+{
+ /**
+ * Error code of the failed request.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * HTTP status of the failed request, 0 if we never got one.
+ */
+ uint32_t http_status;
+};
+
+
+/**
+ * An Anastasis provider we know about. Serializes as one entry of the
+ * `authentication_providers` object, keyed by @e url.
+ */
+struct ANASTASIS_ReduxProvider
+{
+ /**
+ * URL of the provider; the key this entry is stored under.
+ */
+ struct ANASTASIS_ProviderUrl url;
+
+ /**
+ * What we know about this provider.
+ */
+ enum ANASTASIS_ReduxProviderStatus status;
+
+ /**
+ * True if @e config is valid. Set whenever a /config reply was
+ * obtained, and *not* cleared when @e status becomes
+ * #ANASTASIS_RPS_DISABLED.
+ */
+ bool have_config;
+
+ /**
+ * Configuration of the provider; valid iff @e have_config.
+ */
+ struct ANASTASIS_ReduxProviderConfig config;
+
+ /**
+ * Failure details; valid iff @e status is #ANASTASIS_RPS_ERROR.
+ */
+ struct ANASTASIS_ReduxProviderError error;
+};
+
+
+/* ******************** Backup-specific state ************************ */
+
+/**
+ * An authentication method the user configured.
+ */
+struct ANASTASIS_ReduxAuthMethod
+{
+ /**
+ * Type of the method, e.g. "question".
+ */
+ char *type;
+
+ /**
+ * Instructions to show the user during recovery.
+ */
+ char *instructions;
+
+ /**
+ * The challenge itself (answer, phone number, e-mail address, ...).
+ */
+ void *challenge;
+
+ /**
+ * Number of bytes in @e challenge.
+ */
+ size_t challenge_size;
+
+ /**
+ * MIME type of @e challenge, NULL if not given.
+ */
+ char *mime_type;
+};
+
+
+/**
+ * One member of a policy: authenticate with method
+ * @e authentication_method at provider @e provider.
+ */
+struct ANASTASIS_ReduxPolicyMethod
+{
+ /**
+ * Index into the backup state's @e authentication_methods array.
+ */
+ struct ANASTASIS_MethodIndex authentication_method;
+
+ /**
+ * Provider that stores the key share for this method.
+ */
+ struct ANASTASIS_ProviderUrl provider;
+
+ /**
+ * Truth uploaded for this method, NULL until the upload happened.
+ * Owned by this structure; freed with ANASTASIS_truth_free().
+ */
+ struct ANASTASIS_Truth *truth;
+
+ /**
+ * Status of the upload of @e truth; only meaningful if @e truth is
+ * non-NULL.
+ */
+ enum ANASTASIS_UploadStatus upload_status;
+};
+
+
+/**
+ * A recovery policy: a set of methods that together recover the secret.
+ */
+struct ANASTASIS_ReduxPolicy
+{
+ /**
+ * Methods that make up this policy.
+ */
+ struct ANASTASIS_ReduxPolicyMethod *methods;
+
+ /**
+ * Length of the @e methods array.
+ */
+ unsigned int methods_len;
+
+ /**
+ * What recovering via this policy would cost; only valid if
+ * @e have_recovery_cost. Carried through for user interfaces.
+ */
+ struct TALER_Amount recovery_cost;
+
+ /**
+ * True if @e recovery_cost was given.
+ */
+ bool have_recovery_cost;
+};
+
+
+/**
+ * A provider we intend to store the recovery document at.
+ */
+struct ANASTASIS_ReduxPolicyProvider
+{
+ /**
+ * URL of the provider.
+ */
+ struct ANASTASIS_ProviderUrl provider_url;
+
+ /**
+ * Payment secret remembered from an earlier payment request; only
+ * valid if @e have_payment_secret.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret;
+
+ /**
+ * True if @e payment_secret is set.
+ */
+ bool have_payment_secret;
+};
+
+
+/**
+ * Result of a successful policy upload at one provider.
+ */
+struct ANASTASIS_ReduxSuccessDetail
+{
+ /**
+ * URL of the provider; the key this entry is stored under.
+ */
+ struct ANASTASIS_ProviderUrl provider_url;
+
+ /**
+ * When the stored policy expires.
+ */
+ struct GNUNET_TIME_Timestamp policy_expiration;
+
+ /**
+ * Version the policy was stored under.
+ */
+ uint32_t policy_version;
+};
+
+
+/**
+ * A payment we are asking the application to make for a policy upload.
+ */
+struct ANASTASIS_ReduxPolicyPaymentRequest
+{
+ /**
+ * Payment request URI.
+ */
+ char *payto;
+
+ /**
+ * Provider that wants to be paid.
+ */
+ struct ANASTASIS_ProviderUrl provider;
+};
+
+
+/**
+ * Arguments the application passed to the most recent "pay" action.
+ */
+struct ANASTASIS_ReduxPayArguments
+{
+ /**
+ * How long to wait for the payment to go through.
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * True if @e timeout was given.
+ */
+ bool have_timeout;
+};
+
+
+/**
+ * The `backup` variant of the reducer state.
+ */
+struct ANASTASIS_ReduxBackup
+{
+ /**
+ * Which step of the backup process we are at.
+ */
+ enum ANASTASIS_BackupState state;
+
+ /**
+ * Authentication methods the user configured.
+ */
+ struct ANASTASIS_ReduxAuthMethod *authentication_methods;
+
+ /**
+ * Length of the @e authentication_methods array.
+ */
+ unsigned int authentication_methods_len;
+
+ /**
+ * Policies over the @e authentication_methods.
+ */
+ struct ANASTASIS_ReduxPolicy *policies;
+
+ /**
+ * Length of the @e policies array.
+ */
+ unsigned int policies_len;
+
+ /**
+ * Providers we will store the recovery document at.
+ */
+ struct ANASTASIS_ReduxPolicyProvider *policy_providers;
+
+ /**
+ * Length of the @e policy_providers array.
+ */
+ unsigned int policy_providers_len;
+
+ /**
+ * Per-currency total of what the backup will cost.
+ */
+ struct TALER_Amount *upload_fees;
+
+ /**
+ * Length of the @e upload_fees array.
+ */
+ unsigned int upload_fees_len;
+
+ /**
+ * Taler pay URIs the application must settle before we can continue
+ * uploading truths.
+ */
+ char **payments;
+
+ /**
+ * Length of the @e payments array.
+ */
+ unsigned int payments_len;
+
+ /**
+ * Payments the application must settle before we can upload the
+ * recovery document.
+ */
+ struct ANASTASIS_ReduxPolicyPaymentRequest *policy_payment_requests;
+
+ /**
+ * Length of the @e policy_payment_requests array.
+ */
+ unsigned int policy_payment_requests_len;
+
+ /**
+ * Where the recovery document ended up, per provider.
+ */
+ struct ANASTASIS_ReduxSuccessDetail *success_details;
+
+ /**
+ * Length of the @e success_details array.
+ */
+ unsigned int success_details_len;
+
+ /**
+ * The secret to back up. Deliberately untyped: its serialization is
+ * literally the secret that gets uploaded, so the reducer must never
+ * normalize it. NULL if not (yet) set.
+ */
+ json_t *core_secret;
+
+ /**
+ * Name the user gave the secret, NULL if not set.
+ */
+ char *secret_name;
+
+ /**
+ * When the backup should expire.
+ */
+ struct GNUNET_TIME_Timestamp expiration;
+
+ /**
+ * True if @e expiration is set.
+ */
+ bool have_expiration;
+
+ /**
+ * Arguments of the last "pay" action.
+ */
+ struct ANASTASIS_ReduxPayArguments pay_arguments;
+
+ /**
+ * True if @e pay_arguments is set.
+ */
+ bool have_pay_arguments;
+
+ /**
+ * True if the state has an @e authentication_methods field at all.
+ * As in #ANASTASIS_ReduxCommon, presence is tracked separately from
+ * the length because an empty array is meaningful.
+ */
+ bool have_authentication_methods;
+
+ /**
+ * True if the state has a @e policies field at all.
+ */
+ bool have_policies;
+
+ /**
+ * True if the state has a @e policy_providers field at all.
+ */
+ bool have_policy_providers;
+
+ /**
+ * True if the state has an @e upload_fees field at all.
+ */
+ bool have_upload_fees;
+
+ /**
+ * True if the state has a @e payments field at all.
+ */
+ bool have_payments;
+
+ /**
+ * True if the state has a @e policy_payment_requests field at all.
+ */
+ bool have_policy_payment_requests;
+
+ /**
+ * True if the state has a @e success_details field at all.
+ */
+ bool have_success_details;
+};
+
+
+/* ******************* Recovery-specific state *********************** */
+
+/**
+ * A challenge the user could solve to recover a key share.
+ */
+struct ANASTASIS_ReduxChallengeInfo
+{
+ /**
+ * UUID of the truth behind this challenge.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+
+ /**
+ * Type of the challenge, e.g. "question".
+ */
+ char *type;
+
+ /**
+ * Instructions to show the user.
+ */
+ char *instructions;
+
+ /**
+ * Payment secret remembered from a payment request for this
+ * challenge; only valid if @e have_payment_secret.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret;
+
+ /**
+ * True if @e payment_secret is set.
+ */
+ bool have_payment_secret;
+};
+
+
+/**
+ * A decryption policy: solving all of its challenges recovers the
+ * secret.
+ */
+struct ANASTASIS_ReduxRecoveryPolicy
+{
+ /**
+ * UUIDs of the challenges in this policy; each refers to an entry of
+ * the recovery information's @e challenges array.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP *uuids;
+
+ /**
+ * Length of the @e uuids array.
+ */
+ unsigned int uuids_len;
+};
+
+
+/**
+ * What we learned about the recovery document we are recovering from.
+ */
+struct ANASTASIS_ReduxRecoveryInfo
+{
+ /**
+ * All challenges referenced by any policy.
+ */
+ struct ANASTASIS_ReduxChallengeInfo *challenges;
+
+ /**
+ * Length of the @e challenges array.
+ */
+ unsigned int challenges_len;
+
+ /**
+ * Alternative ways to recover the secret.
+ */
+ struct ANASTASIS_ReduxRecoveryPolicy *policies;
+
+ /**
+ * Length of the @e policies array.
+ */
+ unsigned int policies_len;
+
+ /**
+ * Name the user gave the secret, NULL if unknown.
+ */
+ char *secret_name;
+
+ /**
+ * Provider we downloaded the recovery document from.
+ */
+ struct ANASTASIS_ProviderUrl provider_url;
+
+ /**
+ * Version of the recovery document.
+ */
+ uint32_t version;
+};
+
+
+/**
+ * State of a challenge, as reported back to the application.
+ */
+enum ANASTASIS_ReduxFeedbackStatus
+{
+ /**
+ * The TAN was written to a file (test setups only).
+ */
+ ANASTASIS_RFS_CODE_IN_FILE = 0,
+
+ /**
+ * The TAN was sent to the user out-of-band.
+ */
+ ANASTASIS_RFS_SEND_TO_ADDRESS = 1,
+
+ /**
+ * A Taler payment is required first.
+ */
+ ANASTASIS_RFS_TALER_PAYMENT = 2,
+
+ /**
+ * The provider failed.
+ */
+ ANASTASIS_RFS_SERVER_FAILURE = 3,
+
+ /**
+ * The provider does not know this truth.
+ */
+ ANASTASIS_RFS_TRUTH_UNKNOWN = 4,
+
+ /**
+ * A wire transfer is required first.
+ */
+ ANASTASIS_RFS_IBAN_INSTRUCTIONS = 5,
+
+ /**
+ * The challenge was solved.
+ */
+ ANASTASIS_RFS_SOLVED = 6,
+
+ /**
+ * The answer the user gave was wrong.
+ */
+ ANASTASIS_RFS_INCORRECT_ANSWER = 7,
+
+ /**
+ * Too many attempts.
+ */
+ ANASTASIS_RFS_RATE_LIMIT_EXCEEDED = 8
+};
+
+
+/**
+ * Feedback on one challenge. Serializes as one entry of the
+ * `challenge_feedback` object, keyed by @e uuid.
+ */
+struct ANASTASIS_ReduxFeedback
+{
+ /**
+ * Challenge this feedback is about; the key this entry is stored
+ * under.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+
+ /**
+ * Which variant of @e details is valid.
+ */
+ enum ANASTASIS_ReduxFeedbackStatus status;
+
+ /**
+ * Human-readable, translated hint for the user. NULL for the
+ * variants that do not produce one.
+ */
+ char *display_hint;
+
+ /**
+ * Variant-specific details.
+ */
+ union
+ {
+
+ /**
+ * Details for #ANASTASIS_RFS_CODE_IN_FILE.
+ */
+ struct
+ {
+ /**
+ * File the TAN was written to.
+ */
+ char *filename;
+ } code_in_file;
+
+ /**
+ * Details for #ANASTASIS_RFS_SEND_TO_ADDRESS.
+ */
+ struct
+ {
+ /**
+ * Redacted address the TAN went to, NULL if the provider did not
+ * say (because it had already sent the TAN earlier).
+ */
+ char *address_hint;
+ } send_to_address;
+
+ /**
+ * Details for #ANASTASIS_RFS_TALER_PAYMENT.
+ */
+ struct
+ {
+ /**
+ * URI the application must pay.
+ */
+ char *taler_pay_uri;
+
+ /**
+ * Provider that wants to be paid.
+ */
+ struct ANASTASIS_ProviderUrl provider;
+
+ /**
+ * Secret identifying the payment.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret;
+ } taler_payment;
+
+ /**
+ * Details for #ANASTASIS_RFS_SERVER_FAILURE and
+ * #ANASTASIS_RFS_TRUTH_UNKNOWN.
+ */
+ struct
+ {
+ /**
+ * Error code reported.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * HTTP status reported.
+ */
+ uint32_t http_status;
+ } server_failure;
+
+ /**
+ * Details for #ANASTASIS_RFS_IBAN_INSTRUCTIONS.
+ */
+ struct
+ {
+ /**
+ * IBAN to wire the money to.
+ */
+ char *target_iban;
+
+ /**
+ * Name of the account holder.
+ */
+ char *target_business_name;
+
+ /**
+ * Subject to use for the wire transfer.
+ */
+ char *wire_transfer_subject;
+
+ /**
+ * Amount to wire.
+ */
+ struct TALER_Amount amount;
+ } iban_instructions;
+
+ /**
+ * Details for #ANASTASIS_RFS_INCORRECT_ANSWER.
+ */
+ struct
+ {
+ /**
+ * Error code reported.
+ */
+ enum TALER_ErrorCode ec;
+ } incorrect_answer;
+
+ /**
+ * Details for #ANASTASIS_RFS_RATE_LIMIT_EXCEEDED.
+ */
+ struct
+ {
+ /**
+ * Error code reported.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * How many attempts are permitted per @e request_frequency.
+ */
+ uint64_t request_limit;
+
+ /**
+ * Length of the rate-limiting window.
+ */
+ struct GNUNET_TIME_Relative request_frequency;
+ } rate_limit_exceeded;
+
+ } details;
+};
+
+
+/**
+ * The `recovery` variant of the reducer state.
+ */
+struct ANASTASIS_ReduxRecovery
+{
+ /**
+ * Which step of the recovery process we are at.
+ */
+ enum ANASTASIS_RecoveryState state;
+
+ /**
+ * The recovery operation itself. This replaces what used to be the
+ * `recovery_document` field: it is deserialized on parse (inert, no
+ * network activity until ANASTASIS_recovery_resume() is called) and
+ * serialized back on the way out. NULL before the recovery starts.
+ */
+ struct ANASTASIS_Recovery *r;
+
+ /**
+ * What we told the application about the recovery document, NULL
+ * before the recovery starts.
+ */
+ struct ANASTASIS_ReduxRecoveryInfo *ri;
+
+ /**
+ * Feedback on challenges the user tried, keyed by truth UUID.
+ */
+ struct ANASTASIS_ReduxFeedback *challenge_feedback;
+
+ /**
+ * Length of the @e challenge_feedback array.
+ */
+ unsigned int challenge_feedback_len;
+
+ /**
+ * Challenge the user is currently working on; only valid if
+ * @e have_selected_challenge.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP selected_challenge;
+
+ /**
+ * True if @e selected_challenge is set.
+ */
+ bool have_selected_challenge;
+
+ /**
+ * True if the state has a @e challenge_feedback field at all.
+ */
+ bool have_challenge_feedback;
+
+ /**
+ * The recovered secret. Deliberately untyped, see
+ * ANASTASIS_ReduxBackup::core_secret. NULL until recovery succeeds.
+ */
+ json_t *core_secret;
+};
+
+
+/* ********************** Error state ******************************** */
+
+/**
+ * The `error` variant of the reducer state.
+ */
+struct ANASTASIS_ReduxError
+{
+ /**
+ * The error that occurred.
+ */
+ enum TALER_ErrorCode code;
+
+ /**
+ * Hint derived from @e code, NULL if none.
+ */
+ char *hint;
+
+ /**
+ * Additional detail, NULL if none.
+ */
+ char *detail;
+};
+
+
+/* ********************** Top-level state **************************** */
+
+/**
+ * Which variant of a #ANASTASIS_ReduxState is in use.
+ */
+enum ANASTASIS_ReduxType
+{
+ /**
+ * A backup is in progress; `reducer_type` is "backup".
+ */
+ ANASTASIS_RT_BACKUP = 0,
+
+ /**
+ * A recovery is in progress; `reducer_type` is "recovery".
+ */
+ ANASTASIS_RT_RECOVERY = 1,
+
+ /**
+ * Something went wrong; `reducer_type` is "error".
+ */
+ ANASTASIS_RT_ERROR = 2
+};
+
+
+/**
+ * Fields shared by the backup and recovery variants.
+ */
+struct ANASTASIS_ReduxCommon
+{
+ /**
+ * Continents the user may pick from.
+ */
+ struct ANASTASIS_ReduxContinent *continents;
+
+ /**
+ * Length of the @e continents array.
+ */
+ unsigned int continents_len;
+
+ /**
+ * Countries on the selected continent.
+ */
+ struct ANASTASIS_ReduxCountry *countries;
+
+ /**
+ * Length of the @e countries array.
+ */
+ unsigned int countries_len;
+
+ /**
+ * Identity attributes we need from the user in the selected country.
+ */
+ struct ANASTASIS_ReduxAttributeSpec *required_attributes;
+
+ /**
+ * Length of the @e required_attributes array.
+ */
+ unsigned int required_attributes_len;
+
+ /**
+ * Providers we know about, keyed by URL.
+ */
+ struct ANASTASIS_ReduxProvider *providers;
+
+ /**
+ * Length of the @e providers array.
+ */
+ unsigned int providers_len;
+
+ /**
+ * The user's answers to @e required_attributes. Deliberately
+ * untyped: this object is dumped with sorted keys and PoW-hashed to
+ * derive the account key, so the reducer must not normalize it. NULL
+ * if the user has not answered yet.
+ */
+ json_t *identity_attributes;
+
+ /**
+ * Continent the user selected, NULL if none.
+ */
+ char *selected_continent;
+
+ /**
+ * Country the user selected, NULL if none.
+ */
+ char *selected_country;
+
+ /**
+ * True if the state has a @e continents field at all. An array can
+ * legitimately be present but empty, which is different from being
+ * absent, so presence is tracked separately from the length.
+ */
+ bool have_continents;
+
+ /**
+ * True if the state has a @e countries field at all.
+ */
+ bool have_countries;
+
+ /**
+ * True if the state has a @e required_attributes field at all.
+ */
+ bool have_required_attributes;
+
+ /**
+ * True if the state has an @e authentication_providers field at all.
+ */
+ bool have_providers;
+
+ /**
+ * Currencies in play, carried through for user interfaces.
+ */
+ char **currencies;
+
+ /**
+ * Length of the @e currencies array.
+ */
+ unsigned int currencies_len;
+
+ /**
+ * True if the state has a @e currencies field at all.
+ */
+ bool have_currencies;
+};
+
+
+/**
+ * A fully parsed reducer state.
+ */
+struct ANASTASIS_ReduxState
+{
+ /**
+ * Which variant of @e details is valid.
+ */
+ enum ANASTASIS_ReduxType type;
+
+ /**
+ * Fields common to the backup and recovery variants; unused when
+ * @e type is #ANASTASIS_RT_ERROR.
+ */
+ struct ANASTASIS_ReduxCommon common;
+
+ /**
+ * Variant-specific state.
+ */
+ union
+ {
+
+ /**
+ * Valid if @e type is #ANASTASIS_RT_BACKUP.
+ */
+ struct ANASTASIS_ReduxBackup backup;
+
+ /**
+ * Valid if @e type is #ANASTASIS_RT_RECOVERY.
+ */
+ struct ANASTASIS_ReduxRecovery recovery;
+
+ /**
+ * Valid if @e type is #ANASTASIS_RT_ERROR.
+ */
+ struct ANASTASIS_ReduxError error;
+
+ } details;
+};
+
+
+/* ******************* The three generic routines ******************** */
+
+/**
+ * Parse @a json into a typed reducer state. This is the only routine
+ * that reads the JSON encoding of a state.
+ *
+ * Parsing is strict: unknown fields, missing mandatory fields and
+ * values of the wrong type all fail.
+ *
+ * @param json the state to parse
+ * @param[out] ec set to the error that occurred, on failure
+ * @param[out] detail set to a static string naming the offending field,
+ * or NULL; never needs to be freed
+ * @return NULL on failure, otherwise the parsed state, to be freed with
+ * ANASTASIS_REDUX_state_free_()
+ */
+struct ANASTASIS_ReduxState *
+ANASTASIS_REDUX_state_parse_ (const json_t *json,
+ enum TALER_ErrorCode *ec,
+ const char **detail);
+
+
+/**
+ * Serialize @a rs back into its JSON encoding. This is the only
+ * routine that writes the JSON encoding of a state.
+ *
+ * @param rs the state to serialize
+ * @return the JSON encoding, caller must json_decref() it;
+ * NULL on failure
+ */
+json_t *
+ANASTASIS_REDUX_state_serialize_ (const struct ANASTASIS_ReduxState *rs);
+
+
+/**
+ * Release all resources held by @a rs, including any live
+ * `struct ANASTASIS_Recovery` and `struct ANASTASIS_Truth` handles.
+ *
+ * @param[in] rs the state to free, may be NULL
+ */
+void
+ANASTASIS_REDUX_state_free_ (struct ANASTASIS_ReduxState *rs);
+
+
+/**
+ * Serialize @a rs, hand the result to @a cb, and free @a rs. This is
+ * how every action returns its result: it guarantees that the JSON
+ * encoding is produced in exactly one place and that the state is not
+ * leaked on any path.
+ *
+ * @param[in] rs state to return and free
+ * @param cb callback to invoke
+ * @param cb_cls closure for @a cb
+ * @param ec error code to report alongside the state
+ */
+void
+ANASTASIS_REDUX_return_ (struct ANASTASIS_ReduxState *rs,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls,
+ enum TALER_ErrorCode ec);
+
+
+/* ************************ Small helpers **************************** */
+
+/**
+ * Find the provider with the given @a url in @a common.
+ *
+ * @param common state to search
+ * @param url provider URL to look for
+ * @return NULL if not found
+ */
+struct ANASTASIS_ReduxProvider *
+ANASTASIS_REDUX_provider_find_ (const struct ANASTASIS_ReduxCommon *common,
+ const char *url);
+
+
+/**
+ * Drop all providers from @a common, freeing them. Used when the set
+ * of applicable providers changes wholesale, e.g. because the user
+ * picked a different country.
+ *
+ * @param[in,out] common state to clear
+ */
+void
+ANASTASIS_REDUX_providers_clear_ (struct ANASTASIS_ReduxCommon *common);
+
+
+/**
+ * Find, or create, the provider with the given @a url in @a common.
+ *
+ * @param[in,out] common state to search and possibly grow
+ * @param url provider URL to look for
+ * @return the existing or newly added (and #ANASTASIS_RPS_NOT_CONTACTED)
+ * provider entry
+ */
+struct ANASTASIS_ReduxProvider *
+ANASTASIS_REDUX_provider_get_ (struct ANASTASIS_ReduxCommon *common,
+ const char *url);
+
+
+/**
+ * Find the feedback for challenge @a uuid in @a rr.
+ *
+ * @param rr recovery state to search
+ * @param uuid challenge to look for
+ * @return NULL if not found
+ */
+struct ANASTASIS_ReduxFeedback *
+ANASTASIS_REDUX_feedback_find_ (const struct ANASTASIS_ReduxRecovery *rr,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid);
+
+
+/**
+ * Find, or create, the feedback entry for challenge @a uuid in @a rr.
+ * An existing entry is reset (its variant-specific fields freed) so
+ * that the caller can fill in a fresh variant.
+ *
+ * @param[in,out] rr recovery state to search and possibly grow
+ * @param uuid challenge to look for
+ * @return the entry to fill in
+ */
+struct ANASTASIS_ReduxFeedback *
+ANASTASIS_REDUX_feedback_get_ (struct ANASTASIS_ReduxRecovery *rr,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid);
+
+
+/**
+ * Free the variant-specific contents of @a fb (including the display
+ * hint), but not @a fb itself, and zero them. Used both when freeing
+ * a state and when overwriting a feedback entry with a new variant.
+ *
+ * @param[in,out] fb feedback to reset
+ */
+void
+ANASTASIS_REDUX_feedback_clear_ (struct ANASTASIS_ReduxFeedback *fb);
+
+
+/**
+ * Find the challenge with the given @a uuid in the recovery
+ * information of @a rr.
+ *
+ * @param rr recovery state to search
+ * @param uuid challenge to look for
+ * @return NULL if not found
+ */
+struct ANASTASIS_ReduxChallengeInfo *
+ANASTASIS_REDUX_challenge_find_ (const struct ANASTASIS_ReduxRecovery *rr,
+ const struct
+ ANASTASIS_CRYPTO_TruthUUIDP *uuid);
+
+
+/**
+ * Free the contents of @a pu and set it to NULL.
+ *
+ * @param[in,out] pu provider URL to clear
+ */
+void
+ANASTASIS_REDUX_provider_url_clear_ (struct ANASTASIS_ProviderUrl *pu);
+
+
+/**
+ * Set @a pu to a copy of @a url, freeing any previous value.
+ *
+ * @param[in,out] pu provider URL to set
+ * @param url the URL to copy
+ */
+void
+ANASTASIS_REDUX_provider_url_set_ (struct ANASTASIS_ProviderUrl *pu,
+ const char *url);
+
+
+#endif
+
+/* end of anastasis_api_redux_state.h */
diff --git a/src/reducer/test_redux_codec.c b/src/reducer/test_redux_codec.c
@@ -0,0 +1,365 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 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/test_redux_codec.c
+ * @brief tests for the typed reducer state codec
+ * @author Christian Grothoff
+ *
+ * Checks three properties of ANASTASIS_REDUX_state_parse_() and
+ * ANASTASIS_REDUX_state_serialize_():
+ *
+ * - every state the CLI test suite feeds the reducer parses;
+ * - the codec is idempotent (serialize . parse . serialize == serialize .
+ * parse), so no field is lost or invented on the way through;
+ * - unknown fields are rejected rather than silently dropped.
+ *
+ * The codec is also exercised end-to-end against live states by the `cli`
+ * suite, which drives the real reducer; this test is the fast, offline part
+ * that needs neither a database nor a provider.
+ */
+#include "platform.h"
+#include "anastasis_redux.h"
+#include "anastasis_api_redux.h"
+#include "anastasis_api_redux_state.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Return value of the test, 0 on success.
+ */
+static int global_ret;
+
+
+/**
+ * Check that @a json parses and that the codec is idempotent on it.
+ *
+ * @param name name of the input, for error messages
+ * @param json state to check
+ */
+static void
+check_idempotent (const char *name,
+ const json_t *json)
+{
+ struct ANASTASIS_ReduxState *rs;
+ enum TALER_ErrorCode ec;
+ const char *detail;
+ json_t *once;
+ json_t *twice;
+
+ rs = ANASTASIS_REDUX_state_parse_ (json,
+ &ec,
+ &detail);
+ if (NULL == rs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%s: failed to parse at `%s'\n",
+ name,
+ detail);
+ global_ret = 1;
+ return;
+ }
+ once = ANASTASIS_REDUX_state_serialize_ (rs);
+ GNUNET_assert (NULL != once);
+ ANASTASIS_REDUX_state_free_ (rs);
+
+ rs = ANASTASIS_REDUX_state_parse_ (once,
+ &ec,
+ &detail);
+ if (NULL == rs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%s: own output failed to re-parse at `%s'\n",
+ name,
+ detail);
+ json_decref (once);
+ global_ret = 1;
+ return;
+ }
+ twice = ANASTASIS_REDUX_state_serialize_ (rs);
+ GNUNET_assert (NULL != twice);
+ ANASTASIS_REDUX_state_free_ (rs);
+
+ if (1 != json_equal (once,
+ twice))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%s: codec is not idempotent\n",
+ name);
+ json_dumpf (once,
+ stderr,
+ JSON_INDENT (2) | JSON_SORT_KEYS);
+ json_dumpf (twice,
+ stderr,
+ JSON_INDENT (2) | JSON_SORT_KEYS);
+ global_ret = 1;
+ }
+ json_decref (once);
+ json_decref (twice);
+}
+
+
+/**
+ * Check that @a json is rejected by the parser.
+ *
+ * @param name name of the input, for error messages
+ * @param json state that must not parse
+ */
+static void
+check_rejected (const char *name,
+ const json_t *json)
+{
+ struct ANASTASIS_ReduxState *rs;
+ enum TALER_ErrorCode ec;
+ const char *detail;
+
+ rs = ANASTASIS_REDUX_state_parse_ (json,
+ &ec,
+ &detail);
+ if (NULL != rs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%s: expected rejection, but the state parsed\n",
+ name);
+ ANASTASIS_REDUX_state_free_ (rs);
+ global_ret = 1;
+ return;
+ }
+ if (TALER_EC_ANASTASIS_REDUCER_STATE_INVALID != ec)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%s: rejected with unexpected error code %d\n",
+ name,
+ (int) ec);
+ global_ret = 1;
+ }
+}
+
+
+/**
+ * Run the codec over every state fixture the CLI tests use.
+ *
+ * @param srcdir directory holding the `resources/` fixtures
+ */
+static void
+test_fixtures (const char *srcdir)
+{
+ static const char *const files[] = {
+ "00-backup.json",
+ "01-backup.json",
+ "02-backup.json",
+ "03-backup.json",
+ "04-backup.json",
+ "05-backup.json",
+ "06-backup.json",
+ "00-recovery.json",
+ "01-recovery.json",
+ "02-recovery.json",
+ NULL
+ };
+
+ for (unsigned int i = 0; NULL != files[i]; i++)
+ {
+ json_error_t err;
+ json_t *json;
+ char *fn;
+
+ GNUNET_asprintf (&fn,
+ "%s/resources/%s",
+ srcdir,
+ files[i]);
+ json = json_load_file (fn,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == json)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load `%s': %s\n",
+ fn,
+ err.text);
+ GNUNET_free (fn);
+ global_ret = 1;
+ continue;
+ }
+ check_idempotent (files[i],
+ json);
+ json_decref (json);
+ GNUNET_free (fn);
+ }
+}
+
+
+/**
+ * Check that every challenge feedback variant survives the codec.
+ * These are the states with the most structure (a tagged union), and
+ * several of them only occur against a live provider, so they are
+ * covered here rather than by the CLI tests.
+ */
+static void
+test_challenge_feedback (void)
+{
+ static const char *const variants[] = {
+ "{\"state\":\"code-in-file\",\"filename\":\"/tmp/tan\","
+ "\"display_hint\":\"look here\"}",
+ "{\"state\":\"send-to-address\",\"address_hint\":\"a@b.c\","
+ "\"display_hint\":\"sent\"}",
+ "{\"state\":\"send-to-address\",\"display_hint\":\"already sent\"}",
+ "{\"state\":\"taler-payment\",\"taler_pay_uri\":\"taler://pay/x\","
+ "\"provider\":\"http://localhost:8086/\",\"display_hint\":\"pay\","
+ "\"payment_secret\":\"3F1QF6ZY2H0V4CQNVQ8YV2P8V0Y0EWWG14JBQZ3M4P2C1Y8XM53G\"}",
+ "{\"state\":\"server-failure\",\"http_status\":500,\"error_code\":1}",
+ "{\"state\":\"truth-unknown\",\"http_status\":404,\"error_code\":8108}",
+ "{\"state\":\"iban-instructions\",\"target_iban\":\"DE12\","
+ "\"target_business_name\":\"Bank\",\"wire_transfer_subject\":\"subj\","
+ "\"challenge_amount\":\"TESTKUDOS:1\",\"display_hint\":\"wire it\"}",
+ "{\"state\":\"solved\"}",
+ "{\"state\":\"incorrect-answer\",\"error_code\":8110}",
+ "{\"state\":\"rate-limit-exceeded\",\"error_code\":8111,"
+ "\"request_limit\":3,\"request_frequency\":{\"d_us\":1000000},"
+ "\"display_hint\":\"slow down\"}",
+ NULL
+ };
+ static const char *const uuid
+ = "3AHRZ6DMV29GQY5CKT18FPX4BJS07ENW3AHRZ6DMV29GQY5CKT18";
+
+ for (unsigned int i = 0; NULL != variants[i]; i++)
+ {
+ json_error_t err;
+ json_t *fb;
+ json_t *state;
+
+ fb = json_loads (variants[i],
+ JSON_REJECT_DUPLICATES,
+ &err);
+ GNUNET_assert (NULL != fb);
+ state = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("reducer_type",
+ "recovery"),
+ GNUNET_JSON_pack_string ("recovery_state",
+ "CHALLENGE_SOLVING"));
+ {
+ json_t *cf = json_object ();
+
+ GNUNET_assert (NULL != cf);
+ GNUNET_assert (0 ==
+ json_object_set_new (cf,
+ uuid,
+ fb));
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "challenge_feedback",
+ cf));
+ }
+ check_idempotent (variants[i],
+ state);
+ json_decref (state);
+ }
+}
+
+
+/**
+ * Check that fields the reducer does not know about are rejected
+ * instead of being silently carried along or dropped.
+ */
+static void
+test_strictness (void)
+{
+ static const char *const bad[] = {
+ /* unknown field at the top level (this is the shape the TypeScript
+ reducer produces; see dold.md) */
+ "{\"reducer_type\":\"recovery\",\"recovery_state\":\"SECRET_SELECTING\","
+ "\"verbatim_recovery_document\":{}}",
+ /* unknown field inside a nested object */
+ "{\"reducer_type\":\"backup\",\"backup_state\":\"CONTINENT_SELECTING\","
+ "\"continents\":[{\"name\":\"Europe\",\"bogus\":1}]}",
+ /* unknown provider status */
+ "{\"reducer_type\":\"backup\",\"backup_state\":\"CONTINENT_SELECTING\","
+ "\"authentication_providers\":{\"http://x/\":{\"status\":\"weird\"}}}",
+ /* unknown challenge feedback variant */
+ "{\"reducer_type\":\"recovery\",\"recovery_state\":\"CHALLENGE_SOLVING\","
+ "\"challenge_feedback\":{\"3AHRZ6DMV29GQY5CKT18FPX4BJS07ENW3AHRZ6DMV29GQ"
+ "Y5CKT18\":{\"state\":\"invented\"}}}",
+ /* bogus state name */
+ "{\"reducer_type\":\"backup\",\"backup_state\":\"NOT_A_STATE\"}",
+ /* neither backup nor recovery nor error */
+ "{\"reducer_type\":\"sideways\"}",
+ NULL
+ };
+
+ for (unsigned int i = 0; NULL != bad[i]; i++)
+ {
+ json_error_t err;
+ json_t *json;
+
+ json = json_loads (bad[i],
+ JSON_REJECT_DUPLICATES,
+ &err);
+ GNUNET_assert (NULL != json);
+ check_rejected (bad[i],
+ json);
+ json_decref (json);
+ }
+}
+
+
+/**
+ * Check that an `error` state survives the codec.
+ */
+static void
+test_error_state (void)
+{
+ json_t *json;
+
+ json = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("reducer_type",
+ "error"),
+ GNUNET_JSON_pack_uint64 ("code",
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID),
+ GNUNET_JSON_pack_string ("hint",
+ "a hint"),
+ GNUNET_JSON_pack_string ("detail",
+ "a detail"));
+ check_idempotent ("error state",
+ json);
+ json_decref (json);
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ const char *srcdir;
+
+ (void) argc;
+ GNUNET_log_setup ("test-redux-codec",
+ "WARNING",
+ NULL);
+ srcdir = getenv ("ANASTASIS_CLI_SRCDIR");
+ if (NULL == srcdir)
+ {
+ fprintf (stderr,
+ "ANASTASIS_CLI_SRCDIR not set, skipping\n");
+ return 77;
+ }
+ test_fixtures (srcdir);
+ test_challenge_feedback ();
+ test_strictness ();
+ test_error_state ();
+ return global_ret;
+}
+
+
+/* end of test_redux_codec.c */