diff options
Diffstat (limited to 'src/lib/exchange_api_handle.c')
-rw-r--r-- | src/lib/exchange_api_handle.c | 2663 |
1 files changed, 1436 insertions, 1227 deletions
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index 0e76b289d..fdadc8d2a 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2014-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published @@ -40,12 +40,17 @@ * Which version of the Taler protocol is implemented * by this library? Used to determine compatibility. */ -#define EXCHANGE_PROTOCOL_CURRENT 13 +#define EXCHANGE_PROTOCOL_CURRENT 19 /** * How many versions are we backwards compatible with? */ -#define EXCHANGE_PROTOCOL_AGE 0 +#define EXCHANGE_PROTOCOL_AGE 2 + +/** + * Set to 1 for extra debug logging. + */ +#define DEBUG 0 /** * Current version for (local) JSON serialization of persisted @@ -54,7 +59,7 @@ #define EXCHANGE_SERIALIZATION_FORMAT_VERSION 0 /** - * How far off do we allow key liftimes to be? + * How far off do we allow key lifetimes to be? */ #define LIFETIME_TOLERANCE GNUNET_TIME_UNIT_HOURS @@ -65,175 +70,311 @@ #define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS /** - * Set to 1 for extra debug logging. - */ -#define DEBUG 0 - -/** - * Log error related to CURL operations. - * - * @param type log level - * @param function which function failed to run - * @param code what was the curl error code - */ -#define CURL_STRERROR(type, function, code) \ - GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \ - function, __FILE__, __LINE__, curl_easy_strerror (code)); - - -/** - * Data for the request to get the /keys of a exchange. + * If the "Expire" cache control header is missing, for + * how long do we assume the reply to be valid at least? */ -struct KeysRequest; +#define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \ + GNUNET_TIME_UNIT_MINUTES, 2) /** - * Entry in DLL of auditors used by an exchange. + * Handle for a GET /keys request. */ -struct TEAH_AuditorListEntry +struct TALER_EXCHANGE_GetKeysHandle { - /** - * Next pointer of DLL. - */ - struct TEAH_AuditorListEntry *next; /** - * Prev pointer of DLL. + * The exchange base URL (i.e. "https://exchange.demo.taler.net/") */ - struct TEAH_AuditorListEntry *prev; + char *exchange_url; /** - * Base URL of the auditor. + * The url for the /keys request. */ - char *auditor_url; + char *url; /** - * Handle to the auditor. + * Previous /keys response, NULL for none. */ - struct TALER_AUDITOR_Handle *ah; + struct TALER_EXCHANGE_Keys *prev_keys; /** - * Head of DLL of interactions with this auditor. + * Entry for this request with the `struct GNUNET_CURL_Context`. */ - struct TEAH_AuditorInteractionEntry *ai_head; + struct GNUNET_CURL_Job *job; /** - * Tail of DLL of interactions with this auditor. + * Expiration time according to "Expire:" header. + * 0 if not provided by the server. */ - struct TEAH_AuditorInteractionEntry *ai_tail; + struct GNUNET_TIME_Timestamp expire; /** - * Public key of the auditor. + * Function to call with the exchange's certification data, + * NULL if this has already been done. */ - struct TALER_AuditorPublicKeyP auditor_pub; + TALER_EXCHANGE_GetKeysCallback cert_cb; /** - * Flag indicating that the auditor is available and that protocol - * version compatibility is given. + * Closure to pass to @e cert_cb. */ - bool is_up; + void *cert_cb_cls; }; -/* ***************** Internal /keys fetching ************* */ - /** - * Data for the request to get the /keys of a exchange. + * Element in the `struct SignatureContext` array. */ -struct KeysRequest +struct SignatureElement { + /** - * The connection to exchange this request handle will use + * Offset of the denomination in the group array, + * for sorting (2nd rank, ascending). */ - struct TALER_EXCHANGE_Handle *exchange; + unsigned int offset; /** - * The url for this handle + * Offset of the group in the denominations array, + * for sorting (2nd rank, ascending). */ - char *url; + unsigned int group_offset; /** - * Entry for this request with the `struct GNUNET_CURL_Context`. + * Pointer to actual master signature to hash over. */ - struct GNUNET_CURL_Job *job; + struct TALER_MasterSignatureP master_sig; +}; +/** + * Context for collecting the array of master signatures + * needed to verify the exchange_sig online signature. + */ +struct SignatureContext +{ /** - * Expiration time according to "Expire:" header. - * 0 if not provided by the server. + * Array of signatures to hash over. */ - struct GNUNET_TIME_Timestamp expire; + struct SignatureElement *elements; + + /** + * Write offset in the @e elements array. + */ + unsigned int elements_pos; + /** + * Allocated space for @e elements. + */ + unsigned int elements_size; }; -void -TEAH_acc_confirmation_cb (void *cls, - const struct TALER_AUDITOR_HttpResponse *hr) +/** + * Determine order to sort two elements by before + * we hash the master signatures. Used for + * sorting with qsort(). + * + * @param a pointer to a `struct SignatureElement` + * @param b pointer to a `struct SignatureElement` + * @return 0 if equal, -1 if a < b, 1 if a > b. + */ +static int +signature_context_sort_cb (const void *a, + const void *b) { - struct TEAH_AuditorInteractionEntry *aie = cls; - struct TEAH_AuditorListEntry *ale = aie->ale; + const struct SignatureElement *sa = a; + const struct SignatureElement *sb = b; - if (MHD_HTTP_OK != hr->http_status) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to submit deposit confirmation to auditor `%s' with HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n", - ale->auditor_url, - hr->http_status, - hr->ec); - } - GNUNET_CONTAINER_DLL_remove (ale->ai_head, - ale->ai_tail, - aie); - GNUNET_free (aie); + if (sa->group_offset < sb->group_offset) + return -1; + if (sa->group_offset > sb->group_offset) + return 1; + if (sa->offset < sb->offset) + return -1; + if (sa->offset > sb->offset) + return 1; + /* We should never have two disjoint elements + with same time and offset */ + GNUNET_assert (sa == sb); + return 0; } -void -TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h, - TEAH_AuditorCallback ac, - void *ac_cls) +/** + * Append a @a master_sig to the @a sig_ctx using the + * given attributes for (later) sorting. + * + * @param[in,out] sig_ctx signature context to update + * @param group_offset offset for the group + * @param offset offset for the entry + * @param master_sig master signature for the entry + */ +static void +append_signature (struct SignatureContext *sig_ctx, + unsigned int group_offset, + unsigned int offset, + const struct TALER_MasterSignatureP *master_sig) { - if (NULL == h->auditors_head) + struct SignatureElement *element; + unsigned int new_size; + + if (sig_ctx->elements_pos == sig_ctx->elements_size) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "No auditor available for exchange `%s'. Not submitting deposit confirmations.\n", - h->url); - return; + if (0 == sig_ctx->elements_size) + new_size = 1024; + else + new_size = sig_ctx->elements_size * 2; + GNUNET_array_grow (sig_ctx->elements, + sig_ctx->elements_size, + new_size); } - for (struct TEAH_AuditorListEntry *ale = h->auditors_head; - NULL != ale; - ale = ale->next) + element = &sig_ctx->elements[sig_ctx->elements_pos++]; + element->offset = offset; + element->group_offset = group_offset; + element->master_sig = *master_sig; +} + + +/** + * Frees @a wfm array. + * + * @param wfm fee array to release + * @param wfm_len length of the @a wfm array + */ +static void +free_fees (struct TALER_EXCHANGE_WireFeesByMethod *wfm, + unsigned int wfm_len) +{ + for (unsigned int i = 0; i<wfm_len; i++) { - struct TEAH_AuditorInteractionEntry *aie; + struct TALER_EXCHANGE_WireFeesByMethod *wfmi = &wfm[i]; - if (! ale->is_up) - continue; - aie = ac (ac_cls, - ale->ah, - &ale->auditor_pub); - if (NULL != aie) + while (NULL != wfmi->fees_head) { - aie->ale = ale; - GNUNET_CONTAINER_DLL_insert (ale->ai_head, - ale->ai_tail, - aie); + struct TALER_EXCHANGE_WireAggregateFees *fe + = wfmi->fees_head; + + wfmi->fees_head = fe->next; + GNUNET_free (fe); } + GNUNET_free (wfmi->method); } + GNUNET_free (wfm); } /** - * Release memory occupied by a keys request. Note that this does not - * cancel the request itself. + * Parse wire @a fees and return array. * - * @param kr request to free + * @param master_pub master public key to use to check signatures + * @param currency currency amounts are expected in + * @param fees json AggregateTransferFee to parse + * @param[out] fees_len set to length of returned array + * @return NULL on error */ -static void -free_keys_request (struct KeysRequest *kr) +static struct TALER_EXCHANGE_WireFeesByMethod * +parse_fees (const struct TALER_MasterPublicKeyP *master_pub, + const char *currency, + const json_t *fees, + unsigned int *fees_len) +{ + struct TALER_EXCHANGE_WireFeesByMethod *fbm; + size_t fbml = json_object_size (fees); + unsigned int i = 0; + const char *key; + const json_t *fee_array; + + if (UINT_MAX < fbml) + { + GNUNET_break (0); + return NULL; + } + fbm = GNUNET_new_array (fbml, + struct TALER_EXCHANGE_WireFeesByMethod); + *fees_len = (unsigned int) fbml; + json_object_foreach ((json_t *) fees, key, fee_array) { + struct TALER_EXCHANGE_WireFeesByMethod *fe = &fbm[i++]; + size_t idx; + json_t *fee; + + fe->method = GNUNET_strdup (key); + fe->fees_head = NULL; + json_array_foreach (fee_array, idx, fee) + { + struct TALER_EXCHANGE_WireAggregateFees *wa + = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees); + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("sig", + &wa->master_sig), + TALER_JSON_spec_amount ("wire_fee", + currency, + &wa->fees.wire), + TALER_JSON_spec_amount ("closing_fee", + currency, + &wa->fees.closing), + GNUNET_JSON_spec_timestamp ("start_date", + &wa->start_date), + GNUNET_JSON_spec_timestamp ("end_date", + &wa->end_date), + GNUNET_JSON_spec_end () + }; + + wa->next = fe->fees_head; + fe->fees_head = wa; + if (GNUNET_OK != + GNUNET_JSON_parse (fee, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + free_fees (fbm, + i); + return NULL; + } + if (GNUNET_OK != + TALER_exchange_offline_wire_fee_verify ( + key, + wa->start_date, + wa->end_date, + &wa->fees, + master_pub, + &wa->master_sig)) + { + GNUNET_break_op (0); + free_fees (fbm, + i); + return NULL; + } + } /* for all fees over time */ + } /* for all methods */ + GNUNET_assert (i == fbml); + return fbm; +} + + +void +TEAH_get_auditors_for_dc ( + struct TALER_EXCHANGE_Keys *keys, + TEAH_AuditorCallback ac, + void *ac_cls) { - GNUNET_free (kr->url); - GNUNET_free (kr); + if (0 == keys->num_auditors) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No auditor available. Not submitting deposit confirmations.\n"); + return; + } + for (unsigned int i = 0; i<keys->num_auditors; i++) + { + const struct TALER_EXCHANGE_AuditorInformation *auditor + = &keys->auditors[i]; + + ac (ac_cls, + auditor->auditor_url, + &auditor->auditor_pub); + } } @@ -248,21 +389,20 @@ free_keys_request (struct KeysRequest *kr) * * @param[out] sign_key where to return the result * @param check_sigs should we check signatures? - * @param[in] sign_key_obj json to parse + * @param sign_key_obj json to parse * @param master_key master key to use to verify signature * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is - * invalid or the json malformed. + * invalid or the @a sign_key_obj is malformed. */ static enum GNUNET_GenericReturnValue parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, bool check_sigs, - json_t *sign_key_obj, + const json_t *sign_key_obj, const struct TALER_MasterPublicKeyP *master_key) { - struct TALER_MasterSignatureP sign_key_issue_sig; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("master_sig", - &sign_key_issue_sig), + &sign_key->master_sig), GNUNET_JSON_spec_fixed_auto ("key", &sign_key->key), GNUNET_JSON_spec_timestamp ("stamp_start", @@ -282,7 +422,6 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, GNUNET_break_op (0); return GNUNET_SYSERR; } - if (! check_sigs) return GNUNET_OK; if (GNUNET_OK != @@ -292,35 +431,45 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, sign_key->valid_until, sign_key->valid_legal, master_key, - &sign_key_issue_sig)) + &sign_key->master_sig)) { GNUNET_break_op (0); return GNUNET_SYSERR; } - sign_key->master_sig = sign_key_issue_sig; return GNUNET_OK; } /** - * Parse a exchange's denomination key encoded in JSON. + * Parse a exchange's denomination key encoded in JSON partially. + * + * Only the values for master_sig, timestamps and the cipher-specific public + * key are parsed. All other fields (fees, age_mask, value) MUST have been set + * prior to calling this function, otherwise the signature verification + * performed within this function will fail. * - * @param currency expected currency of all fees * @param[out] denom_key where to return the result + * @param cipher cipher type to parse * @param check_sigs should we check signatures? - * @param[in] denom_key_obj json to parse + * @param denom_key_obj json to parse * @param master_key master key to use to verify signature - * @param hash_context where to accumulate data for signature verification + * @param group_offset offset for the group + * @param index index of this denomination key in the group + * @param sig_ctx where to write details about encountered + * master signatures, NULL if not used * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is * invalid or the json malformed. */ static enum GNUNET_GenericReturnValue -parse_json_denomkey (const char *currency, - struct TALER_EXCHANGE_DenomPublicKey *denom_key, - bool check_sigs, - json_t *denom_key_obj, - struct TALER_MasterPublicKeyP *master_key, - struct GNUNET_HashContext *hash_context) +parse_json_denomkey_partially ( + struct TALER_EXCHANGE_DenomPublicKey *denom_key, + enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher, + bool check_sigs, + const json_t *denom_key_obj, + struct TALER_MasterPublicKeyP *master_key, + unsigned int group_offset, + unsigned int index, + struct SignatureContext *sig_ctx) { struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("master_sig", @@ -333,14 +482,13 @@ parse_json_denomkey (const char *currency, &denom_key->valid_from), GNUNET_JSON_spec_timestamp ("stamp_expire_legal", &denom_key->expire_legal), - TALER_JSON_spec_amount ("value", - currency, - &denom_key->value), - TALER_JSON_SPEC_DENOM_FEES ("fee", - currency, - &denom_key->fees), - TALER_JSON_spec_denom_pub ("denom_pub", - &denom_key->key), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("lost", + &denom_key->lost), + NULL), + TALER_JSON_spec_denom_pub_cipher (NULL, + cipher, + &denom_key->key), GNUNET_JSON_spec_end () }; @@ -354,10 +502,11 @@ parse_json_denomkey (const char *currency, } TALER_denom_pub_hash (&denom_key->key, &denom_key->h_key); - if (NULL != hash_context) - GNUNET_CRYPTO_hash_context_read (hash_context, - &denom_key->h_key, - sizeof (struct GNUNET_HashCode)); + if (NULL != sig_ctx) + append_signature (sig_ctx, + group_offset, + index, + &denom_key->master_sig); if (! check_sigs) return GNUNET_OK; EXITIF (GNUNET_SYSERR == @@ -373,11 +522,11 @@ parse_json_denomkey (const char *currency, &denom_key->master_sig)); return GNUNET_OK; EXITIF_exit: + GNUNET_JSON_parse_free (spec); /* invalidate denom_key, just to be sure */ memset (denom_key, 0, sizeof (*denom_key)); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } @@ -387,7 +536,7 @@ EXITIF_exit: * * @param[out] auditor where to return the result * @param check_sigs should we check signatures - * @param[in] auditor_obj json to parse + * @param auditor_obj json to parse * @param key_data information about denomination keys * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is * invalid or the json malformed. @@ -395,22 +544,21 @@ EXITIF_exit: static enum GNUNET_GenericReturnValue parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, bool check_sigs, - json_t *auditor_obj, + const json_t *auditor_obj, const struct TALER_EXCHANGE_Keys *key_data) { - json_t *keys; + const json_t *keys; json_t *key; - unsigned int len; - unsigned int off; - unsigned int i; + size_t off; + size_t pos; const char *auditor_url; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("auditor_pub", &auditor->auditor_pub), - GNUNET_JSON_spec_string ("auditor_url", + TALER_JSON_spec_web_url ("auditor_url", &auditor_url), - GNUNET_JSON_spec_json ("denomination_keys", - &keys), + GNUNET_JSON_spec_array_const ("denomination_keys", + &keys), GNUNET_JSON_spec_end () }; @@ -428,16 +576,15 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, return GNUNET_SYSERR; } auditor->auditor_url = GNUNET_strdup (auditor_url); - len = json_array_size (keys); - auditor->denom_keys = GNUNET_new_array (len, - struct - TALER_EXCHANGE_AuditorDenominationInfo); - off = 0; - json_array_foreach (keys, i, key) { + auditor->denom_keys + = GNUNET_new_array (json_array_size (keys), + struct TALER_EXCHANGE_AuditorDenominationInfo); + pos = 0; + json_array_foreach (keys, off, key) { struct TALER_AuditorSignatureP auditor_sig; struct TALER_DenominationHashP denom_h; - const struct TALER_EXCHANGE_DenomPublicKey *dk; - unsigned int dk_off; + const struct TALER_EXCHANGE_DenomPublicKey *dk = NULL; + unsigned int dk_off = UINT_MAX; struct GNUNET_JSON_Specification kspec[] = { GNUNET_JSON_spec_fixed_auto ("auditor_sig", &auditor_sig), @@ -454,8 +601,6 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, GNUNET_break_op (0); continue; } - dk = NULL; - dk_off = UINT_MAX; for (unsigned int j = 0; j<key_data->num_denom_keys; j++) { if (0 == GNUNET_memcmp (&denom_h, @@ -490,16 +635,19 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, &auditor_sig)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } } - auditor->denom_keys[off].denom_key_offset = dk_off; - auditor->denom_keys[off].auditor_sig = auditor_sig; - off++; + auditor->denom_keys[pos].denom_key_offset = dk_off; + auditor->denom_keys[pos].auditor_sig = auditor_sig; + pos++; } - auditor->num_denom_keys = off; - GNUNET_JSON_parse_free (spec); + if (pos > UINT_MAX) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + auditor->num_denom_keys = (unsigned int) pos; return GNUNET_OK; } @@ -509,7 +657,7 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, * * @param[out] gf where to return the result * @param check_sigs should we check signatures - * @param[in] fee_obj json to parse + * @param fee_obj json to parse * @param key_data already parsed information about the exchange * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is * invalid or the json malformed. @@ -517,7 +665,7 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, static enum GNUNET_GenericReturnValue parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf, bool check_sigs, - json_t *fee_obj, + const json_t *fee_obj, const struct TALER_EXCHANGE_Keys *key_data) { struct GNUNET_JSON_Specification spec[] = { @@ -527,8 +675,6 @@ parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf, &gf->end_date), GNUNET_JSON_spec_relative_time ("purse_timeout", &gf->purse_timeout), - GNUNET_JSON_spec_relative_time ("account_kyc_timeout", - &gf->kyc_timeout), GNUNET_JSON_spec_relative_time ("history_expiration", &gf->history_expiration), GNUNET_JSON_spec_uint32 ("purse_account_limit", @@ -561,7 +707,6 @@ parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf, gf->end_date, &gf->fees, gf->purse_timeout, - gf->kyc_timeout, gf->history_expiration, gf->purse_account_limit, &key_data->master_pub, @@ -578,113 +723,12 @@ parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf, /** - * Function called with information about the auditor. Marks an - * auditor as 'up'. - * - * @param cls closure, a `struct TEAH_AuditorListEntry *` - * @param hr http response from the auditor - * @param vi basic information about the auditor - * @param compat protocol compatibility information - */ -static void -auditor_version_cb ( - void *cls, - const struct TALER_AUDITOR_HttpResponse *hr, - const struct TALER_AUDITOR_VersionInformation *vi, - enum TALER_AUDITOR_VersionCompatibility compat) -{ - struct TEAH_AuditorListEntry *ale = cls; - - (void) hr; - if (NULL == vi) - { - /* In this case, we don't mark the auditor as 'up' */ - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Auditor `%s' gave unexpected version response.\n", - ale->auditor_url); - return; - } - - if (0 != (TALER_AUDITOR_VC_INCOMPATIBLE & compat)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Auditor `%s' runs incompatible protocol version!\n", - ale->auditor_url); - if (0 != (TALER_AUDITOR_VC_OLDER & compat)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Auditor `%s' runs outdated protocol version!\n", - ale->auditor_url); - } - if (0 != (TALER_AUDITOR_VC_NEWER & compat)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Auditor `%s' runs more recent incompatible version. We should upgrade!\n", - ale->auditor_url); - } - return; - } - ale->is_up = true; -} - - -/** - * Recalculate our auditor list, we got /keys and it may have - * changed. - * - * @param exchange exchange for which to update the list. - */ -static void -update_auditors (struct TALER_EXCHANGE_Handle *exchange) -{ - struct TALER_EXCHANGE_Keys *kd = &exchange->key_data; - - TALER_LOG_DEBUG ("Updating auditors\n"); - for (unsigned int i = 0; i<kd->num_auditors; i++) - { - /* Compare auditor data from /keys with auditor data - * from owned exchange structures. */ - struct TALER_EXCHANGE_AuditorInformation *auditor = &kd->auditors[i]; - struct TEAH_AuditorListEntry *ale = NULL; - - for (struct TEAH_AuditorListEntry *a = exchange->auditors_head; - NULL != a; - a = a->next) - { - if (0 == GNUNET_memcmp (&auditor->auditor_pub, - &a->auditor_pub)) - { - ale = a; - break; - } - } - if (NULL != ale) - continue; /* found, no need to add */ - - /* new auditor, add */ - TALER_LOG_DEBUG ("Found new auditor %s!\n", - auditor->auditor_url); - ale = GNUNET_new (struct TEAH_AuditorListEntry); - ale->auditor_pub = auditor->auditor_pub; - ale->auditor_url = GNUNET_strdup (auditor->auditor_url); - GNUNET_CONTAINER_DLL_insert (exchange->auditors_head, - exchange->auditors_tail, - ale); - ale->ah = TALER_AUDITOR_connect (exchange->ctx, - ale->auditor_url, - &auditor_version_cb, - ale); - } -} - - -/** * Compare two denomination keys. Ignores revocation data. * * @param denom1 first denomination key * @param denom2 second denomination key * @return 0 if the two keys are equal (not necessarily - * the same object), 1 otherwise. + * the same object), non-zero otherwise. */ static unsigned int denoms_cmp (const struct TALER_EXCHANGE_DenomPublicKey *denom1, @@ -729,31 +773,21 @@ decode_keys_json (const json_t *resp_obj, struct TALER_EXCHANGE_Keys *key_data, enum TALER_EXCHANGE_VersionCompatibility *vc) { - struct TALER_ExchangeSignatureP sig; - struct GNUNET_HashContext *hash_context = NULL; - struct GNUNET_HashContext *hash_context_restricted = NULL; - bool have_age_restricted_denom = false; - struct TALER_ExchangePublicKeyP pub; - const char *currency; - struct GNUNET_JSON_Specification mspec[] = { - GNUNET_JSON_spec_fixed_auto ("eddsa_sig", - &sig), - GNUNET_JSON_spec_fixed_auto ("eddsa_pub", - &pub), - GNUNET_JSON_spec_fixed_auto ("master_public_key", - &key_data->master_pub), - GNUNET_JSON_spec_timestamp ("list_issue_date", - &key_data->list_issue_date), - GNUNET_JSON_spec_relative_time ("reserve_closing_delay", - &key_data->reserve_closing_delay), - GNUNET_JSON_spec_string ("currency", - ¤cy), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("wallet_balance_limit_without_kyc", - &key_data->wallet_balance_limit_without_kyc), - NULL), - GNUNET_JSON_spec_end () - }; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_ExchangePublicKeyP exchange_pub; + const json_t *wblwk = NULL; + const json_t *global_fees; + const json_t *sign_keys_array; + const json_t *denominations_by_group; + const json_t *auditors_array; + const json_t *recoup_array = NULL; + const json_t *manifests = NULL; + bool no_extensions = false; + bool no_signature = false; + const json_t *accounts; + const json_t *fees; + const json_t *wads; + struct SignatureContext sig_ctx = { 0 }; if (JSON_OBJECT != json_typeof (resp_obj)) { @@ -767,14 +801,10 @@ decode_keys_json (const json_t *resp_obj, #endif /* check the version first */ { - const char *ver; - unsigned int age; - unsigned int revision; - unsigned int current; - char dummy; + struct TALER_JSON_ProtocolVersion pv; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("version", - &ver), + TALER_JSON_spec_version ("version", + &pv), GNUNET_JSON_spec_end () }; @@ -786,208 +816,331 @@ decode_keys_json (const json_t *resp_obj, GNUNET_break_op (0); return GNUNET_SYSERR; } - if (3 != sscanf (ver, - "%u:%u:%u%c", - ¤t, - &revision, - &age, - &dummy)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } *vc = TALER_EXCHANGE_VC_MATCH; - if (EXCHANGE_PROTOCOL_CURRENT < current) + if (EXCHANGE_PROTOCOL_CURRENT < pv.current) { *vc |= TALER_EXCHANGE_VC_NEWER; - if (EXCHANGE_PROTOCOL_CURRENT < current - age) + if (EXCHANGE_PROTOCOL_CURRENT < pv.current - pv.age) *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; } - if (EXCHANGE_PROTOCOL_CURRENT > current) + if (EXCHANGE_PROTOCOL_CURRENT > pv.current) { *vc |= TALER_EXCHANGE_VC_OLDER; - if (EXCHANGE_PROTOCOL_CURRENT - EXCHANGE_PROTOCOL_AGE > current) + if (EXCHANGE_PROTOCOL_CURRENT - EXCHANGE_PROTOCOL_AGE > pv.current) *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; } - key_data->version = GNUNET_strdup (ver); } - EXITIF (GNUNET_OK != - GNUNET_JSON_parse (resp_obj, - (check_sig) ? mspec : &mspec[2], - NULL, NULL)); - key_data->currency = GNUNET_strdup (currency); - - if (GNUNET_OK == - TALER_amount_is_valid (&key_data->wallet_balance_limit_without_kyc)) { - if (0 != strcasecmp (currency, - key_data->wallet_balance_limit_without_kyc.currency)) + const char *ver; + const char *currency; + const char *asset_type; + struct GNUNET_JSON_Specification mspec[] = { + GNUNET_JSON_spec_fixed_auto ( + "exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ( + "exchange_pub", + &exchange_pub), + GNUNET_JSON_spec_fixed_auto ( + "master_public_key", + &key_data->master_pub), + GNUNET_JSON_spec_array_const ("accounts", + &accounts), + GNUNET_JSON_spec_object_const ("wire_fees", + &fees), + GNUNET_JSON_spec_array_const ("wads", + &wads), + GNUNET_JSON_spec_timestamp ( + "list_issue_date", + &key_data->list_issue_date), + GNUNET_JSON_spec_relative_time ( + "reserve_closing_delay", + &key_data->reserve_closing_delay), + GNUNET_JSON_spec_string ( + "currency", + ¤cy), + GNUNET_JSON_spec_string ( + "asset_type", + &asset_type), + GNUNET_JSON_spec_array_const ( + "global_fees", + &global_fees), + GNUNET_JSON_spec_array_const ( + "signkeys", + &sign_keys_array), + GNUNET_JSON_spec_array_const ( + "denominations", + &denominations_by_group), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ( + "recoup", + &recoup_array), + NULL), + GNUNET_JSON_spec_array_const ( + "auditors", + &auditors_array), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ( + "rewards_allowed", + &key_data->rewards_allowed), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("extensions", + &manifests), + &no_extensions), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ( + "extensions_sig", + &key_data->extensions_sig), + &no_signature), + GNUNET_JSON_spec_string ("version", + &ver), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ( + "wallet_balance_limit_without_kyc", + &wblwk), + NULL), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + if (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + (check_sig) ? mspec : &mspec[2], + &emsg, + &eline)) { - GNUNET_break_op (0); - return GNUNET_SYSERR; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Parsing /keys failed for `%s' (%u)\n", + emsg, + eline); + EXITIF (1); } - } + { + struct GNUNET_JSON_Specification sspec[] = { + TALER_JSON_spec_currency_specification ( + "currency_specification", + currency, + &key_data->cspec), + TALER_JSON_spec_amount ( + "stefan_abs", + currency, + &key_data->stefan_abs), + TALER_JSON_spec_amount ( + "stefan_log", + currency, + &key_data->stefan_log), + GNUNET_JSON_spec_double ( + "stefan_lin", + &key_data->stefan_lin), + GNUNET_JSON_spec_end () + }; - /* parse the master public key and issue date of the response */ - if (check_sig) - { - hash_context = GNUNET_CRYPTO_hash_context_start (); - hash_context_restricted = GNUNET_CRYPTO_hash_context_start (); + if (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + sspec, + &emsg, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Parsing /keys failed for `%s' (%u)\n", + emsg, + eline); + EXITIF (1); + } + } + + key_data->currency = GNUNET_strdup (currency); + key_data->version = GNUNET_strdup (ver); + key_data->asset_type = GNUNET_strdup (asset_type); + if (! no_extensions) + key_data->extensions = json_incref ((json_t *) manifests); } /* parse the global fees */ + EXITIF (json_array_size (global_fees) > UINT_MAX); + key_data->num_global_fees + = (unsigned int) json_array_size (global_fees); + if (0 != key_data->num_global_fees) { - json_t *global_fees; json_t *global_fee; - unsigned int index; + size_t index; - EXITIF (NULL == (global_fees = - json_object_get (resp_obj, - "global_fees"))); - EXITIF (! json_is_array (global_fees)); - if (0 != (key_data->num_global_fees = - json_array_size (global_fees))) + key_data->global_fees + = GNUNET_new_array (key_data->num_global_fees, + struct TALER_EXCHANGE_GlobalFee); + json_array_foreach (global_fees, index, global_fee) { - key_data->global_fees - = GNUNET_new_array (key_data->num_global_fees, - struct TALER_EXCHANGE_GlobalFee); - json_array_foreach (global_fees, index, global_fee) { - EXITIF (GNUNET_SYSERR == - parse_global_fee (&key_data->global_fees[index], - check_sig, - global_fee, - key_data)); - } + EXITIF (GNUNET_SYSERR == + parse_global_fee (&key_data->global_fees[index], + check_sig, + global_fee, + key_data)); } } /* parse the signing keys */ + EXITIF (json_array_size (sign_keys_array) > UINT_MAX); + key_data->num_sign_keys + = (unsigned int) json_array_size (sign_keys_array); + if (0 != key_data->num_sign_keys) { - json_t *sign_keys_array; json_t *sign_key_obj; - unsigned int index; + size_t index; - EXITIF (NULL == (sign_keys_array = - json_object_get (resp_obj, - "signkeys"))); - EXITIF (! json_is_array (sign_keys_array)); - if (0 != (key_data->num_sign_keys = - json_array_size (sign_keys_array))) + key_data->sign_keys + = GNUNET_new_array (key_data->num_sign_keys, + struct TALER_EXCHANGE_SigningPublicKey); + json_array_foreach (sign_keys_array, index, sign_key_obj) { + EXITIF (GNUNET_SYSERR == + parse_json_signkey (&key_data->sign_keys[index], + check_sig, + sign_key_obj, + &key_data->master_pub)); + } + } + + /* Parse balance limits */ + if (NULL != wblwk) + { + EXITIF (json_array_size (wblwk) > UINT_MAX); + key_data->wblwk_length + = (unsigned int) json_array_size (wblwk); + key_data->wallet_balance_limit_without_kyc + = GNUNET_new_array (key_data->wblwk_length, + struct TALER_Amount); + for (unsigned int i = 0; i<key_data->wblwk_length; i++) { - key_data->sign_keys - = GNUNET_new_array (key_data->num_sign_keys, - struct TALER_EXCHANGE_SigningPublicKey); - json_array_foreach (sign_keys_array, index, sign_key_obj) { - EXITIF (GNUNET_SYSERR == - parse_json_signkey (&key_data->sign_keys[index], - check_sig, - sign_key_obj, - &key_data->master_pub)); - } + struct TALER_Amount *a = &key_data->wallet_balance_limit_without_kyc[i]; + const json_t *aj = json_array_get (wblwk, + i); + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount (NULL, + key_data->currency, + a), + GNUNET_JSON_spec_end () + }; + + EXITIF (GNUNET_OK != + GNUNET_JSON_parse (aj, + spec, + NULL, NULL)); } } + /* Parse wire accounts */ + key_data->fees = parse_fees (&key_data->master_pub, + key_data->currency, + fees, + &key_data->fees_len); + EXITIF (NULL == key_data->fees); + /* parse accounts */ + EXITIF (json_array_size (accounts) > UINT_MAX); + GNUNET_array_grow (key_data->accounts, + key_data->accounts_len, + json_array_size (accounts)); + EXITIF (GNUNET_OK != + TALER_EXCHANGE_parse_accounts (&key_data->master_pub, + accounts, + key_data->accounts_len, + key_data->accounts)); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Parsed %u wire accounts from JSON\n", + key_data->accounts_len); + + /* Parse the supported extension(s): age-restriction. */ /* TODO: maybe lift all this into a FP in TALER_Extension ? */ + if (! no_extensions) { - struct TALER_MasterSignatureP extensions_sig = {0}; - json_t *extensions = NULL; - struct GNUNET_JSON_Specification ext_spec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("extensions", - &extensions), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ( - "extensions_sig", - &extensions_sig), - NULL), - GNUNET_JSON_spec_end () - }; - - /* 1. Search for extensions in the response to /keys */ - EXITIF (GNUNET_OK != - GNUNET_JSON_parse (resp_obj, - ext_spec, - NULL, NULL)); - - if (NULL != extensions) + if (no_signature) { - /* 2. We have an extensions object. Verify its signature. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "found extensions without signature\n"); + } + else + { + /* We have an extensions object. Verify its signature. */ EXITIF (GNUNET_OK != - TALER_extensions_verify_json_config_signature ( - extensions, - &extensions_sig, + TALER_extensions_verify_manifests_signature ( + manifests, + &key_data->extensions_sig, &key_data->master_pub)); - /* 3. Parse and set the the configuration of the extensions accordingly */ + /* Parse and set the the configuration of the extensions accordingly */ EXITIF (GNUNET_OK != - TALER_extensions_load_json_config (extensions)); + TALER_extensions_load_manifests (manifests)); } - /* 4. assuming we might have now a new value for age_mask, set it in key_data */ - key_data->age_mask = TALER_extensions_age_restriction_ageMask (); + /* Assuming we might have now a new value for age_mask, set it in key_data */ + key_data->age_mask = TALER_extensions_get_age_restriction_mask (); } - /* parse the denomination keys, merging with the - possibly EXISTING array as required (/keys cherry picking) */ + /* + * Parse the denomination keys, merging with the + * possibly EXISTING array as required (/keys cherry picking). + * + * The denominations are grouped by common values of + * {cipher, value, fee, age_mask}. + */ { - /* The denominations can be in "denoms" and (optionally) in - * "age_restricted_denoms" - */ - struct - { - char *name; - struct GNUNET_HashContext *hc; - bool is_optional_age_restriction; - } - hive[2] = { - { - "denoms", - hash_context, - false - }, - { - "age_restricted_denoms", - hash_context_restricted, - true - } - }; + json_t *group_obj; + unsigned int group_idx; - for (size_t s = 0; s < sizeof(hive) / sizeof(hive[0]); s++) + json_array_foreach (denominations_by_group, + group_idx, + group_obj) { - json_t *denom_keys_array; + /* First, parse { cipher, fees, value, age_mask, hash } of the current + group. */ + struct TALER_DenominationGroup group = {0}; + const json_t *denom_keys_array; + struct GNUNET_JSON_Specification group_spec[] = { + TALER_JSON_spec_denomination_group (NULL, + key_data->currency, + &group), + GNUNET_JSON_spec_array_const ("denoms", + &denom_keys_array), + GNUNET_JSON_spec_end () + }; json_t *denom_key_obj; unsigned int index; - denom_keys_array = json_object_get (resp_obj, - hive[s].name); - - if (NULL == denom_keys_array) - continue; - - EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); - - json_array_foreach (denom_keys_array, index, denom_key_obj) { - struct TALER_EXCHANGE_DenomPublicKey dk; + EXITIF (GNUNET_SYSERR == + GNUNET_JSON_parse (group_obj, + group_spec, + NULL, + NULL)); + + /* Now, parse the individual denominations */ + json_array_foreach (denom_keys_array, + index, + denom_key_obj) + { + /* Set the common fields from the group for this particular + denomination. Required to make the validity check inside + parse_json_denomkey_partially pass */ + struct TALER_EXCHANGE_DenomPublicKey dk = { + .value = group.value, + .fees = group.fees, + .key.age_mask = group.age_mask + }; bool found = false; - /* mark that we have at least one age restricted denomination, needed - * for the hash calculation and signature verification below. */ - have_age_restricted_denom |= hive[s].is_optional_age_restriction; - - memset (&dk, - 0, - sizeof (dk)); EXITIF (GNUNET_SYSERR == - parse_json_denomkey (key_data->currency, - &dk, - check_sig, - denom_key_obj, - &key_data->master_pub, - hive[s].hc)); - + parse_json_denomkey_partially (&dk, + group.cipher, + check_sig, + denom_key_obj, + &key_data->master_pub, + group_idx, + index, + check_sig + ? &sig_ctx + : NULL)); for (unsigned int j = 0; j<key_data->num_denom_keys; j++) @@ -999,6 +1152,7 @@ decode_keys_json (const json_t *resp_obj, break; } } + if (found) { /* 0:0:0 did not support /keys cherry picking */ @@ -1006,10 +1160,14 @@ decode_keys_json (const json_t *resp_obj, TALER_denom_pub_free (&dk.key); continue; } + if (key_data->denom_keys_size == key_data->num_denom_keys) GNUNET_array_grow (key_data->denom_keys, key_data->denom_keys_size, key_data->denom_keys_size * 2 + 2); + GNUNET_assert (key_data->denom_keys_size > + key_data->num_denom_keys); + GNUNET_assert (key_data->num_denom_keys < UINT_MAX); key_data->denom_keys[key_data->num_denom_keys++] = dk; /* Update "last_denom_issue_date" */ @@ -1018,23 +1176,18 @@ decode_keys_json (const json_t *resp_obj, key_data->last_denom_issue_date = GNUNET_TIME_timestamp_max (key_data->last_denom_issue_date, dk.valid_from); - }; - } - } + }; /* end of json_array_foreach over denominations */ + } /* end of json_array_foreach over groups of denominations */ + } /* end of scope for group_ojb/group_idx */ /* parse the auditor information */ { - json_t *auditors_array; json_t *auditor_info; unsigned int index; - EXITIF (NULL == (auditors_array = - json_object_get (resp_obj, - "auditors"))); - EXITIF (JSON_ARRAY != json_typeof (auditors_array)); - /* Merge with the existing auditor information we have (/keys cherry picking) */ - json_array_foreach (auditors_array, index, auditor_info) { + json_array_foreach (auditors_array, index, auditor_info) + { struct TALER_EXCHANGE_AuditorInformation ai; bool found = false; @@ -1093,181 +1246,94 @@ decode_keys_json (const json_t *resp_obj, GNUNET_array_grow (key_data->auditors, key_data->auditors_size, key_data->auditors_size * 2 + 2); + GNUNET_assert (key_data->auditors_size > + key_data->num_auditors); GNUNET_assert (NULL != ai.auditor_url); + GNUNET_assert (key_data->num_auditors < UINT_MAX); key_data->auditors[key_data->num_auditors++] = ai; }; } /* parse the revocation/recoup information */ + if (NULL != recoup_array) { - json_t *recoup_array; json_t *recoup_info; unsigned int index; - if (NULL != (recoup_array = - json_object_get (resp_obj, - "recoup"))) + json_array_foreach (recoup_array, index, recoup_info) { - EXITIF (JSON_ARRAY != json_typeof (recoup_array)); - - json_array_foreach (recoup_array, index, recoup_info) { - struct TALER_DenominationHashP h_denom_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("h_denom_pub", - &h_denom_pub), - GNUNET_JSON_spec_end () - }; + struct TALER_DenominationHashP h_denom_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("h_denom_pub", + &h_denom_pub), + GNUNET_JSON_spec_end () + }; - EXITIF (GNUNET_OK != - GNUNET_JSON_parse (recoup_info, - spec, - NULL, NULL)); - for (unsigned int j = 0; - j<key_data->num_denom_keys; - j++) + EXITIF (GNUNET_OK != + GNUNET_JSON_parse (recoup_info, + spec, + NULL, NULL)); + for (unsigned int j = 0; + j<key_data->num_denom_keys; + j++) + { + if (0 == GNUNET_memcmp (&h_denom_pub, + &key_data->denom_keys[j].h_key)) { - if (0 == GNUNET_memcmp (&h_denom_pub, - &key_data->denom_keys[j].h_key)) - { - key_data->denom_keys[j].revoked = GNUNET_YES; - break; - } + key_data->denom_keys[j].revoked = true; + break; } - }; + } } } if (check_sig) { + struct GNUNET_HashContext *hash_context; struct GNUNET_HashCode hc; - /* If we had any age restricted denominations, add their hash to the end of - * the normal denominations. */ - if (have_age_restricted_denom) + hash_context = GNUNET_CRYPTO_hash_context_start (); + qsort (sig_ctx.elements, + sig_ctx.elements_pos, + sizeof (struct SignatureElement), + &signature_context_sort_cb); + for (unsigned int i = 0; i<sig_ctx.elements_pos; i++) { - struct GNUNET_HashCode hcr; + struct SignatureElement *element = &sig_ctx.elements[i]; - GNUNET_CRYPTO_hash_context_finish (hash_context_restricted, - &hcr); - hash_context_restricted = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Adding %u,%u,%s\n", + element->group_offset, + element->offset, + TALER_B2S (&element->master_sig)); GNUNET_CRYPTO_hash_context_read (hash_context, - &hcr, - sizeof(struct GNUNET_HashCode)); + &element->master_sig, + sizeof (element->master_sig)); } - else - { - GNUNET_CRYPTO_hash_context_abort (hash_context_restricted); - hash_context_restricted = NULL; - } - + GNUNET_array_grow (sig_ctx.elements, + sig_ctx.elements_size, + 0); GNUNET_CRYPTO_hash_context_finish (hash_context, &hc); - hash_context = NULL; EXITIF (GNUNET_OK != TALER_EXCHANGE_test_signing_key (key_data, - &pub)); - + &exchange_pub)); EXITIF (GNUNET_OK != TALER_exchange_online_key_set_verify ( key_data->list_issue_date, &hc, - &pub, - &sig)); + &exchange_pub, + &exchange_sig)); } return GNUNET_OK; -EXITIF_exit: +EXITIF_exit: *vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR; - if (NULL != hash_context) - GNUNET_CRYPTO_hash_context_abort (hash_context); - if (NULL != hash_context_restricted) - GNUNET_CRYPTO_hash_context_abort (hash_context_restricted); return GNUNET_SYSERR; } /** - * Free key data object. - * - * @param key_data data to free (pointer itself excluded) - */ -static void -free_key_data (struct TALER_EXCHANGE_Keys *key_data) -{ - GNUNET_array_grow (key_data->sign_keys, - key_data->num_sign_keys, - 0); - for (unsigned int i = 0; i<key_data->num_denom_keys; i++) - TALER_denom_pub_free (&key_data->denom_keys[i].key); - - GNUNET_array_grow (key_data->denom_keys, - key_data->denom_keys_size, - 0); - for (unsigned int i = 0; i<key_data->num_auditors; i++) - { - GNUNET_array_grow (key_data->auditors[i].denom_keys, - key_data->auditors[i].num_denom_keys, - 0); - GNUNET_free (key_data->auditors[i].auditor_url); - } - GNUNET_array_grow (key_data->auditors, - key_data->auditors_size, - 0); - GNUNET_free (key_data->version); - GNUNET_free (key_data->currency); - GNUNET_free (key_data->global_fees); -} - - -/** - * Initiate download of /keys from the exchange. - * - * @param cls exchange where to download /keys from - */ -static void -request_keys (void *cls); - - -void -TALER_EXCHANGE_set_last_denom (struct TALER_EXCHANGE_Handle *exchange, - struct GNUNET_TIME_Timestamp last_denom_new) -{ - TALER_LOG_DEBUG ( - "Application explicitly set last denomination validity to %s\n", - GNUNET_TIME_timestamp2s (last_denom_new)); - exchange->key_data.last_denom_issue_date = last_denom_new; -} - - -struct GNUNET_TIME_Timestamp -TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange, - enum TALER_EXCHANGE_CheckKeysFlags flags) -{ - bool force_download = 0 != (flags & TALER_EXCHANGE_CKF_FORCE_DOWNLOAD); - bool pull_all_keys = 0 != (flags & TALER_EXCHANGE_CKF_PULL_ALL_KEYS); - - if (NULL != exchange->kr) - return GNUNET_TIME_UNIT_ZERO_TS; - - if (pull_all_keys) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Forcing re-download of all exchange keys\n"); - GNUNET_break (GNUNET_YES == force_download); - exchange->state = MHS_INIT; - } - if ( (! force_download) && - (GNUNET_TIME_absolute_is_future ( - exchange->key_data_expiration.abs_time)) ) - return exchange->key_data_expiration; - if (NULL == exchange->retry_task) - exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, - exchange); - return GNUNET_TIME_UNIT_ZERO_TS; -} - - -/** * Callback used when downloading the reply to a /keys request * is complete. * @@ -1280,132 +1346,122 @@ keys_completed_cb (void *cls, long response_code, const void *resp_obj) { - struct KeysRequest *kr = cls; - struct TALER_EXCHANGE_Handle *exchange = kr->exchange; - struct TALER_EXCHANGE_Keys kd; - struct TALER_EXCHANGE_Keys kd_old; - enum TALER_EXCHANGE_VersionCompatibility vc; + struct TALER_EXCHANGE_GetKeysHandle *gkh = cls; const json_t *j = resp_obj; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = j, - .http_status = (unsigned int) response_code + struct TALER_EXCHANGE_Keys *kd = NULL; + struct TALER_EXCHANGE_KeysResponse kresp = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code, + .details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR, }; + gkh->job = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received keys from URL `%s' with status %ld and expiration %s.\n", - kr->url, + gkh->url, response_code, - GNUNET_TIME_timestamp2s (kr->expire)); - if (GNUNET_TIME_absolute_is_past (kr->expire.abs_time)) + GNUNET_TIME_timestamp2s (gkh->expire)); + if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time)) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Exchange failed to give expiration time, assuming in %s\n", - GNUNET_TIME_relative2s (DEFAULT_EXPIRATION, - true)); - kr->expire + if (MHD_HTTP_OK == response_code) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Exchange failed to give expiration time, assuming in %s\n", + GNUNET_TIME_relative2s (DEFAULT_EXPIRATION, + true)); + gkh->expire = GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION)); } - kd_old = exchange->key_data; - memset (&kd, - 0, - sizeof (struct TALER_EXCHANGE_Keys)); - vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR; switch (response_code) { case 0: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to receive /keys response from exchange %s\n", - exchange->url); - free_keys_request (kr); - exchange->keys_error_count++; - exchange->kr = NULL; - GNUNET_assert (NULL == exchange->retry_task); - exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay); - exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, - &request_keys, - exchange); - return; + gkh->exchange_url); + break; case MHD_HTTP_OK: - exchange->keys_error_count = 0; if (NULL == j) { + GNUNET_break (0); response_code = 0; break; } - /* We keep the denomination keys and auditor signatures from the - previous iteration (/keys cherry picking) */ - kd.num_denom_keys = kd_old.num_denom_keys; - kd.last_denom_issue_date = kd_old.last_denom_issue_date; - GNUNET_array_grow (kd.denom_keys, - kd.denom_keys_size, - kd.num_denom_keys); - - /* First make a shallow copy, we then need another pass for the RSA key... */ - memcpy (kd.denom_keys, - kd_old.denom_keys, - kd_old.num_denom_keys * sizeof (struct - TALER_EXCHANGE_DenomPublicKey)); - - for (unsigned int i = 0; i<kd_old.num_denom_keys; i++) - TALER_denom_pub_deep_copy (&kd.denom_keys[i].key, - &kd_old.denom_keys[i].key); - - kd.num_auditors = kd_old.num_auditors; - kd.auditors = GNUNET_new_array (kd.num_auditors, - struct TALER_EXCHANGE_AuditorInformation); - /* Now the necessary deep copy... */ - for (unsigned int i = 0; i<kd_old.num_auditors; i++) + kd = GNUNET_new (struct TALER_EXCHANGE_Keys); + kd->exchange_url = GNUNET_strdup (gkh->exchange_url); + if (NULL != gkh->prev_keys) { - const struct TALER_EXCHANGE_AuditorInformation *aold = - &kd_old.auditors[i]; - struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i]; - - anew->auditor_pub = aold->auditor_pub; - GNUNET_assert (NULL != aold->auditor_url); - anew->auditor_url = GNUNET_strdup (aold->auditor_url); - GNUNET_array_grow (anew->denom_keys, - anew->num_denom_keys, - aold->num_denom_keys); - memcpy (anew->denom_keys, - aold->denom_keys, - aold->num_denom_keys - * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); - } + const struct TALER_EXCHANGE_Keys *kd_old = gkh->prev_keys; + + /* We keep the denomination keys and auditor signatures from the + previous iteration (/keys cherry picking) */ + kd->num_denom_keys + = kd_old->num_denom_keys; + kd->last_denom_issue_date + = kd_old->last_denom_issue_date; + GNUNET_array_grow (kd->denom_keys, + kd->denom_keys_size, + kd->num_denom_keys); + /* First make a shallow copy, we then need another pass for the RSA key... */ + GNUNET_memcpy (kd->denom_keys, + kd_old->denom_keys, + kd_old->num_denom_keys + * sizeof (struct TALER_EXCHANGE_DenomPublicKey)); + for (unsigned int i = 0; i<kd_old->num_denom_keys; i++) + TALER_denom_pub_copy (&kd->denom_keys[i].key, + &kd_old->denom_keys[i].key); + kd->num_auditors = kd_old->num_auditors; + kd->auditors = GNUNET_new_array (kd->num_auditors, + struct TALER_EXCHANGE_AuditorInformation); + /* Now the necessary deep copy... */ + for (unsigned int i = 0; i<kd_old->num_auditors; i++) + { + const struct TALER_EXCHANGE_AuditorInformation *aold = + &kd_old->auditors[i]; + struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i]; - /* Old auditors got just copied into new ones. */ + anew->auditor_pub = aold->auditor_pub; + anew->auditor_url = GNUNET_strdup (aold->auditor_url); + GNUNET_array_grow (anew->denom_keys, + anew->num_denom_keys, + aold->num_denom_keys); + GNUNET_memcpy ( + anew->denom_keys, + aold->denom_keys, + aold->num_denom_keys + * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); + } + } + /* Now decode fresh /keys response */ if (GNUNET_OK != decode_keys_json (j, true, - &kd, - &vc)) + kd, + &kresp.details.ok.compat)) { TALER_LOG_ERROR ("Could not decode /keys response\n"); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - for (unsigned int i = 0; i<kd.num_auditors; i++) - { - struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i]; - - GNUNET_array_grow (anew->denom_keys, - anew->num_denom_keys, - 0); - GNUNET_free (anew->auditor_url); - } - GNUNET_free (kd.auditors); - kd.auditors = NULL; - kd.num_auditors = 0; - for (unsigned int i = 0; i<kd_old.num_denom_keys; i++) - TALER_denom_pub_free (&kd.denom_keys[i].key); - GNUNET_array_grow (kd.denom_keys, - kd.denom_keys_size, - 0); - kd.num_denom_keys = 0; + kd->rc = 1; + TALER_EXCHANGE_keys_decref (kd); + kd = NULL; + kresp.hr.http_status = 0; + kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - json_decref (exchange->key_data_raw); - exchange->key_data_raw = json_deep_copy (j); - exchange->retry_delay = GNUNET_TIME_UNIT_ZERO; + kd->rc = 1; + kd->key_data_expiration = gkh->expire; + if (GNUNET_TIME_relative_cmp ( + GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time), + <, + MINIMUM_EXPIRATION)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Exchange returned keys with expiration time below %s. Compensating.\n", + GNUNET_TIME_relative2s (MINIMUM_EXPIRATION, + true)); + kd->key_data_expiration + = GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION); + } + + kresp.details.ok.keys = kd; break; case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_UNAUTHORIZED: @@ -1413,108 +1469,36 @@ keys_completed_cb (void *cls, case MHD_HTTP_NOT_FOUND: if (NULL == j) { - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.hint = TALER_ErrorCode_get_hint (hr.ec); + kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec); } else { - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + kresp.hr.ec = TALER_JSON_get_error_code (j); + kresp.hr.hint = TALER_JSON_get_error_hint (j); } break; default: - if (MHD_HTTP_GATEWAY_TIMEOUT == response_code) - exchange->keys_error_count++; if (NULL == j) { - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.hint = TALER_ErrorCode_get_hint (hr.ec); + kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec); } else { - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + kresp.hr.ec = TALER_JSON_get_error_code (j); + kresp.hr.hint = TALER_JSON_get_error_hint (j); } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, - (int) hr.ec); + (int) kresp.hr.ec); break; } - exchange->key_data = kd; - if (GNUNET_TIME_absolute_is_past ( - exchange->key_data.last_denom_issue_date.abs_time)) - TALER_LOG_WARNING ("Last DK issue date from exchange is in the past: %s\n", - GNUNET_TIME_timestamp2s ( - exchange->key_data.last_denom_issue_date)); - else - TALER_LOG_DEBUG ("Last DK issue date updated to: %s\n", - GNUNET_TIME_timestamp2s ( - exchange->key_data.last_denom_issue_date)); - - - if (MHD_HTTP_OK != response_code) - { - exchange->kr = NULL; - free_keys_request (kr); - exchange->state = MHS_FAILED; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Exchange keys download failed\n"); - if (NULL != exchange->key_data_raw) - { - json_decref (exchange->key_data_raw); - exchange->key_data_raw = NULL; - } - free_key_data (&kd_old); - /* notify application that we failed */ - exchange->cert_cb (exchange->cert_cb_cls, - &hr, - NULL, - vc); - return; - } - - exchange->kr = NULL; - exchange->key_data_expiration = kr->expire; - free_keys_request (kr); - exchange->state = MHS_CERT; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Successfully downloaded exchange's keys\n"); - update_auditors (exchange); - /* notify application about the key information */ - exchange->cert_cb (exchange->cert_cb_cls, - &hr, - &exchange->key_data, - vc); - free_key_data (&kd_old); -} - - -/* ********************* library internal API ********* */ - - -struct GNUNET_CURL_Context * -TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h) -{ - return h->ctx; -} - - -enum GNUNET_GenericReturnValue -TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h) -{ - return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO; -} - - -char * -TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h, - const char *path) -{ - GNUNET_assert ('/' == path[0]); - return TALER_url_join (h->url, - path + 1, - NULL); + gkh->cert_cb (gkh->cert_cb_cls, + &kresp, + kd); + TALER_EXCHANGE_get_keys_cancel (gkh); } @@ -1528,7 +1512,7 @@ TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h, * Parse HTTP timestamp. * * @param dateline header to parse header - * @param at where to write the result + * @param[out] at where to write the result * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue @@ -1628,7 +1612,7 @@ parse_date_string (const char *dateline, * @param buffer header data received * @param size size of an item in @a buffer * @param nitems number of items in @a buffer - * @param userdata the `struct KeysRequest` + * @param userdata the `struct TALER_EXCHANGE_GetKeysHandle` * @return `size * nitems` on success (everything else aborts) */ static size_t @@ -1637,7 +1621,7 @@ header_cb (char *buffer, size_t nitems, void *userdata) { - struct KeysRequest *kr = userdata; + struct TALER_EXCHANGE_GetKeysHandle *kr = userdata; size_t total = size * nitems; char *val; @@ -1649,6 +1633,10 @@ header_cb (char *buffer, return total; val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")], total - strlen (MHD_HTTP_HEADER_EXPIRES ": ")); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Found %s header `%s'\n", + MHD_HTTP_HEADER_EXPIRES, + val); if (GNUNET_OK != parse_date_string (val, &kr->expire)) @@ -1664,409 +1652,66 @@ header_cb (char *buffer, } -/* ********************* public API ******************* */ - - -/** - * Deserialize the key data and use it to bootstrap @a exchange to - * more efficiently recover the state. Errors in @a data must be - * tolerated (i.e. by re-downloading instead). - * - * @param exchange which exchange's key and wire data should be deserialized - * @param data the data to deserialize - */ -static void -deserialize_data (struct TALER_EXCHANGE_Handle *exchange, - const json_t *data) -{ - enum TALER_EXCHANGE_VersionCompatibility vc; - json_t *keys; - const char *url; - uint32_t version; - struct GNUNET_TIME_Timestamp expire; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint32 ("version", - &version), - GNUNET_JSON_spec_json ("keys", - &keys), - GNUNET_JSON_spec_string ("exchange_url", - &url), - GNUNET_JSON_spec_timestamp ("expire", - &expire), - GNUNET_JSON_spec_end () - }; - struct TALER_EXCHANGE_Keys key_data; - struct TALER_EXCHANGE_HttpResponse hr = { - .ec = TALER_EC_NONE, - .http_status = MHD_HTTP_OK, - .reply = data - }; - - if (NULL == data) - return; - if (GNUNET_OK != - GNUNET_JSON_parse (data, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return; - } - if (0 != version) - { - GNUNET_JSON_parse_free (spec); - return; /* unsupported version */ - } - if (0 != strcmp (url, - exchange->url)) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return; - } - memset (&key_data, - 0, - sizeof (struct TALER_EXCHANGE_Keys)); - if (GNUNET_OK != - decode_keys_json (keys, - false, - &key_data, - &vc)) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return; - } - /* decode successful, initialize with the result */ - GNUNET_assert (NULL == exchange->key_data_raw); - exchange->key_data_raw = json_deep_copy (keys); - exchange->key_data = key_data; - exchange->key_data_expiration = expire; - exchange->state = MHS_CERT; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Successfully loaded exchange's keys via deserialization\n"); - update_auditors (exchange); - /* notify application about the key information */ - exchange->cert_cb (exchange->cert_cb_cls, - &hr, - &exchange->key_data, - vc); - GNUNET_JSON_parse_free (spec); -} - - -json_t * -TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange) -{ - const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data; - struct GNUNET_TIME_Timestamp now; - json_t *keys; - json_t *signkeys; - json_t *denoms; - json_t *auditors; - - now = GNUNET_TIME_timestamp_get (); - signkeys = json_array (); - if (NULL == signkeys) - { - GNUNET_break (0); - return NULL; - } - for (unsigned int i = 0; i<kd->num_sign_keys; i++) - { - const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i]; - json_t *signkey; - - if (GNUNET_TIME_timestamp_cmp (now, - >, - sk->valid_until)) - continue; /* skip keys that have expired */ - signkey = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("key", - &sk->key), - GNUNET_JSON_pack_data_auto ("master_sig", - &sk->master_sig), - GNUNET_JSON_pack_timestamp ("stamp_start", - sk->valid_from), - GNUNET_JSON_pack_timestamp ("stamp_expire", - sk->valid_until), - GNUNET_JSON_pack_timestamp ("stamp_end", - sk->valid_legal)); - if (NULL == signkey) - { - GNUNET_break (0); - continue; - } - if (0 != json_array_append_new (signkeys, - signkey)) - { - GNUNET_break (0); - json_decref (signkey); - json_decref (signkeys); - return NULL; - } - } - denoms = json_array (); - if (NULL == denoms) - { - GNUNET_break (0); - json_decref (signkeys); - return NULL; - } - for (unsigned int i = 0; i<kd->num_denom_keys; i++) - { - const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i]; - json_t *denom; - - if (GNUNET_TIME_timestamp_cmp (now, - >, - dk->expire_deposit)) - continue; /* skip keys that have expired */ - denom = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_timestamp ("stamp_expire_deposit", - dk->expire_deposit), - GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw", - dk->withdraw_valid_until), - GNUNET_JSON_pack_timestamp ("stamp_start", - dk->valid_from), - GNUNET_JSON_pack_timestamp ("stamp_expire_legal", - dk->expire_legal), - TALER_JSON_pack_amount ("value", - &dk->value), - TALER_JSON_PACK_DENOM_FEES ("fee", - &dk->fees), - GNUNET_JSON_pack_data_auto ("master_sig", - &dk->master_sig), - TALER_JSON_pack_denom_pub ("denom_pub", - &dk->key)); - GNUNET_assert (0 == - json_array_append_new (denoms, - denom)); - } - auditors = json_array (); - GNUNET_assert (NULL != auditors); - for (unsigned int i = 0; i<kd->num_auditors; i++) - { - const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i]; - json_t *a; - json_t *adenoms; - - adenoms = json_array (); - if (NULL == adenoms) - { - GNUNET_break (0); - json_decref (denoms); - json_decref (signkeys); - json_decref (auditors); - return NULL; - } - for (unsigned int j = 0; j<ai->num_denom_keys; j++) - { - const struct TALER_EXCHANGE_AuditorDenominationInfo *adi = - &ai->denom_keys[j]; - const struct TALER_EXCHANGE_DenomPublicKey *dk = - &kd->denom_keys[adi->denom_key_offset]; - json_t *k; - - if (GNUNET_TIME_timestamp_cmp (now, - >, - dk->expire_deposit)) - continue; /* skip auditor signatures for denomination keys that have expired */ - GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys); - k = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("denom_pub_h", - &dk->h_key), - GNUNET_JSON_pack_data_auto ("auditor_sig", - &adi->auditor_sig)); - GNUNET_assert (0 == - json_array_append_new (adenoms, - k)); - } - - a = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("auditor_pub", - &ai->auditor_pub), - GNUNET_JSON_pack_string ("auditor_url", - ai->auditor_url), - GNUNET_JSON_pack_array_steal ("denomination_keys", - adenoms)); - GNUNET_assert (0 == - json_array_append_new (auditors, - a)); - } - keys = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("version", - kd->version), - GNUNET_JSON_pack_string ("currency", - kd->currency), - GNUNET_JSON_pack_data_auto ("master_public_key", - &kd->master_pub), - GNUNET_JSON_pack_time_rel ("reserve_closing_delay", - kd->reserve_closing_delay), - GNUNET_JSON_pack_timestamp ("list_issue_date", - kd->list_issue_date), - GNUNET_JSON_pack_array_steal ("signkeys", - signkeys), - GNUNET_JSON_pack_array_steal ("denoms", - denoms), - GNUNET_JSON_pack_array_steal ("auditors", - auditors)); - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("version", - EXCHANGE_SERIALIZATION_FORMAT_VERSION), - GNUNET_JSON_pack_timestamp ("expire", - exchange->key_data_expiration), - GNUNET_JSON_pack_string ("exchange_url", - exchange->url), - GNUNET_JSON_pack_object_steal ("keys", - keys)); -} - - -struct TALER_EXCHANGE_Handle * -TALER_EXCHANGE_connect ( +struct TALER_EXCHANGE_GetKeysHandle * +TALER_EXCHANGE_get_keys ( struct GNUNET_CURL_Context *ctx, const char *url, - TALER_EXCHANGE_CertificationCallback cert_cb, - void *cert_cb_cls, - ...) + struct TALER_EXCHANGE_Keys *last_keys, + TALER_EXCHANGE_GetKeysCallback cert_cb, + void *cert_cb_cls) { - struct TALER_EXCHANGE_Handle *exchange; - va_list ap; - enum TALER_EXCHANGE_Option opt; + struct TALER_EXCHANGE_GetKeysHandle *gkh; + CURL *eh; + char last_date[80] = { 0 }; TALER_LOG_DEBUG ("Connecting to the exchange (%s)\n", url); - /* Disable 100 continue processing */ - GNUNET_break (GNUNET_OK == - GNUNET_CURL_append_header (ctx, - MHD_HTTP_HEADER_EXPECT ":")); - exchange = GNUNET_new (struct TALER_EXCHANGE_Handle); - exchange->ctx = ctx; - exchange->url = GNUNET_strdup (url); - exchange->cert_cb = cert_cb; - exchange->cert_cb_cls = cert_cb_cls; - exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, - exchange); - va_start (ap, cert_cb_cls); - while (TALER_EXCHANGE_OPTION_END != - (opt = va_arg (ap, int))) - { - switch (opt) - { - case TALER_EXCHANGE_OPTION_END: - GNUNET_assert (0); - break; - case TALER_EXCHANGE_OPTION_DATA: - { - const json_t *data = va_arg (ap, const json_t *); - - deserialize_data (exchange, - data); - break; - } - default: - GNUNET_assert (0); - break; - } - } - va_end (ap); - return exchange; -} - - -/** - * Compute the network timeout for the next request to /keys. - * - * @param exchange the exchange handle - * @returns the timeout in seconds (for use by CURL) - */ -static long -get_keys_timeout_seconds (struct TALER_EXCHANGE_Handle *exchange) -{ - unsigned int kec; - - /* if retry counter >= 8, do not bother to go further, we - stop the exponential back-off at 128 anyway. */ - kec = GNUNET_MIN (7, - exchange->keys_error_count); - return GNUNET_MIN (120, - 5 + (1L << kec)); -} - - -/** - * Initiate download of /keys from the exchange. - * - * @param cls exchange where to download /keys from - */ -static void -request_keys (void *cls) -{ - struct TALER_EXCHANGE_Handle *exchange = cls; - struct KeysRequest *kr; - CURL *eh; - char url[200] = "/keys?"; - - exchange->retry_task = NULL; - GNUNET_assert (NULL == exchange->kr); - kr = GNUNET_new (struct KeysRequest); - kr->exchange = exchange; - - if (GNUNET_YES == TEAH_handle_is_ready (exchange)) + gkh = GNUNET_new (struct TALER_EXCHANGE_GetKeysHandle); + gkh->exchange_url = GNUNET_strdup (url); + gkh->cert_cb = cert_cb; + gkh->cert_cb_cls = cert_cb_cls; + if (NULL != last_keys) { + gkh->prev_keys = TALER_EXCHANGE_keys_incref (last_keys); TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n", GNUNET_TIME_timestamp2s ( - exchange->key_data.last_denom_issue_date)); - sprintf (&url[strlen (url)], - "last_issue_date=%llu&", - (unsigned long long) - exchange->key_data.last_denom_issue_date.abs_time.abs_value_us - / 1000000LLU); + last_keys->last_denom_issue_date)); + GNUNET_snprintf (last_date, + sizeof (last_date), + "%llu", + (unsigned long long) + last_keys->last_denom_issue_date.abs_time.abs_value_us + / 1000000LLU); } - - /* Clean the last '&'/'?' sign that we optimistically put. */ - url[strlen (url) - 1] = '\0'; - kr->url = TEAH_path_to_url (exchange, - url); - if (NULL == kr->url) - { - struct TALER_EXCHANGE_HttpResponse hr = { - .ec = TALER_EC_GENERIC_CONFIGURATION_INVALID - }; - - GNUNET_free (kr); - exchange->keys_error_count++; - exchange->state = MHS_FAILED; - exchange->cert_cb (exchange->cert_cb_cls, - &hr, - NULL, - TALER_EXCHANGE_VC_PROTOCOL_ERROR); - return; - } - + gkh->url = TALER_url_join (url, + "keys", + (NULL != last_keys) + ? "last_issue_date" + : NULL, + (NULL != last_keys) + ? last_date + : NULL, + NULL); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Requesting keys with URL `%s'.\n", - kr->url); - eh = TALER_EXCHANGE_curl_easy_get_ (kr->url); + gkh->url); + eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url); if (NULL == eh) { - GNUNET_free (kr->url); - GNUNET_free (kr); - exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay); - exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, - &request_keys, - exchange); - return; + GNUNET_break (0); + GNUNET_free (gkh->exchange_url); + GNUNET_free (gkh->url); + GNUNET_free (gkh); + return NULL; } GNUNET_break (CURLE_OK == curl_easy_setopt (eh, CURLOPT_VERBOSE, - 0)); + 1)); GNUNET_break (CURLE_OK == curl_easy_setopt (eh, CURLOPT_TIMEOUT, - get_keys_timeout_seconds (exchange))); + 120 /* seconds */)); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HEADERFUNCTION, @@ -2074,70 +1719,35 @@ request_keys (void *cls) GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HEADERDATA, - kr)); - kr->job = GNUNET_CURL_job_add_with_ct_json (exchange->ctx, - eh, - &keys_completed_cb, - kr); - exchange->kr = kr; + gkh)); + gkh->job = GNUNET_CURL_job_add_with_ct_json (ctx, + eh, + &keys_completed_cb, + gkh); + return gkh; } void -TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange) +TALER_EXCHANGE_get_keys_cancel ( + struct TALER_EXCHANGE_GetKeysHandle *gkh) { - struct TEAH_AuditorListEntry *ale; - - while (NULL != (ale = exchange->auditors_head)) + if (NULL != gkh->job) { - struct TEAH_AuditorInteractionEntry *aie; - - while (NULL != (aie = ale->ai_head)) - { - GNUNET_assert (aie->ale == ale); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Not sending deposit confirmation to auditor `%s' due to exchange disconnect\n", - ale->auditor_url); - TALER_AUDITOR_deposit_confirmation_cancel (aie->dch); - GNUNET_CONTAINER_DLL_remove (ale->ai_head, - ale->ai_tail, - aie); - GNUNET_free (aie); - } - GNUNET_CONTAINER_DLL_remove (exchange->auditors_head, - exchange->auditors_tail, - ale); - TALER_LOG_DEBUG ("Disconnecting the auditor `%s'\n", - ale->auditor_url); - TALER_AUDITOR_disconnect (ale->ah); - GNUNET_free (ale->auditor_url); - GNUNET_free (ale); + GNUNET_CURL_job_cancel (gkh->job); + gkh->job = NULL; } - if (NULL != exchange->kr) - { - GNUNET_CURL_job_cancel (exchange->kr->job); - free_keys_request (exchange->kr); - exchange->kr = NULL; - } - free_key_data (&exchange->key_data); - if (NULL != exchange->key_data_raw) - { - json_decref (exchange->key_data_raw); - exchange->key_data_raw = NULL; - } - if (NULL != exchange->retry_task) - { - GNUNET_SCHEDULER_cancel (exchange->retry_task); - exchange->retry_task = NULL; - } - GNUNET_free (exchange->url); - GNUNET_free (exchange); + TALER_EXCHANGE_keys_decref (gkh->prev_keys); + GNUNET_free (gkh->exchange_url); + GNUNET_free (gkh->url); + GNUNET_free (gkh); } enum GNUNET_GenericReturnValue -TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys, - const struct TALER_ExchangePublicKeyP *pub) +TALER_EXCHANGE_test_signing_key ( + const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ExchangePublicKeyP *pub) { struct GNUNET_TIME_Absolute now; @@ -2164,13 +1774,6 @@ TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys, } -const char * -TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange) -{ - return exchange->url; -} - - const struct TALER_EXCHANGE_DenomPublicKey * TALER_EXCHANGE_get_denomination_key ( const struct TALER_EXCHANGE_Keys *keys, @@ -2214,8 +1817,8 @@ TALER_EXCHANGE_copy_denomination_key ( copy = GNUNET_new (struct TALER_EXCHANGE_DenomPublicKey); *copy = *key; - TALER_denom_pub_deep_copy (©->key, - &key->key); + TALER_denom_pub_copy (©->key, + &key->key); return copy; } @@ -2242,21 +1845,627 @@ TALER_EXCHANGE_get_denomination_key_by_hash ( } -const struct TALER_EXCHANGE_Keys * -TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange) +struct TALER_EXCHANGE_Keys * +TALER_EXCHANGE_keys_incref (struct TALER_EXCHANGE_Keys *keys) +{ + GNUNET_assert (keys->rc < UINT_MAX); + keys->rc++; + return keys; +} + + +void +TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys) +{ + if (NULL == keys) + return; + GNUNET_assert (0 < keys->rc); + keys->rc--; + if (0 != keys->rc) + return; + GNUNET_array_grow (keys->sign_keys, + keys->num_sign_keys, + 0); + for (unsigned int i = 0; i<keys->num_denom_keys; i++) + TALER_denom_pub_free (&keys->denom_keys[i].key); + + GNUNET_array_grow (keys->denom_keys, + keys->denom_keys_size, + 0); + for (unsigned int i = 0; i<keys->num_auditors; i++) + { + GNUNET_array_grow (keys->auditors[i].denom_keys, + keys->auditors[i].num_denom_keys, + 0); + GNUNET_free (keys->auditors[i].auditor_url); + } + GNUNET_array_grow (keys->auditors, + keys->auditors_size, + 0); + TALER_EXCHANGE_free_accounts (keys->accounts_len, + keys->accounts); + GNUNET_array_grow (keys->accounts, + keys->accounts_len, + 0); + free_fees (keys->fees, + keys->fees_len); + json_decref (keys->extensions); + GNUNET_free (keys->cspec.name); + json_decref (keys->cspec.map_alt_unit_names); + GNUNET_free (keys->wallet_balance_limit_without_kyc); + GNUNET_free (keys->version); + GNUNET_free (keys->currency); + GNUNET_free (keys->asset_type); + GNUNET_free (keys->global_fees); + GNUNET_free (keys->exchange_url); + GNUNET_free (keys); +} + + +struct TALER_EXCHANGE_Keys * +TALER_EXCHANGE_keys_from_json (const json_t *j) +{ + const json_t *jkeys; + const char *url; + uint32_t version; + struct GNUNET_TIME_Timestamp expire + = GNUNET_TIME_UNIT_ZERO_TS; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ("version", + &version), + GNUNET_JSON_spec_object_const ("keys", + &jkeys), + TALER_JSON_spec_web_url ("exchange_url", + &url), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("expire", + &expire), + NULL), + GNUNET_JSON_spec_end () + }; + struct TALER_EXCHANGE_Keys *keys; + enum TALER_EXCHANGE_VersionCompatibility compat; + + if (NULL == j) + return NULL; + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return NULL; + } + if (0 != version) + { + return NULL; /* unsupported version */ + } + keys = GNUNET_new (struct TALER_EXCHANGE_Keys); + if (GNUNET_OK != + decode_keys_json (jkeys, + false, + keys, + &compat)) + { + GNUNET_break (0); + return NULL; + } + keys->rc = 1; + keys->key_data_expiration = expire; + keys->exchange_url = GNUNET_strdup (url); + return keys; +} + + +/** + * Data we track per denomination group. + */ +struct GroupData +{ + /** + * The json blob with the group meta-data and list of denominations + */ + json_t *json; + + /** + * Meta data for this group. + */ + struct TALER_DenominationGroup meta; +}; + + +/** + * Add denomination group represented by @a value + * to list of denominations in @a cls. Also frees + * the @a value. + * + * @param[in,out] cls a `json_t *` with an array to build + * @param key unused + * @param value a `struct GroupData *` + * @return #GNUNET_OK (continue to iterate) + */ +static enum GNUNET_GenericReturnValue +add_grp (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + json_t *denominations_by_group = cls; + struct GroupData *gd = value; + const char *cipher; + json_t *ge; + bool age_restricted = gd->meta.age_mask.bits != 0; + + (void) key; + switch (gd->meta.cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + cipher = age_restricted ? "RSA+age_restricted" : "RSA"; + break; + case GNUNET_CRYPTO_BSA_CS: + cipher = age_restricted ? "CS+age_restricted" : "CS"; + break; + default: + GNUNET_assert (false); + } + + ge = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + cipher), + GNUNET_JSON_pack_array_steal ("denoms", + gd->json), + TALER_JSON_PACK_DENOM_FEES ("fee", + &gd->meta.fees), + GNUNET_JSON_pack_allow_null ( + age_restricted + ? GNUNET_JSON_pack_uint64 ("age_mask", + gd->meta.age_mask.bits) + : GNUNET_JSON_pack_string ("dummy", + NULL)), + TALER_JSON_pack_amount ("value", + &gd->meta.value)); + GNUNET_assert (0 == + json_array_append_new (denominations_by_group, + ge)); + GNUNET_free (gd); + return GNUNET_OK; +} + + +/** + * Convert array of account restrictions @a ars to JSON. + * + * @param ar_len length of @a ars + * @param ars account restrictions to convert + * @return JSON representation + */ +static json_t * +ar_to_json (unsigned int ar_len, + const struct TALER_EXCHANGE_AccountRestriction ars[static ar_len]) { - (void) TALER_EXCHANGE_check_keys_current (exchange, - TALER_EXCHANGE_CKF_NONE); - return &exchange->key_data; + json_t *rval; + + rval = json_array (); + GNUNET_assert (NULL != rval); + for (unsigned int i = 0; i<ar_len; i++) + { + const struct TALER_EXCHANGE_AccountRestriction *ar = &ars[i]; + + switch (ar->type) + { + case TALER_EXCHANGE_AR_INVALID: + GNUNET_break (0); + json_decref (rval); + return NULL; + case TALER_EXCHANGE_AR_DENY: + GNUNET_assert ( + 0 == + json_array_append_new ( + rval, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "deny")))); + break; + case TALER_EXCHANGE_AR_REGEX: + GNUNET_assert ( + 0 == + json_array_append_new ( + rval, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ( + "type", + "regex"), + GNUNET_JSON_pack_string ( + "payto_regex", + ar->details.regex.posix_egrep), + GNUNET_JSON_pack_string ( + "human_hint", + ar->details.regex.human_hint), + GNUNET_JSON_pack_object_incref ( + "human_hint_i18n", + (json_t *) ar->details.regex.human_hint_i18n) + ))); + break; + } + } + return rval; } json_t * -TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange) +TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) { - (void) TALER_EXCHANGE_check_keys_current (exchange, - TALER_EXCHANGE_CKF_NONE); - return json_deep_copy (exchange->key_data_raw); + struct GNUNET_TIME_Timestamp now; + json_t *keys; + json_t *signkeys; + json_t *denominations_by_group; + json_t *auditors; + json_t *recoup; + json_t *wire_fees; + json_t *accounts; + json_t *global_fees; + json_t *wblwk = NULL; + + now = GNUNET_TIME_timestamp_get (); + signkeys = json_array (); + GNUNET_assert (NULL != signkeys); + for (unsigned int i = 0; i<kd->num_sign_keys; i++) + { + const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i]; + json_t *signkey; + + if (GNUNET_TIME_timestamp_cmp (now, + >, + sk->valid_until)) + continue; /* skip keys that have expired */ + signkey = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("key", + &sk->key), + GNUNET_JSON_pack_data_auto ("master_sig", + &sk->master_sig), + GNUNET_JSON_pack_timestamp ("stamp_start", + sk->valid_from), + GNUNET_JSON_pack_timestamp ("stamp_expire", + sk->valid_until), + GNUNET_JSON_pack_timestamp ("stamp_end", + sk->valid_legal)); + GNUNET_assert (NULL != signkey); + GNUNET_assert (0 == + json_array_append_new (signkeys, + signkey)); + } + + denominations_by_group = json_array (); + GNUNET_assert (NULL != denominations_by_group); + { + struct GNUNET_CONTAINER_MultiHashMap *dbg; + + dbg = GNUNET_CONTAINER_multihashmap_create (128, + false); + for (unsigned int i = 0; i<kd->num_denom_keys; i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i]; + struct TALER_DenominationGroup meta = { + .cipher = dk->key.bsign_pub_key->cipher, + .value = dk->value, + .fees = dk->fees, + .age_mask = dk->key.age_mask + }; + struct GNUNET_HashCode key; + struct GroupData *gd; + json_t *denom; + struct GNUNET_JSON_PackSpec key_spec; + + if (GNUNET_TIME_timestamp_cmp (now, + >, + dk->expire_deposit)) + continue; /* skip keys that have expired */ + TALER_denomination_group_get_key (&meta, + &key); + gd = GNUNET_CONTAINER_multihashmap_get (dbg, + &key); + if (NULL == gd) + { + gd = GNUNET_new (struct GroupData); + gd->meta = meta; + gd->json = json_array (); + GNUNET_assert (NULL != gd->json); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (dbg, + &key, + gd, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + + } + switch (meta.cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + key_spec = + GNUNET_JSON_pack_rsa_public_key ( + "rsa_pub", + dk->key.bsign_pub_key->details.rsa_public_key); + break; + case GNUNET_CRYPTO_BSA_CS: + key_spec = + GNUNET_JSON_pack_data_varsize ( + "cs_pub", + &dk->key.bsign_pub_key->details.cs_public_key, + sizeof (dk->key.bsign_pub_key->details.cs_public_key)); + break; + default: + GNUNET_assert (false); + } + denom = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_timestamp ("stamp_expire_deposit", + dk->expire_deposit), + GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw", + dk->withdraw_valid_until), + GNUNET_JSON_pack_timestamp ("stamp_start", + dk->valid_from), + GNUNET_JSON_pack_timestamp ("stamp_expire_legal", + dk->expire_legal), + GNUNET_JSON_pack_data_auto ("master_sig", + &dk->master_sig), + key_spec + ); + GNUNET_assert (0 == + json_array_append_new (gd->json, + denom)); + } + GNUNET_CONTAINER_multihashmap_iterate (dbg, + &add_grp, + denominations_by_group); + GNUNET_CONTAINER_multihashmap_destroy (dbg); + } + + auditors = json_array (); + GNUNET_assert (NULL != auditors); + for (unsigned int i = 0; i<kd->num_auditors; i++) + { + const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i]; + json_t *a; + json_t *adenoms; + + adenoms = json_array (); + GNUNET_assert (NULL != adenoms); + for (unsigned int j = 0; j<ai->num_denom_keys; j++) + { + const struct TALER_EXCHANGE_AuditorDenominationInfo *adi = + &ai->denom_keys[j]; + const struct TALER_EXCHANGE_DenomPublicKey *dk = + &kd->denom_keys[adi->denom_key_offset]; + json_t *k; + + GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys); + if (GNUNET_TIME_timestamp_cmp (now, + >, + dk->expire_deposit)) + continue; /* skip auditor signatures for denomination keys that have expired */ + GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys); + k = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("denom_pub_h", + &dk->h_key), + GNUNET_JSON_pack_data_auto ("auditor_sig", + &adi->auditor_sig)); + GNUNET_assert (0 == + json_array_append_new (adenoms, + k)); + } + + a = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("auditor_pub", + &ai->auditor_pub), + GNUNET_JSON_pack_string ("auditor_url", + ai->auditor_url), + GNUNET_JSON_pack_array_steal ("denomination_keys", + adenoms)); + GNUNET_assert (0 == + json_array_append_new (auditors, + a)); + } + + global_fees = json_array (); + GNUNET_assert (NULL != global_fees); + for (unsigned int i = 0; i<kd->num_global_fees; i++) + { + const struct TALER_EXCHANGE_GlobalFee *gf + = &kd->global_fees[i]; + + if (GNUNET_TIME_absolute_is_past (gf->end_date.abs_time)) + continue; + GNUNET_assert ( + 0 == + json_array_append_new ( + global_fees, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_timestamp ("start_date", + gf->start_date), + GNUNET_JSON_pack_timestamp ("end_date", + gf->end_date), + TALER_JSON_PACK_GLOBAL_FEES (&gf->fees), + GNUNET_JSON_pack_time_rel ("history_expiration", + gf->history_expiration), + GNUNET_JSON_pack_time_rel ("purse_timeout", + gf->purse_timeout), + GNUNET_JSON_pack_uint64 ("purse_account_limit", + gf->purse_account_limit), + GNUNET_JSON_pack_data_auto ("master_sig", + &gf->master_sig)))); + } + + accounts = json_array (); + GNUNET_assert (NULL != accounts); + for (unsigned int i = 0; i<kd->accounts_len; i++) + { + const struct TALER_EXCHANGE_WireAccount *acc + = &kd->accounts[i]; + json_t *credit_restrictions; + json_t *debit_restrictions; + + credit_restrictions + = ar_to_json (acc->credit_restrictions_length, + acc->credit_restrictions); + GNUNET_assert (NULL != credit_restrictions); + debit_restrictions + = ar_to_json (acc->debit_restrictions_length, + acc->debit_restrictions); + GNUNET_assert (NULL != debit_restrictions); + GNUNET_assert ( + 0 == + json_array_append_new ( + accounts, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("payto_uri", + acc->payto_uri), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("conversion_url", + acc->conversion_url)), + GNUNET_JSON_pack_int64 ("priority", + acc->priority), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("bank_label", + acc->bank_label)), + GNUNET_JSON_pack_array_steal ("debit_restrictions", + debit_restrictions), + GNUNET_JSON_pack_array_steal ("credit_restrictions", + credit_restrictions), + GNUNET_JSON_pack_data_auto ("master_sig", + &acc->master_sig)))); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Serialized %u/%u wire accounts to JSON\n", + (unsigned int) json_array_size (accounts), + kd->accounts_len); + + wire_fees = json_object (); + GNUNET_assert (NULL != wire_fees); + for (unsigned int i = 0; i<kd->fees_len; i++) + { + const struct TALER_EXCHANGE_WireFeesByMethod *fbw + = &kd->fees[i]; + json_t *wf; + + wf = json_array (); + GNUNET_assert (NULL != wf); + for (struct TALER_EXCHANGE_WireAggregateFees *p = fbw->fees_head; + NULL != p; + p = p->next) + { + GNUNET_assert ( + 0 == + json_array_append_new ( + wf, + GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("wire_fee", + &p->fees.wire), + TALER_JSON_pack_amount ("closing_fee", + &p->fees.closing), + GNUNET_JSON_pack_timestamp ("start_date", + p->start_date), + GNUNET_JSON_pack_timestamp ("end_date", + p->end_date), + GNUNET_JSON_pack_data_auto ("sig", + &p->master_sig)))); + } + GNUNET_assert (0 == + json_object_set_new (wire_fees, + fbw->method, + wf)); + } + + recoup = json_array (); + GNUNET_assert (NULL != recoup); + for (unsigned int i = 0; i<kd->num_denom_keys; i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *dk + = &kd->denom_keys[i]; + if (! dk->revoked) + continue; + GNUNET_assert (0 == + json_array_append_new ( + recoup, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("h_denom_pub", + &dk->h_key)))); + } + + wblwk = json_array (); + GNUNET_assert (NULL != wblwk); + for (unsigned int i = 0; i<kd->wblwk_length; i++) + { + const struct TALER_Amount *a = &kd->wallet_balance_limit_without_kyc[i]; + + GNUNET_assert (0 == + json_array_append_new ( + wblwk, + TALER_JSON_from_amount (a))); + } + + keys = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("version", + kd->version), + GNUNET_JSON_pack_string ("currency", + kd->currency), + GNUNET_JSON_pack_object_steal ("currency_specification", + TALER_CONFIG_currency_specs_to_json ( + &kd->cspec)), + TALER_JSON_pack_amount ("stefan_abs", + &kd->stefan_abs), + TALER_JSON_pack_amount ("stefan_log", + &kd->stefan_log), + GNUNET_JSON_pack_double ("stefan_lin", + kd->stefan_lin), + GNUNET_JSON_pack_string ("asset_type", + kd->asset_type), + GNUNET_JSON_pack_data_auto ("master_public_key", + &kd->master_pub), + GNUNET_JSON_pack_time_rel ("reserve_closing_delay", + kd->reserve_closing_delay), + GNUNET_JSON_pack_timestamp ("list_issue_date", + kd->list_issue_date), + GNUNET_JSON_pack_array_steal ("global_fees", + global_fees), + GNUNET_JSON_pack_array_steal ("signkeys", + signkeys), + GNUNET_JSON_pack_object_steal ("wire_fees", + wire_fees), + GNUNET_JSON_pack_array_steal ("accounts", + accounts), + GNUNET_JSON_pack_array_steal ("wads", + json_array ()), + GNUNET_JSON_pack_array_steal ("denominations", + denominations_by_group), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_steal ("recoup", + recoup)), + GNUNET_JSON_pack_array_steal ("auditors", + auditors), + GNUNET_JSON_pack_bool ("rewards_allowed", + kd->rewards_allowed), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("extensions", + kd->extensions)), + GNUNET_JSON_pack_allow_null ( + GNUNET_is_zero (&kd->extensions_sig) + ? GNUNET_JSON_pack_string ("dummy", + NULL) + : GNUNET_JSON_pack_data_auto ("extensions_sig", + &kd->extensions_sig)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_steal ("wallet_balance_limit_without_kyc", + wblwk)) + + ); + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("version", + EXCHANGE_SERIALIZATION_FORMAT_VERSION), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_timestamp ("expire", + kd->key_data_expiration)), + GNUNET_JSON_pack_string ("exchange_url", + kd->exchange_url), + GNUNET_JSON_pack_object_steal ("keys", + keys)); } |