Merge branch 'master' into age-withdraw

This commit is contained in:
Özgür Kesim 2023-07-14 09:25:43 +02:00
commit 34f44ccb27
Signed by: oec
GPG Key ID: 3D76A56D79EDD9D7
23 changed files with 1329 additions and 1515 deletions

@ -1 +1 @@
Subproject commit fa4729db5637c82d5fc6f5bb7021f6c350c8c5a6
Subproject commit 66e99d09d4351bb6e6c5fd442f14ec7cf1363a81

View File

@ -184,7 +184,6 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \
taler-exchange-httpd_terms.c taler-exchange-httpd_terms.h \
taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h \
taler-exchange-httpd_wire.c taler-exchange-httpd_wire.h \
taler-exchange-httpd_withdraw.c taler-exchange-httpd_withdraw.h
taler_exchange_httpd_LDADD = \

View File

@ -70,7 +70,6 @@
#include "taler-exchange-httpd_reserves_status.h"
#include "taler-exchange-httpd_terms.h"
#include "taler-exchange-httpd_transfers_get.h"
#include "taler-exchange-httpd_wire.h"
#include "taler-exchange-httpd_withdraw.h"
#include "taler_exchangedb_lib.h"
#include "taler_exchangedb_plugin.h"

View File

@ -41,7 +41,7 @@
*
* Returned via both /config and /keys endpoints.
*/
#define EXCHANGE_PROTOCOL_VERSION "15:0:0"
#define EXCHANGE_PROTOCOL_VERSION "16:0:1"
/**

View File

@ -388,6 +388,117 @@ struct SuspendedKeysRequests
struct GNUNET_TIME_Absolute timeout;
};
/**
* Information we track about wire fees.
*/
struct WireFeeSet
{
/**
* Kept in a DLL.
*/
struct WireFeeSet *next;
/**
* Kept in a DLL.
*/
struct WireFeeSet *prev;
/**
* Actual fees.
*/
struct TALER_WireFeeSet fees;
/**
* Start date of fee validity (inclusive).
*/
struct GNUNET_TIME_Timestamp start_date;
/**
* End date of fee validity (exclusive).
*/
struct GNUNET_TIME_Timestamp end_date;
/**
* Wire method the fees apply to.
*/
char *method;
};
/**
* State we keep per thread to cache the /wire response.
*/
struct WireStateHandle
{
/**
* Cached reply for /wire response.
*/
struct MHD_Response *wire_reply;
/**
* JSON reply for /wire response.
*/
json_t *json_reply;
/**
* ETag for this response (if any).
*/
char *etag;
/**
* head of DLL of wire fees.
*/
struct WireFeeSet *wfs_head;
/**
* Tail of DLL of wire fees.
*/
struct WireFeeSet *wfs_tail;
/**
* Earliest timestamp of all the wire methods when we have no more fees.
*/
struct GNUNET_TIME_Absolute cache_expiration;
/**
* @e cache_expiration time, formatted.
*/
char dat[128];
/**
* For which (global) wire_generation was this data structure created?
* Used to check when we are outdated and need to be re-generated.
*/
uint64_t wire_generation;
/**
* HTTP status to return with this response.
*/
unsigned int http_status;
};
/**
* Stores the latest generation of our wire response.
*/
static struct WireStateHandle *wire_state;
/**
* Handler listening for wire updates by other exchange
* services.
*/
static struct GNUNET_DB_EventHandler *wire_eh;
/**
* Counter incremented whenever we have a reason to re-build the #wire_state
* because something external changed.
*/
static uint64_t wire_generation;
/**
* Stores the latest generation of our key state.
*/
@ -465,6 +576,545 @@ static struct TALER_SecurityModulePublicKeyP esign_sm_pub;
static bool terminating;
/**
* Free memory associated with @a wsh
*
* @param[in] wsh wire state to destroy
*/
static void
destroy_wire_state (struct WireStateHandle *wsh)
{
struct WireFeeSet *wfs;
while (NULL != (wfs = wsh->wfs_head))
{
GNUNET_CONTAINER_DLL_remove (wsh->wfs_head,
wsh->wfs_tail,
wfs);
GNUNET_free (wfs->method);
GNUNET_free (wfs);
}
MHD_destroy_response (wsh->wire_reply);
json_decref (wsh->json_reply);
GNUNET_free (wsh->etag);
GNUNET_free (wsh);
}
/**
* Function called whenever another exchange process has updated
* the wire data in the database.
*
* @param cls NULL
* @param extra unused
* @param extra_size number of bytes in @a extra unused
*/
static void
wire_update_event_cb (void *cls,
const void *extra,
size_t extra_size)
{
(void) cls;
(void) extra;
(void) extra_size;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received /wire update event\n");
TEH_check_invariants ();
wire_generation++;
key_generation++;
TEH_resume_keys_requests (false);
}
enum GNUNET_GenericReturnValue
TEH_wire_init ()
{
struct GNUNET_DB_EventHeaderP es = {
.size = htons (sizeof (es)),
.type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
};
wire_eh = TEH_plugin->event_listen (TEH_plugin->cls,
GNUNET_TIME_UNIT_FOREVER_REL,
&es,
&wire_update_event_cb,
NULL);
if (NULL == wire_eh)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
void
TEH_wire_done ()
{
if (NULL != wire_state)
{
destroy_wire_state (wire_state);
wire_state = NULL;
}
if (NULL != wire_eh)
{
TEH_plugin->event_listen_cancel (TEH_plugin->cls,
wire_eh);
wire_eh = NULL;
}
}
/**
* Add information about a wire account to @a cls.
*
* @param cls a `json_t *` object to expand with wire account details
* @param payto_uri the exchange bank account URI to add
* @param conversion_url URL of a conversion service, NULL if there is no conversion
* @param debit_restrictions JSON array with debit restrictions on the account
* @param credit_restrictions JSON array with credit restrictions on the account
* @param master_sig master key signature affirming that this is a bank
* account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS)
*/
static void
add_wire_account (void *cls,
const char *payto_uri,
const char *conversion_url,
const json_t *debit_restrictions,
const json_t *credit_restrictions,
const struct TALER_MasterSignatureP *master_sig)
{
json_t *a = cls;
if (0 !=
json_array_append_new (
a,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("payto_uri",
payto_uri),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("conversion_url",
conversion_url)),
GNUNET_JSON_pack_array_incref ("debit_restrictions",
(json_t *) debit_restrictions),
GNUNET_JSON_pack_array_incref ("credit_restrictions",
(json_t *) credit_restrictions),
GNUNET_JSON_pack_data_auto ("master_sig",
master_sig))))
{
GNUNET_break (0); /* out of memory!? */
return;
}
}
/**
* Closure for #add_wire_fee().
*/
struct AddContext
{
/**
* Wire method the fees are for.
*/
char *wire_method;
/**
* Wire state we are building.
*/
struct WireStateHandle *wsh;
/**
* Array to append the fee to.
*/
json_t *a;
/**
* Context we hash "everything" we add into. This is used
* to compute the etag. Technically, we only hash the
* master_sigs, as they imply the rest.
*/
struct GNUNET_HashContext *hc;
/**
* Set to the maximum end-date seen.
*/
struct GNUNET_TIME_Absolute max_seen;
};
/**
* Add information about a wire account to @a cls.
*
* @param cls a `struct AddContext`
* @param fees the wire fees we charge
* @param start_date from when are these fees valid (start date)
* @param end_date until when are these fees valid (end date, exclusive)
* @param master_sig master key signature affirming that this is the correct
* fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES)
*/
static void
add_wire_fee (void *cls,
const struct TALER_WireFeeSet *fees,
struct GNUNET_TIME_Timestamp start_date,
struct GNUNET_TIME_Timestamp end_date,
const struct TALER_MasterSignatureP *master_sig)
{
struct AddContext *ac = cls;
struct WireFeeSet *wfs;
GNUNET_CRYPTO_hash_context_read (ac->hc,
master_sig,
sizeof (*master_sig));
ac->max_seen = GNUNET_TIME_absolute_max (ac->max_seen,
end_date.abs_time);
wfs = GNUNET_new (struct WireFeeSet);
wfs->start_date = start_date;
wfs->end_date = end_date;
wfs->fees = *fees;
wfs->method = GNUNET_strdup (ac->wire_method);
GNUNET_CONTAINER_DLL_insert (ac->wsh->wfs_head,
ac->wsh->wfs_tail,
wfs);
if (0 !=
json_array_append_new (
ac->a,
GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("wire_fee",
&fees->wire),
TALER_JSON_pack_amount ("closing_fee",
&fees->closing),
GNUNET_JSON_pack_timestamp ("start_date",
start_date),
GNUNET_JSON_pack_timestamp ("end_date",
end_date),
GNUNET_JSON_pack_data_auto ("sig",
master_sig))))
{
GNUNET_break (0); /* out of memory!? */
return;
}
}
/**
* Create the /wire response from our database state.
*
* @return NULL on error
*/
static struct WireStateHandle *
build_wire_state (void)
{
json_t *wire_accounts_array;
json_t *wire_fee_object;
uint64_t wg = wire_generation; /* must be obtained FIRST */
enum GNUNET_DB_QueryStatus qs;
struct WireStateHandle *wsh;
struct GNUNET_HashContext *hc;
json_t *wads;
wsh = GNUNET_new (struct WireStateHandle);
wsh->wire_generation = wg;
wire_accounts_array = json_array ();
GNUNET_assert (NULL != wire_accounts_array);
qs = TEH_plugin->get_wire_accounts (TEH_plugin->cls,
&add_wire_account,
wire_accounts_array);
if (0 > qs)
{
GNUNET_break (0);
json_decref (wire_accounts_array);
wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
wsh->wire_reply
= TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
"get_wire_accounts");
return wsh;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Build /wire data with %u accounts\n",
(unsigned int) json_array_size (wire_accounts_array));
wire_fee_object = json_object ();
GNUNET_assert (NULL != wire_fee_object);
wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS;
hc = GNUNET_CRYPTO_hash_context_start ();
{
json_t *account;
size_t index;
json_array_foreach (wire_accounts_array, index, account) {
char *wire_method;
const char *payto_uri = json_string_value (json_object_get (account,
"payto_uri"));
GNUNET_assert (NULL != payto_uri);
wire_method = TALER_payto_get_method (payto_uri);
if (NULL == wire_method)
{
wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
wsh->wire_reply
= TALER_MHD_make_error (
TALER_EC_EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED,
payto_uri);
json_decref (wire_accounts_array);
json_decref (wire_fee_object);
GNUNET_CRYPTO_hash_context_abort (hc);
return wsh;
}
if (NULL == json_object_get (wire_fee_object,
wire_method))
{
struct AddContext ac = {
.wire_method = wire_method,
.wsh = wsh,
.a = json_array (),
.hc = hc
};
GNUNET_assert (NULL != ac.a);
qs = TEH_plugin->get_wire_fees (TEH_plugin->cls,
wire_method,
&add_wire_fee,
&ac);
if (0 > qs)
{
GNUNET_break (0);
json_decref (ac.a);
json_decref (wire_fee_object);
json_decref (wire_accounts_array);
GNUNET_free (wire_method);
wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
wsh->wire_reply
= TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
"get_wire_fees");
GNUNET_CRYPTO_hash_context_abort (hc);
return wsh;
}
if (0 == json_array_size (ac.a))
{
json_decref (ac.a);
json_decref (wire_accounts_array);
json_decref (wire_fee_object);
wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
wsh->wire_reply
= TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED,
wire_method);
GNUNET_free (wire_method);
GNUNET_CRYPTO_hash_context_abort (hc);
return wsh;
}
wsh->cache_expiration = GNUNET_TIME_absolute_min (ac.max_seen,
wsh->cache_expiration);
GNUNET_assert (0 ==
json_object_set_new (wire_fee_object,
wire_method,
ac.a));
}
GNUNET_free (wire_method);
}
}
wads = json_array (); /* #7271 */
GNUNET_assert (NULL != wads);
wsh->json_reply = GNUNET_JSON_PACK (
GNUNET_JSON_pack_array_incref ("accounts",
wire_accounts_array),
GNUNET_JSON_pack_array_incref ("wads",
wads),
GNUNET_JSON_pack_object_incref ("fees",
wire_fee_object));
wsh->wire_reply = TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_array_steal ("accounts",
wire_accounts_array),
GNUNET_JSON_pack_array_steal ("wads",
wads),
GNUNET_JSON_pack_object_steal ("fees",
wire_fee_object),
GNUNET_JSON_pack_data_auto ("master_public_key",
&TEH_master_public_key));
{
struct GNUNET_TIME_Timestamp m;
m = GNUNET_TIME_absolute_to_timestamp (wsh->cache_expiration);
TALER_MHD_get_date_string (m.abs_time,
wsh->dat);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Setting 'Expires' header for '/wire' to '%s'\n",
wsh->dat);
GNUNET_break (MHD_YES ==
MHD_add_response_header (wsh->wire_reply,
MHD_HTTP_HEADER_EXPIRES,
wsh->dat));
}
/* Set cache control headers: our response varies depending on these headers */
GNUNET_break (MHD_YES ==
MHD_add_response_header (wsh->wire_reply,
MHD_HTTP_HEADER_VARY,
MHD_HTTP_HEADER_ACCEPT_ENCODING));
/* Information is always public, revalidate after 1 day */
GNUNET_break (MHD_YES ==
MHD_add_response_header (wsh->wire_reply,
MHD_HTTP_HEADER_CACHE_CONTROL,
"public,max-age=86400"));
{
struct GNUNET_HashCode h;
char etag[sizeof (h) * 2];
char *end;
GNUNET_CRYPTO_hash_context_finish (hc,
&h);
end = GNUNET_STRINGS_data_to_string (&h,
sizeof (h),
etag,
sizeof (etag));
*end = '\0';
wsh->etag = GNUNET_strdup (etag);
GNUNET_break (MHD_YES ==
MHD_add_response_header (wsh->wire_reply,
MHD_HTTP_HEADER_ETAG,
etag));
}
wsh->http_status = MHD_HTTP_OK;
return wsh;
}
void
TEH_wire_update_state (void)
{
struct GNUNET_DB_EventHeaderP es = {
.size = htons (sizeof (es)),
.type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED),
};
TEH_plugin->event_notify (TEH_plugin->cls,
&es,
NULL,
0);
wire_generation++;
key_generation++;
}
/**
* Return the current key state for this thread. Possibly
* re-builds the key state if we have reason to believe
* that something changed.
*
* @return NULL on error
*/
struct WireStateHandle *
get_wire_state (void)
{
struct WireStateHandle *old_wsh;
old_wsh = wire_state;
if ( (NULL == old_wsh) ||
(old_wsh->wire_generation < wire_generation) )
{
struct WireStateHandle *wsh;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Rebuilding /wire, generation upgrade from %llu to %llu\n",
(unsigned long long) (NULL == old_wsh) ? 0LL :
old_wsh->wire_generation,
(unsigned long long) wire_generation);
TEH_check_invariants ();
wsh = build_wire_state ();
wire_state = wsh;
if (NULL != old_wsh)
destroy_wire_state (old_wsh);
TEH_check_invariants ();
return wsh;
}
return old_wsh;
}
MHD_RESULT
TEH_handler_wire (struct TEH_RequestContext *rc,
const char *const args[])
{
struct WireStateHandle *wsh;
(void) args;
wsh = get_wire_state ();
if (NULL == wsh)
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
NULL);
{
const char *etag;
etag = MHD_lookup_connection_value (rc->connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_IF_NONE_MATCH);
if ( (NULL != etag) &&
(MHD_HTTP_OK == wsh->http_status) &&
(NULL != wsh->etag) &&
(0 == strcmp (etag,
wsh->etag)) )
{
MHD_RESULT ret;
struct MHD_Response *resp;
resp = MHD_create_response_from_buffer (0,
NULL,
MHD_RESPMEM_PERSISTENT);
TALER_MHD_add_global_headers (resp);
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
MHD_HTTP_HEADER_EXPIRES,
wsh->dat));
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
MHD_HTTP_HEADER_ETAG,
wsh->etag));
ret = MHD_queue_response (rc->connection,
MHD_HTTP_NOT_MODIFIED,
resp);
GNUNET_break (MHD_YES == ret);
MHD_destroy_response (resp);
return ret;
}
}
return MHD_queue_response (rc->connection,
wsh->http_status,
wsh->wire_reply);
}
const struct TALER_WireFeeSet *
TEH_wire_fees_by_time (
struct GNUNET_TIME_Timestamp ts,
const char *method)
{
struct WireStateHandle *wsh = get_wire_state ();
for (struct WireFeeSet *wfs = wsh->wfs_head;
NULL != wfs;
wfs = wfs->next)
{
if (0 != strcmp (method,
wfs->method))
continue;
if ( (GNUNET_TIME_timestamp_cmp (wfs->start_date,
>,
ts)) ||
(GNUNET_TIME_timestamp_cmp (ts,
>=,
wfs->end_date)) )
continue;
return &wfs->fees;
}
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"No wire fees for method `%s' at %s configured\n",
method,
GNUNET_TIME_timestamp2s (ts));
return NULL;
}
/**
* Function called to forcefully resume suspended keys requests.
*
@ -1673,6 +2323,7 @@ add_denom_key_cb (void *cls,
*/
static enum GNUNET_GenericReturnValue
setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
struct WireStateHandle *wsh,
struct MHD_Response *response)
{
char dat[128];
@ -1692,12 +2343,17 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
{
struct GNUNET_TIME_Relative r;
struct GNUNET_TIME_Absolute a;
struct GNUNET_TIME_Timestamp km;
struct GNUNET_TIME_Timestamp m;
struct GNUNET_TIME_Timestamp we;
r = GNUNET_TIME_relative_min (TEH_max_keys_caching,
ksh->rekey_frequency);
a = GNUNET_TIME_relative_to_absolute (r);
m = GNUNET_TIME_absolute_to_timestamp (a);
km = GNUNET_TIME_absolute_to_timestamp (a);
we = GNUNET_TIME_absolute_to_timestamp (wsh->cache_expiration);
m = GNUNET_TIME_timestamp_min (we,
km);
TALER_MHD_get_date_string (m.abs_time,
dat);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@ -1777,8 +2433,10 @@ create_krd (struct TEH_KeyStateHandle *ksh,
struct TALER_ExchangeSignatureP exchange_sig;
struct TALER_ExchangePublicKeyP grouped_exchange_pub;
struct TALER_ExchangeSignatureP grouped_exchange_sig;
struct WireStateHandle *wsh;
json_t *keys;
wsh = get_wire_state ();
GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
last_cherry_pick_date.abs_time));
GNUNET_assert (NULL != signkeys);
@ -1850,6 +2508,12 @@ create_krd (struct TEH_KeyStateHandle *ksh,
ksh->signature_expires);
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Build /keys data with %u wire accounts\n",
(unsigned int) json_array_size (
json_object_get (wsh->json_reply,
"accounts")));
keys = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("version",
EXCHANGE_PROTOCOL_VERSION),
@ -1874,6 +2538,15 @@ create_krd (struct TEH_KeyStateHandle *ksh,
recoup),
GNUNET_JSON_pack_array_incref ("denoms",
denoms),
GNUNET_JSON_pack_array_incref ("wads",
json_object_get (wsh->json_reply,
"wads")),
GNUNET_JSON_pack_array_incref ("accounts",
json_object_get (wsh->json_reply,
"accounts")),
GNUNET_JSON_pack_object_incref ("wire_fees",
json_object_get (wsh->json_reply,
"fees")),
GNUNET_JSON_pack_array_incref ("denominations",
grouped_denominations),
GNUNET_JSON_pack_array_incref ("auditors",
@ -2010,6 +2683,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
GNUNET_assert (NULL != krd.response_uncompressed);
GNUNET_assert (GNUNET_OK ==
setup_general_response_headers (ksh,
wsh,
krd.response_uncompressed));
GNUNET_break (MHD_YES ==
MHD_add_response_header (krd.response_uncompressed,
@ -2032,7 +2706,18 @@ create_krd (struct TEH_KeyStateHandle *ksh,
"deflate")) );
GNUNET_assert (GNUNET_OK ==
setup_general_response_headers (ksh,
wsh,
krd.response_compressed));
/* Set cache control headers: our response varies depending on these headers */
GNUNET_break (MHD_YES ==
MHD_add_response_header (wsh->wire_reply,
MHD_HTTP_HEADER_VARY,
MHD_HTTP_HEADER_ACCEPT_ENCODING));
/* Information is always public, revalidate after 1 day */
GNUNET_break (MHD_YES ==
MHD_add_response_header (wsh->wire_reply,
MHD_HTTP_HEADER_CACHE_CONTROL,
"public,max-age=86400"));
GNUNET_break (MHD_YES ==
MHD_add_response_header (krd.response_compressed,
MHD_HTTP_HEADER_ETAG,
@ -2290,8 +2975,18 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
/* Now that we have found/created the right group, add the
denomination to the list */
{
struct HelperDenomination *hd;
struct GNUNET_JSON_PackSpec key_spec;
bool private_key_lost;
hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
&dk->h_denom_pub.hash);
private_key_lost
= (NULL == hd) ||
GNUNET_TIME_absolute_is_past (
GNUNET_TIME_absolute_add (
hd->start_time.abs_time,
hd->validity_duration));
switch (meta.cipher)
{
case TALER_DENOMINATION_RSA:
@ -2314,6 +3009,12 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
entry = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("master_sig",
&dk->master_sig),
GNUNET_JSON_pack_allow_null (
private_key_lost
? GNUNET_JSON_pack_bool ("lost",
true)
: GNUNET_JSON_pack_string ("dummy",
NULL)),
GNUNET_JSON_pack_timestamp ("stamp_start",
dk->meta.start),
GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
@ -2666,7 +3367,7 @@ keys_get_state (bool management_only)
if ( (old_ksh->key_generation < key_generation) ||
(GNUNET_TIME_absolute_is_past (old_ksh->signature_expires.abs_time)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Rebuilding /keys, generation upgrade from %llu to %llu\n",
(unsigned long long) old_ksh->key_generation,
(unsigned long long) key_generation);
@ -3195,7 +3896,9 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc,
{
struct GNUNET_TIME_Timestamp last_issue_date;
const char *etag;
struct WireStateHandle *wsh;
wsh = get_wire_state ();
etag = MHD_lookup_connection_value (rc->connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_IF_NONE_MATCH);
@ -3293,6 +3996,7 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc,
TALER_MHD_add_global_headers (resp);
GNUNET_break (GNUNET_OK ==
setup_general_response_headers (ksh,
wsh,
resp));
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,

View File

@ -154,6 +154,60 @@ struct TEH_KeyStateHandle;
void
TEH_check_invariants (void);
/**
* Clean up wire subsystem.
*/
void
TEH_wire_done (void);
/**
* Look up wire fee structure by @a ts.
*
* @param ts timestamp to lookup wire fees at
* @param method wire method to lookup fees for
* @return the wire fee details, or
* NULL if none are configured for @a ts and @a method
*/
const struct TALER_WireFeeSet *
TEH_wire_fees_by_time (
struct GNUNET_TIME_Timestamp ts,
const char *method);
/**
* Initialize wire subsystem.
*
* @return #GNUNET_OK on success
*/
enum GNUNET_GenericReturnValue
TEH_wire_init (void);
/**
* Something changed in the database. Rebuild the wire replies. This function
* should be called if the exchange learns about a new signature from our
* master key.
*
* (We do not do so immediately, but merely signal to all threads that they
* need to rebuild their wire state upon the next call to
* #TEH_handler_wire()).
*/
void
TEH_wire_update_state (void);
/**
* Handle a "/wire" request.
*
* @param rc request context
* @param args array of additional options (must be empty for this function)
* @return MHD result code
*/
MHD_RESULT
TEH_handler_wire (struct TEH_RequestContext *rc,
const char *const args[]);
/**
* Return the current key state for this thread. Possibly re-builds the key

View File

@ -28,7 +28,7 @@
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_wire.h"
#include "taler-exchange-httpd_keys.h"
/**

View File

@ -29,7 +29,7 @@
#include "taler_signatures.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_wire.h"
#include "taler-exchange-httpd_keys.h"
/**

View File

@ -29,7 +29,7 @@
#include "taler_signatures.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_wire.h"
#include "taler-exchange-httpd_keys.h"
/**

View File

@ -34,7 +34,6 @@
#include "taler-exchange-httpd_responses.h"
#include "taler_exchangedb_lib.h"
#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_wire.h"
/**

View File

@ -27,7 +27,7 @@
#include "taler_mhd_lib.h"
#include "taler_json_lib.h"
#include "taler_dbevents.h"
#include "taler-exchange-httpd_wire.h"
#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_reserves_close.h"
#include "taler-exchange-httpd_responses.h"

View File

@ -1,663 +0,0 @@
/*
This file is part of TALER
Copyright (C) 2015-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_wire.c
* @brief Handle /wire requests
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_json_lib.h>
#include "taler_dbevents.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_wire.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include <jansson.h>
/**
* Information we track about wire fees.
*/
struct WireFeeSet
{
/**
* Kept in a DLL.
*/
struct WireFeeSet *next;
/**
* Kept in a DLL.
*/
struct WireFeeSet *prev;
/**
* Actual fees.
*/
struct TALER_WireFeeSet fees;
/**
* Start date of fee validity (inclusive).
*/
struct GNUNET_TIME_Timestamp start_date;
/**
* End date of fee validity (exclusive).
*/
struct GNUNET_TIME_Timestamp end_date;
/**
* Wire method the fees apply to.
*/
char *method;
};
/**
* State we keep per thread to cache the /wire response.
*/
struct WireStateHandle
{
/**
* Cached reply for /wire response.
*/
struct MHD_Response *wire_reply;
/**
* ETag for this response (if any).
*/
char *etag;
/**
* head of DLL of wire fees.
*/
struct WireFeeSet *wfs_head;
/**
* Tail of DLL of wire fees.
*/
struct WireFeeSet *wfs_tail;
/**
* Earliest timestamp of all the wire methods when we have no more fees.
*/
struct GNUNET_TIME_Absolute cache_expiration;
/**
* @e cache_expiration time, formatted.
*/
char dat[128];
/**
* For which (global) wire_generation was this data structure created?
* Used to check when we are outdated and need to be re-generated.
*/
uint64_t wire_generation;
/**
* HTTP status to return with this response.
*/
unsigned int http_status;
};
/**
* Stores the latest generation of our wire response.
*/
static struct WireStateHandle *wire_state;
/**
* Handler listening for wire updates by other exchange
* services.
*/
static struct GNUNET_DB_EventHandler *wire_eh;
/**
* Counter incremented whenever we have a reason to re-build the #wire_state
* because something external changed.
*/
static uint64_t wire_generation;
/**
* Free memory associated with @a wsh
*
* @param[in] wsh wire state to destroy
*/
static void
destroy_wire_state (struct WireStateHandle *wsh)
{
struct WireFeeSet *wfs;
while (NULL != (wfs = wsh->wfs_head))
{
GNUNET_CONTAINER_DLL_remove (wsh->wfs_head,
wsh->wfs_tail,
wfs);
GNUNET_free (wfs->method);
GNUNET_free (wfs);
}
MHD_destroy_response (wsh->wire_reply);
GNUNET_free (wsh->etag);
GNUNET_free (wsh);
}
/**
* Function called whenever another exchange process has updated
* the wire data in the database.
*
* @param cls NULL
* @param extra unused
* @param extra_size number of bytes in @a extra unused
*/
static void
wire_update_event_cb (void *cls,
const void *extra,
size_t extra_size)
{
(void) cls;
(void) extra;
(void) extra_size;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received /wire update event\n");
TEH_check_invariants ();
wire_generation++;
}
enum GNUNET_GenericReturnValue
TEH_wire_init ()
{
struct GNUNET_DB_EventHeaderP es = {
.size = htons (sizeof (es)),
.type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
};
wire_eh = TEH_plugin->event_listen (TEH_plugin->cls,
GNUNET_TIME_UNIT_FOREVER_REL,
&es,
&wire_update_event_cb,
NULL);
if (NULL == wire_eh)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
void
TEH_wire_done ()
{
if (NULL != wire_state)
{
destroy_wire_state (wire_state);
wire_state = NULL;
}
if (NULL != wire_eh)
{
TEH_plugin->event_listen_cancel (TEH_plugin->cls,
wire_eh);
wire_eh = NULL;
}
}
/**
* Add information about a wire account to @a cls.
*
* @param cls a `json_t *` object to expand with wire account details
* @param payto_uri the exchange bank account URI to add
* @param conversion_url URL of a conversion service, NULL if there is no conversion
* @param debit_restrictions JSON array with debit restrictions on the account
* @param credit_restrictions JSON array with credit restrictions on the account
* @param master_sig master key signature affirming that this is a bank
* account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS)
*/
static void
add_wire_account (void *cls,
const char *payto_uri,
const char *conversion_url,
const json_t *debit_restrictions,
const json_t *credit_restrictions,
const struct TALER_MasterSignatureP *master_sig)
{
json_t *a = cls;
if (0 !=
json_array_append_new (
a,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("payto_uri",
payto_uri),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("conversion_url",
conversion_url)),
GNUNET_JSON_pack_array_incref ("debit_restrictions",
(json_t *) debit_restrictions),
GNUNET_JSON_pack_array_incref ("credit_restrictions",
(json_t *) credit_restrictions),
GNUNET_JSON_pack_data_auto ("master_sig",
master_sig))))
{
GNUNET_break (0); /* out of memory!? */
return;
}
}
/**
* Closure for #add_wire_fee().
*/
struct AddContext
{
/**
* Wire method the fees are for.
*/
char *wire_method;
/**
* Wire state we are building.
*/
struct WireStateHandle *wsh;
/**
* Array to append the fee to.
*/
json_t *a;
/**
* Context we hash "everything" we add into. This is used
* to compute the etag. Technically, we only hash the
* master_sigs, as they imply the rest.
*/
struct GNUNET_HashContext *hc;
/**
* Set to the maximum end-date seen.
*/
struct GNUNET_TIME_Absolute max_seen;
};
/**
* Add information about a wire account to @a cls.
*
* @param cls a `struct AddContext`
* @param fees the wire fees we charge
* @param start_date from when are these fees valid (start date)
* @param end_date until when are these fees valid (end date, exclusive)
* @param master_sig master key signature affirming that this is the correct
* fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES)
*/
static void
add_wire_fee (void *cls,
const struct TALER_WireFeeSet *fees,
struct GNUNET_TIME_Timestamp start_date,
struct GNUNET_TIME_Timestamp end_date,
const struct TALER_MasterSignatureP *master_sig)
{
struct AddContext *ac = cls;
struct WireFeeSet *wfs;
GNUNET_CRYPTO_hash_context_read (ac->hc,
master_sig,
sizeof (*master_sig));
ac->max_seen = GNUNET_TIME_absolute_max (ac->max_seen,
end_date.abs_time);
wfs = GNUNET_new (struct WireFeeSet);
wfs->start_date = start_date;
wfs->end_date = end_date;
wfs->fees = *fees;
wfs->method = GNUNET_strdup (ac->wire_method);
GNUNET_CONTAINER_DLL_insert (ac->wsh->wfs_head,
ac->wsh->wfs_tail,
wfs);
if (0 !=
json_array_append_new (
ac->a,
GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("wire_fee",
&fees->wire),
TALER_JSON_pack_amount ("closing_fee",
&fees->closing),
GNUNET_JSON_pack_timestamp ("start_date",
start_date),
GNUNET_JSON_pack_timestamp ("end_date",
end_date),
GNUNET_JSON_pack_data_auto ("sig",
master_sig))))
{
GNUNET_break (0); /* out of memory!? */
return;
}
}
/**
* Create the /wire response from our database state.
*
* @return NULL on error
*/
static struct WireStateHandle *
build_wire_state (void)
{
json_t *wire_accounts_array;
json_t *wire_fee_object;
uint64_t wg = wire_generation; /* must be obtained FIRST */
enum GNUNET_DB_QueryStatus qs;
struct WireStateHandle *wsh;
struct GNUNET_HashContext *hc;
wsh = GNUNET_new (struct WireStateHandle);
wsh->wire_generation = wg;
wire_accounts_array = json_array ();
GNUNET_assert (NULL != wire_accounts_array);
qs = TEH_plugin->get_wire_accounts (TEH_plugin->cls,
&add_wire_account,
wire_accounts_array);
if (0 > qs)
{
GNUNET_break (0);
json_decref (wire_accounts_array);
wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
wsh->wire_reply
= TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
"get_wire_accounts");
return wsh;
}
if (0 == json_array_size (wire_accounts_array))
{
json_decref (wire_accounts_array);
wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
wsh->wire_reply
= TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_NO_ACCOUNTS_CONFIGURED,
NULL);
return wsh;
}
wire_fee_object = json_object ();
GNUNET_assert (NULL != wire_fee_object);
wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS;
hc = GNUNET_CRYPTO_hash_context_start ();
{
json_t *account;
size_t index;
json_array_foreach (wire_accounts_array, index, account) {
char *wire_method;
const char *payto_uri = json_string_value (json_object_get (account,
"payto_uri"));
GNUNET_assert (NULL != payto_uri);
wire_method = TALER_payto_get_method (payto_uri);
if (NULL == wire_method)
{
wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
wsh->wire_reply
= TALER_MHD_make_error (
TALER_EC_EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED,
payto_uri);
json_decref (wire_accounts_array);
json_decref (wire_fee_object);
GNUNET_CRYPTO_hash_context_abort (hc);
return wsh;
}
if (NULL == json_object_get (wire_fee_object,
wire_method))
{
struct AddContext ac = {
.wire_method = wire_method,
.wsh = wsh,
.a = json_array (),
.hc = hc
};
GNUNET_assert (NULL != ac.a);
qs = TEH_plugin->get_wire_fees (TEH_plugin->cls,
wire_method,
&add_wire_fee,
&ac);
if (0 > qs)
{
GNUNET_break (0);
json_decref (ac.a);
json_decref (wire_fee_object);
json_decref (wire_accounts_array);
GNUNET_free (wire_method);
wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
wsh->wire_reply
= TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
"get_wire_fees");
GNUNET_CRYPTO_hash_context_abort (hc);
return wsh;
}
if (0 == json_array_size (ac.a))
{
json_decref (ac.a);
json_decref (wire_accounts_array);
json_decref (wire_fee_object);
wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
wsh->wire_reply
= TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED,
wire_method);
GNUNET_free (wire_method);
GNUNET_CRYPTO_hash_context_abort (hc);
return wsh;
}
wsh->cache_expiration = GNUNET_TIME_absolute_min (ac.max_seen,
wsh->cache_expiration);
GNUNET_assert (0 ==
json_object_set_new (wire_fee_object,
wire_method,
ac.a));
}
GNUNET_free (wire_method);
}
}
wsh->wire_reply = TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_array_steal ("accounts",
wire_accounts_array),
GNUNET_JSON_pack_array_steal ("wads", /* #7271 */
json_array ()),
GNUNET_JSON_pack_object_steal ("fees",
wire_fee_object),
GNUNET_JSON_pack_data_auto ("master_public_key",
&TEH_master_public_key));
{
struct GNUNET_TIME_Timestamp m;
m = GNUNET_TIME_absolute_to_timestamp (wsh->cache_expiration);
TALER_MHD_get_date_string (m.abs_time,
wsh->dat);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Setting 'Expires' header for '/wire' to '%s'\n",
wsh->dat);
GNUNET_break (MHD_YES ==
MHD_add_response_header (wsh->wire_reply,
MHD_HTTP_HEADER_EXPIRES,
wsh->dat));
}
/* Set cache control headers: our response varies depending on these headers */
GNUNET_break (MHD_YES ==
MHD_add_response_header (wsh->wire_reply,
MHD_HTTP_HEADER_VARY,
MHD_HTTP_HEADER_ACCEPT_ENCODING));
/* Information is always public, revalidate after 1 day */
GNUNET_break (MHD_YES ==
MHD_add_response_header (wsh->wire_reply,
MHD_HTTP_HEADER_CACHE_CONTROL,
"public,max-age=86400"));
{
struct GNUNET_HashCode h;
char etag[sizeof (h) * 2];
char *end;
GNUNET_CRYPTO_hash_context_finish (hc,
&h);
end = GNUNET_STRINGS_data_to_string (&h,
sizeof (h),
etag,
sizeof (etag));
*end = '\0';
wsh->etag = GNUNET_strdup (etag);
GNUNET_break (MHD_YES ==
MHD_add_response_header (wsh->wire_reply,
MHD_HTTP_HEADER_ETAG,
etag));
}
wsh->http_status = MHD_HTTP_OK;
return wsh;
}
void
TEH_wire_update_state (void)
{
struct GNUNET_DB_EventHeaderP es = {
.size = htons (sizeof (es)),
.type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED),
};
TEH_plugin->event_notify (TEH_plugin->cls,
&es,
NULL,
0);
wire_generation++;
}
/**
* Return the current key state for this thread. Possibly
* re-builds the key state if we have reason to believe
* that something changed.
*
* @return NULL on error
*/
struct WireStateHandle *
get_wire_state (void)
{
struct WireStateHandle *old_wsh;
old_wsh = wire_state;
if ( (NULL == old_wsh) ||
(old_wsh->wire_generation < wire_generation) )
{
struct WireStateHandle *wsh;
TEH_check_invariants ();
wsh = build_wire_state ();
wire_state = wsh;
if (NULL != old_wsh)
destroy_wire_state (old_wsh);
TEH_check_invariants ();
return wsh;
}
return old_wsh;
}
MHD_RESULT
TEH_handler_wire (struct TEH_RequestContext *rc,
const char *const args[])
{
struct WireStateHandle *wsh;
(void) args;
wsh = get_wire_state ();
if (NULL == wsh)
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
NULL);
{
const char *etag;
etag = MHD_lookup_connection_value (rc->connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_IF_NONE_MATCH);
if ( (NULL != etag) &&
(MHD_HTTP_OK == wsh->http_status) &&
(NULL != wsh->etag) &&
(0 == strcmp (etag,
wsh->etag)) )
{
MHD_RESULT ret;
struct MHD_Response *resp;
resp = MHD_create_response_from_buffer (0,
NULL,
MHD_RESPMEM_PERSISTENT);
TALER_MHD_add_global_headers (resp);
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
MHD_HTTP_HEADER_EXPIRES,
wsh->dat));
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
MHD_HTTP_HEADER_ETAG,
wsh->etag));
ret = MHD_queue_response (rc->connection,
MHD_HTTP_NOT_MODIFIED,
resp);
GNUNET_break (MHD_YES == ret);
MHD_destroy_response (resp);
return ret;
}
}
return MHD_queue_response (rc->connection,
wsh->http_status,
wsh->wire_reply);
}
const struct TALER_WireFeeSet *
TEH_wire_fees_by_time (
struct GNUNET_TIME_Timestamp ts,
const char *method)
{
struct WireStateHandle *wsh = get_wire_state ();
for (struct WireFeeSet *wfs = wsh->wfs_head;
NULL != wfs;
wfs = wfs->next)
{
if (0 != strcmp (method,
wfs->method))
continue;
if ( (GNUNET_TIME_timestamp_cmp (wfs->start_date,
>,
ts)) ||
(GNUNET_TIME_timestamp_cmp (ts,
>=,
wfs->end_date)) )
continue;
return &wfs->fees;
}
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"No wire fees for method `%s' at %s configured\n",
method,
GNUNET_TIME_timestamp2s (ts));
return NULL;
}
/* end of taler-exchange-httpd_wire.c */

View File

@ -1,84 +0,0 @@
/*
This file is part of TALER
Copyright (C) 2014--2021 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_wire.h
* @brief Handle /wire requests
* @author Christian Grothoff
*/
#ifndef TALER_EXCHANGE_HTTPD_WIRE_H
#define TALER_EXCHANGE_HTTPD_WIRE_H
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
* Clean up wire subsystem.
*/
void
TEH_wire_done (void);
/**
* Look up wire fee structure by @a ts.
*
* @param ts timestamp to lookup wire fees at
* @param method wire method to lookup fees for
* @return the wire fee details, or
* NULL if none are configured for @a ts and @a method
*/
const struct TALER_WireFeeSet *
TEH_wire_fees_by_time (
struct GNUNET_TIME_Timestamp ts,
const char *method);
/**
* Initialize wire subsystem.
*
* @return #GNUNET_OK on success
*/
enum GNUNET_GenericReturnValue
TEH_wire_init (void);
/**
* Something changed in the database. Rebuild the wire replies. This function
* should be called if the exchange learns about a new signature from our
* master key.
*
* (We do not do so immediately, but merely signal to all threads that they
* need to rebuild their wire state upon the next call to
* #TEH_handler_wire()).
*/
void
TEH_wire_update_state (void);
/**
* Handle a "/wire" request.
*
* @param rc request context
* @param args array of additional options (must be empty for this function)
* @return MHD result code
*/
MHD_RESULT
TEH_handler_wire (struct TEH_RequestContext *rc,
const char *const args[]);
#endif

View File

@ -120,6 +120,13 @@ struct TALER_EXCHANGE_DenomPublicKey
*/
struct TALER_DenomFeeSet fees;
/**
* Set to true if the private denomination key has been
* lost by the exchange and thus the key cannot be
* used for withdrawing at this time.
*/
bool lost;
/**
* Set to true if this denomination key has been
* revoked by the exchange.
@ -229,6 +236,173 @@ struct TALER_EXCHANGE_GlobalFee
};
/**
* List sorted by @a start_date with fees to be paid for aggregate wire transfers.
*/
struct TALER_EXCHANGE_WireAggregateFees
{
/**
* This is a linked list.
*/
struct TALER_EXCHANGE_WireAggregateFees *next;
/**
* Fee to be paid whenever the exchange wires funds to the merchant.
*/
struct TALER_WireFeeSet fees;
/**
* Time when this fee goes into effect (inclusive)
*/
struct GNUNET_TIME_Timestamp start_date;
/**
* Time when this fee stops being in effect (exclusive).
*/
struct GNUNET_TIME_Timestamp end_date;
/**
* Signature affirming the above fee structure.
*/
struct TALER_MasterSignatureP master_sig;
};
/**
* Information about wire fees by wire method.
*/
struct TALER_EXCHANGE_WireFeesByMethod
{
/**
* Wire method with the given @e fees.
*/
char *method;
/**
* Linked list of wire fees the exchange charges for
* accounts of the wire @e method.
*/
struct TALER_EXCHANGE_WireAggregateFees *fees_head;
};
/**
* Type of an account restriction.
*/
enum TALER_EXCHANGE_AccountRestrictionType
{
/**
* Invalid restriction.
*/
TALER_EXCHANGE_AR_INVALID = 0,
/**
* Account must not be used for this operation.
*/
TALER_EXCHANGE_AR_DENY = 1,
/**
* Other account must match given regular expression.
*/
TALER_EXCHANGE_AR_REGEX = 2
};
/**
* Restrictions that apply to using a given exchange bank account.
*/
struct TALER_EXCHANGE_AccountRestriction
{
/**
* Type of the account restriction.
*/
enum TALER_EXCHANGE_AccountRestrictionType type;
/**
* Restriction details depending on @e type.
*/
union
{
/**
* Details if type is #TALER_EXCHANGE_AR_REGEX.
*/
struct
{
/**
* Regular expression that the payto://-URI of the partner account must
* follow. The regular expression should follow posix-egrep, but
* without support for character classes, GNU extensions,
* back-references or intervals. See
* https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html
* for a description of the posix-egrep syntax. Applications may support
* regexes with additional features, but exchanges must not use such
* regexes.
*/
char *posix_egrep;
/**
* Hint for a human to understand the restriction.
*/
char *human_hint;
/**
* Internationalizations for the @e human_hint. Map from IETF BCP 47
* language tax to localized human hints.
*/
json_t *human_hint_i18n;
} regex;
} details;
};
/**
* Information about a wire account of the exchange.
*/
struct TALER_EXCHANGE_WireAccount
{
/**
* payto://-URI of the exchange.
*/
char *payto_uri;
/**
* URL of a conversion service in case using this account is subject to
* currency conversion. NULL for no conversion needed.
*/
char *conversion_url;
/**
* Array of restrictions that apply when crediting
* this account.
*/
struct TALER_EXCHANGE_AccountRestriction *credit_restrictions;
/**
* Array of restrictions that apply when debiting
* this account.
*/
struct TALER_EXCHANGE_AccountRestriction *debit_restrictions;
/**
* Length of the @e credit_restrictions array.
*/
unsigned int credit_restrictions_length;
/**
* Length of the @e debit_restrictions array.
*/
unsigned int debit_restrictions_length;
/**
* Signature of the exchange over the account (was checked by the API).
*/
struct TALER_MasterSignatureP master_sig;
};
/**
* @brief Information about keys from the exchange.
*/
@ -303,6 +477,16 @@ struct TALER_EXCHANGE_Keys
*/
struct TALER_Amount *wallet_balance_limit_without_kyc;
/**
* Array of accounts of the exchange.
*/
struct TALER_EXCHANGE_WireAccount *accounts;
/**
* Array of wire fees by wire method.
*/
struct TALER_EXCHANGE_WireFeesByMethod *fees;
/**
* How long after a reserve went idle will the exchange close it?
* This is an approximate number, not cryptographically signed by
@ -332,6 +516,16 @@ struct TALER_EXCHANGE_Keys
*/
struct TALER_AgeMask age_mask;
/**
* Length of @e accounts array.
*/
unsigned int accounts_len;
/**
* Length of @e fees array.
*/
unsigned int fees_len;
/**
* Length of the @e wallet_balance_limit_without_kyc
* array.
@ -702,174 +896,7 @@ TALER_EXCHANGE_get_signing_key_info (
const struct TALER_ExchangePublicKeyP *exchange_pub);
/* ********************* /wire *********************** */
/**
* List sorted by @a start_date with fees to be paid for aggregate wire transfers.
*/
struct TALER_EXCHANGE_WireAggregateFees
{
/**
* This is a linked list.
*/
struct TALER_EXCHANGE_WireAggregateFees *next;
/**
* Fee to be paid whenever the exchange wires funds to the merchant.
*/
struct TALER_WireFeeSet fees;
/**
* Time when this fee goes into effect (inclusive)
*/
struct GNUNET_TIME_Timestamp start_date;
/**
* Time when this fee stops being in effect (exclusive).
*/
struct GNUNET_TIME_Timestamp end_date;
/**
* Signature affirming the above fee structure.
*/
struct TALER_MasterSignatureP master_sig;
};
/**
* Information about wire fees by wire method.
*/
struct TALER_EXCHANGE_WireFeesByMethod
{
/**
* Wire method with the given @e fees.
*/
const char *method;
/**
* Linked list of wire fees the exchange charges for
* accounts of the wire @e method.
*/
struct TALER_EXCHANGE_WireAggregateFees *fees_head;
};
/**
* Type of an account restriction.
*/
enum TALER_EXCHANGE_AccountRestrictionType
{
/**
* Invalid restriction.
*/
TALER_EXCHANGE_AR_INVALID = 0,
/**
* Account must not be used for this operation.
*/
TALER_EXCHANGE_AR_DENY = 1,
/**
* Other account must match given regular expression.
*/
TALER_EXCHANGE_AR_REGEX = 2
};
/**
* Restrictions that apply to using a given exchange bank account.
*/
struct TALER_EXCHANGE_AccountRestriction
{
/**
* Type of the account restriction.
*/
enum TALER_EXCHANGE_AccountRestrictionType type;
/**
* Restriction details depending on @e type.
*/
union
{
/**
* Details if type is #TALER_EXCHANGE_AR_REGEX.
*/
struct
{
/**
* Regular expression that the payto://-URI of the partner account must
* follow. The regular expression should follow posix-egrep, but
* without support for character classes, GNU extensions,
* back-references or intervals. See
* https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html
* for a description of the posix-egrep syntax. Applications may support
* regexes with additional features, but exchanges must not use such
* regexes.
*/
const char *posix_egrep;
/**
* Hint for a human to understand the restriction.
*/
const char *human_hint;
/**
* Internationalizations for the @e human_hint. Map from IETF BCP 47
* language tax to localized human hints.
*/
const json_t *human_hint_i18n;
} regex;
} details;
};
/**
* Information about a wire account of the exchange.
*/
struct TALER_EXCHANGE_WireAccount
{
/**
* payto://-URI of the exchange.
*/
const char *payto_uri;
/**
* URL of a conversion service in case using this account is subject to
* currency conversion. NULL for no conversion needed.
*/
const char *conversion_url;
/**
* Array of restrictions that apply when crediting
* this account.
*/
struct TALER_EXCHANGE_AccountRestriction *credit_restrictions;
/**
* Array of restrictions that apply when debiting
* this account.
*/
struct TALER_EXCHANGE_AccountRestriction *debit_restrictions;
/**
* Length of the @e credit_restrictions array.
*/
unsigned int credit_restrictions_length;
/**
* Length of the @e debit_restrictions array.
*/
unsigned int debit_restrictions_length;
/**
* Signature of the exchange over the account (was checked by the API).
*/
struct TALER_MasterSignatureP master_sig;
};
/* ********************* wire helpers *********************** */
/**
@ -901,116 +928,6 @@ TALER_EXCHANGE_free_accounts (
struct TALER_EXCHANGE_WireAccount was[static was_len]);
/**
* Response to a /wire request.
*/
struct TALER_EXCHANGE_WireResponse
{
/**
* HTTP response details.
*/
struct TALER_EXCHANGE_HttpResponse hr;
/**
* Response details depending on status.
*/
union
{
/**
* Details for #MHD_HTTP_OK.
*/
struct
{
/**
* Array of accounts of the exchange.
*/
const struct TALER_EXCHANGE_WireAccount *accounts;
/**
* Array of wire fees by wire method.
*/
const struct TALER_EXCHANGE_WireFeesByMethod *fees;
/**
* Length of @e accounts array.
*/
unsigned int accounts_len;
/**
* Length of @e fees array.
*/
unsigned int fees_len;
} ok;
} details;
};
/**
* Callbacks of this type are used to serve the result of submitting a wire
* format inquiry request to a exchange.
*
* If the request fails to generate a valid response from the
* exchange, the http_status will also be zero.
*
* @param cls closure
* @param wr response data
*/
typedef void
(*TALER_EXCHANGE_WireCallback) (
void *cls,
const struct TALER_EXCHANGE_WireResponse *wr);
/**
* @brief A Wire format inquiry handle
*/
struct TALER_EXCHANGE_WireHandle;
/**
* Obtain information about a exchange's wire instructions. A
* exchange may provide wire instructions for creating a reserve. The
* wire instructions also indicate which wire formats merchants may
* use with the exchange. This API is typically used by a wallet for
* wiring funds, and possibly by a merchant to determine supported
* wire formats.
*
* Note that while we return the (main) response verbatim to the
* caller for further processing, we do already verify that the
* response is well-formed (i.e. that signatures included in the
* response are all valid). If the exchange's reply is not
* well-formed, we return an HTTP status code of zero to @a cb.
*
* @param ctx curl context
* @param url exchange base URL
* @param keys the keys of the exchange
* @param wire_cb the callback to call when a reply for this request is available
* @param wire_cb_cls closure for the above callback
* @return a handle for this request
*/
struct TALER_EXCHANGE_WireHandle *
TALER_EXCHANGE_wire (
struct GNUNET_CURL_Context *ctx,
const char *url,
struct TALER_EXCHANGE_Keys *keys,
TALER_EXCHANGE_WireCallback wire_cb,
void *wire_cb_cls);
/**
* Cancel a wire information request. This function cannot be used
* on a request handle if a response is already served for it.
*
* @param wh the wire information request handle
*/
void
TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh);
/* ********************* /coins/$COIN_PUB/deposit *********************** */

View File

@ -1174,24 +1174,6 @@ struct TALER_TESTING_Command
TALER_TESTING_cmd_withdraw_with_retry (struct TALER_TESTING_Command cmd);
/**
* Create a "wire" command.
*
* @param label the command label.
* @param expected_method which wire-transfer method is expected
* to be offered by the exchange.
* @param expected_fee the fee the exchange should charge.
* @param expected_response_code the HTTP response the exchange
* should return.
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_wire (const char *label,
const char *expected_method,
const char *expected_fee,
unsigned int expected_response_code);
/**
* Create a GET "reserves" command.
*

View File

@ -76,8 +76,7 @@ libtalerexchange_la_SOURCES = \
exchange_api_reserves_status.c \
exchange_api_transfers_get.c \
exchange_api_withdraw.c \
exchange_api_withdraw2.c \
exchange_api_wire.c
exchange_api_withdraw2.c
libtalerexchange_la_LIBADD = \
libtalerauditor.la \
$(top_builddir)/src/json/libtalerjson.la \

View File

@ -2241,15 +2241,17 @@ parse_restrictions (const json_t *jresta,
if (0 == strcmp (type,
"regex"))
{
const char *regex;
const char *hint;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string (
"payto_regex",
&ar->details.regex.posix_egrep),
&regex),
GNUNET_JSON_spec_string (
"human_hint",
&ar->details.regex.human_hint),
&hint),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_object_const (
GNUNET_JSON_spec_json (
"human_hint_i18n",
&ar->details.regex.human_hint_i18n),
NULL),
@ -2266,6 +2268,8 @@ parse_restrictions (const json_t *jresta,
goto fail;
}
ar->type = TALER_EXCHANGE_AR_REGEX;
ar->details.regex.posix_egrep = GNUNET_strdup (regex);
ar->details.regex.human_hint = GNUNET_strdup (hint);
continue;
}
/* unsupported type */
@ -2297,14 +2301,16 @@ TALER_EXCHANGE_parse_accounts (
i++)
{
struct TALER_EXCHANGE_WireAccount *wa = &was[i];
const char *payto_uri;
const char *conversion_url;
const json_t *credit_restrictions;
const json_t *debit_restrictions;
struct GNUNET_JSON_Specification spec_account[] = {
GNUNET_JSON_spec_string ("payto_uri",
&wa->payto_uri),
&payto_uri),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("conversion_url",
&wa->conversion_url),
&conversion_url),
NULL),
GNUNET_JSON_spec_array_const ("credit_restrictions",
&credit_restrictions),
@ -2330,7 +2336,7 @@ TALER_EXCHANGE_parse_accounts (
{
char *err;
err = TALER_payto_validate (wa->payto_uri);
err = TALER_payto_validate (payto_uri);
if (NULL != err)
{
GNUNET_break_op (0);
@ -2341,12 +2347,13 @@ TALER_EXCHANGE_parse_accounts (
if ( (NULL != master_pub) &&
(GNUNET_OK !=
TALER_exchange_wire_signature_check (wa->payto_uri,
wa->conversion_url,
debit_restrictions,
credit_restrictions,
master_pub,
&wa->master_sig)) )
TALER_exchange_wire_signature_check (
payto_uri,
conversion_url,
debit_restrictions,
credit_restrictions,
master_pub,
&wa->master_sig)) )
{
/* bogus reply */
GNUNET_break_op (0);
@ -2365,11 +2372,44 @@ TALER_EXCHANGE_parse_accounts (
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
wa->payto_uri = GNUNET_strdup (payto_uri);
if (NULL != conversion_url)
wa->conversion_url = GNUNET_strdup (conversion_url);
} /* end 'for all accounts */
return GNUNET_OK;
}
/**
* Free array of account restrictions.
*
* @param ar_len length of @a ar
* @param[in] ar array to free contents of (but not @a ar itself)
*/
static void
free_restrictions (unsigned int ar_len,
struct TALER_EXCHANGE_AccountRestriction ar[static ar_len])
{
for (unsigned int i = 0; i<ar_len; i++)
{
struct TALER_EXCHANGE_AccountRestriction *a = &ar[i];
switch (a->type)
{
case TALER_EXCHANGE_AR_INVALID:
GNUNET_break (0);
break;
case TALER_EXCHANGE_AR_DENY:
break;
case TALER_EXCHANGE_AR_REGEX:
GNUNET_free (ar->details.regex.posix_egrep);
GNUNET_free (ar->details.regex.human_hint);
json_decref (ar->details.regex.human_hint_i18n);
break;
}
}
}
void
TALER_EXCHANGE_free_accounts (
unsigned int was_len,
@ -2379,8 +2419,18 @@ TALER_EXCHANGE_free_accounts (
{
struct TALER_EXCHANGE_WireAccount *wa = &was[i];
GNUNET_free (wa->credit_restrictions);
GNUNET_free (wa->debit_restrictions);
GNUNET_free (wa->payto_uri);
GNUNET_free (wa->conversion_url);
free_restrictions (wa->credit_restrictions_length,
wa->credit_restrictions);
GNUNET_array_grow (wa->credit_restrictions,
wa->credit_restrictions_length,
0);
free_restrictions (wa->debit_restrictions_length,
wa->debit_restrictions);
GNUNET_array_grow (wa->debit_restrictions,
wa->debit_restrictions_length,
0);
}
}

View File

@ -40,7 +40,7 @@
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
#define EXCHANGE_PROTOCOL_CURRENT 15
#define EXCHANGE_PROTOCOL_CURRENT 16
/**
* How many versions are we backwards compatible with?
@ -123,6 +123,115 @@ struct TALER_EXCHANGE_GetKeysHandle
};
/**
* 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 TALER_EXCHANGE_WireFeesByMethod *wfmi = &wfm[i];
while (NULL != wfmi->fees_head)
{
struct TALER_EXCHANGE_WireAggregateFees *fe
= wfmi->fees_head;
wfmi->fees_head = fe->next;
GNUNET_free (fe);
}
GNUNET_free (wfmi->method);
}
GNUNET_free (wfm);
}
/**
* Parse wire @a fees and return array.
*
* @param master_pub master public key to use to check signatures
* @param fees json AggregateTransferFee to parse
* @param[out] fees_len set to length of returned array
* @return NULL on error
*/
static struct TALER_EXCHANGE_WireFeesByMethod *
parse_fees (const struct TALER_MasterPublicKeyP *master_pub,
const json_t *fees,
unsigned int *fees_len)
{
struct TALER_EXCHANGE_WireFeesByMethod *fbm;
unsigned int fbml = json_object_size (fees);
unsigned int i = 0;
const char *key;
const json_t *fee_array;
fbm = GNUNET_new_array (fbml,
struct TALER_EXCHANGE_WireFeesByMethod);
*fees_len = fbml;
json_object_foreach ((json_t *) fees, key, fee_array) {
struct TALER_EXCHANGE_WireFeesByMethod *fe = &fbm[i++];
unsigned int 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_any ("wire_fee",
&wa->fees.wire),
TALER_JSON_spec_amount_any ("closing_fee",
&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,
@ -246,6 +355,10 @@ parse_json_denomkey_partially (
&denom_key->valid_from),
GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
&denom_key->expire_legal),
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),
@ -537,6 +650,9 @@ decode_keys_json (const json_t *resp_obj,
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;
if (JSON_OBJECT != json_typeof (resp_obj))
{
@ -608,6 +724,12 @@ decode_keys_json (const json_t *resp_obj,
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),
@ -736,6 +858,26 @@ decode_keys_json (const json_t *resp_obj,
}
}
/* Parse wire accounts */
key_data->fees = parse_fees (&key_data->master_pub,
fees,
&key_data->fees_len);
EXITIF (NULL == key_data->fees);
/* parse accounts */
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",
(unsigned int) json_array_size (accounts));
/* Parse the supported extension(s): age-restriction. */
/* TODO: maybe lift all this into a FP in TALER_Extension ? */
if (! no_extensions)
@ -1542,6 +1684,13 @@ TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys)
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->wallet_balance_limit_without_kyc);
GNUNET_free (keys->version);
@ -1684,6 +1833,66 @@ add_grp (void *cls,
}
/**
* 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])
{
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 (
"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_keys_to_json (const struct TALER_EXCHANGE_Keys *kd)
{
@ -1693,16 +1902,14 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd)
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 ();
if (NULL == signkeys)
{
GNUNET_break (0);
return NULL;
}
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];
@ -1723,28 +1930,14 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd)
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;
}
}
denominations_by_group = json_array ();
if (NULL == denominations_by_group)
{
GNUNET_break (0);
json_decref (signkeys);
return NULL;
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;
@ -1904,6 +2097,82 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd)
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_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++)
@ -1949,6 +2218,12 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd)
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 (

View File

@ -1,392 +0,0 @@
/*
This file is part of TALER
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 by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see
<http://www.gnu.org/licenses/>
*/
/**
* @file lib/exchange_api_wire.c
* @brief Implementation of the /wire request of the exchange's HTTP API
* @author Christian Grothoff
*/
#include "platform.h"
#include <jansson.h>
#include <microhttpd.h> /* just for HTTP status codes */
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler_exchange_service.h"
#include "taler_json_lib.h"
#include "taler_signatures.h"
#include "exchange_api_handle.h"
#include "exchange_api_curl_defaults.h"
/**
* @brief A Wire Handle
*/
struct TALER_EXCHANGE_WireHandle
{
/**
* The keys of the exchange this request handle will use
*/
struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
*/
char *url;
/**
* Handle for the request.
*/
struct GNUNET_CURL_Job *job;
/**
* Function to call with the result.
*/
TALER_EXCHANGE_WireCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
};
/**
* 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 TALER_EXCHANGE_WireFeesByMethod *wfmi = &wfm[i];
while (NULL != wfmi->fees_head)
{
struct TALER_EXCHANGE_WireAggregateFees *fe
= wfmi->fees_head;
wfmi->fees_head = fe->next;
GNUNET_free (fe);
}
}
GNUNET_free (wfm);
}
/**
* Parse wire @a fees and return array.
*
* @param master_pub master public key to use to check signatures
* @param fees json AggregateTransferFee to parse
* @param[out] fees_len set to length of returned array
* @return NULL on error
*/
static struct TALER_EXCHANGE_WireFeesByMethod *
parse_fees (const struct TALER_MasterPublicKeyP *master_pub,
const json_t *fees,
unsigned int *fees_len)
{
struct TALER_EXCHANGE_WireFeesByMethod *fbm;
unsigned int fbml = json_object_size (fees);
unsigned int i = 0;
const char *key;
const json_t *fee_array;
fbm = GNUNET_new_array (fbml,
struct TALER_EXCHANGE_WireFeesByMethod);
*fees_len = fbml;
json_object_foreach ((json_t *) fees, key, fee_array) {
struct TALER_EXCHANGE_WireFeesByMethod *fe = &fbm[i++];
unsigned int idx;
json_t *fee;
fe->method = 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_any ("wire_fee",
&wa->fees.wire),
TALER_JSON_spec_amount_any ("closing_fee",
&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;
}
/**
* Function called when we're done processing the
* HTTP /wire request.
*
* @param cls the `struct TALER_EXCHANGE_WireHandle`
* @param response_code HTTP response code, 0 on error
* @param response parsed JSON result, NULL on error
*/
static void
handle_wire_finished (void *cls,
long response_code,
const void *response)
{
struct TALER_EXCHANGE_WireHandle *wh = cls;
const json_t *j = response;
struct TALER_EXCHANGE_WireResponse wr = {
.hr.reply = j,
.hr.http_status = (unsigned int) response_code
};
TALER_LOG_DEBUG ("Checking raw /wire response\n");
wh->job = NULL;
switch (response_code)
{
case 0:
wr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
{
const json_t *accounts;
const json_t *fees;
const json_t *wads;
struct TALER_EXCHANGE_WireFeesByMethod *fbm;
struct TALER_MasterPublicKeyP master_pub;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("master_public_key",
&master_pub),
GNUNET_JSON_spec_array_const ("accounts",
&accounts),
GNUNET_JSON_spec_object_const ("fees",
&fees),
GNUNET_JSON_spec_array_const ("wads",
&wads),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
spec,
NULL, NULL))
{
/* bogus reply */
GNUNET_break_op (0);
wr.hr.http_status = 0;
wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
if (0 != GNUNET_memcmp (&wh->keys->master_pub,
&master_pub))
{
/* bogus reply: master public key in /wire differs from that in /keys */
GNUNET_break_op (0);
wr.hr.http_status = 0;
wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
wr.details.ok.accounts_len
= json_array_size (accounts);
if (0 == wr.details.ok.accounts_len)
{
/* bogus reply */
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
wr.hr.http_status = 0;
wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
fbm = parse_fees (&master_pub,
fees,
&wr.details.ok.fees_len);
wr.details.ok.fees = fbm;
if (NULL == fbm)
{
/* bogus reply */
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
wr.hr.http_status = 0;
wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
/* parse accounts */
{
struct TALER_EXCHANGE_WireAccount was[wr.details.ok.accounts_len];
wr.details.ok.accounts = was;
if (GNUNET_OK !=
TALER_EXCHANGE_parse_accounts (&master_pub,
accounts,
wr.details.ok.accounts_len,
was))
{
GNUNET_break_op (0);
wr.hr.http_status = 0;
wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
}
else if (NULL != wh->cb)
{
wh->cb (wh->cb_cls,
&wr);
wh->cb = NULL;
}
TALER_EXCHANGE_free_accounts (
wr.details.ok.accounts_len,
was);
} /* end of 'parse accounts */
free_fees (fbm,
wr.details.ok.fees_len);
GNUNET_JSON_parse_free (spec);
} /* end of MHD_HTTP_OK */
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */
wr.hr.ec = TALER_JSON_get_error_code (j);
wr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
wr.hr.ec = TALER_JSON_get_error_code (j);
wr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
wr.hr.ec = TALER_JSON_get_error_code (j);
wr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
GNUNET_break_op (0);
wr.hr.ec = TALER_JSON_get_error_code (j);
wr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for exchange wire\n",
(unsigned int) response_code,
(int) wr.hr.ec);
break;
}
if (NULL != wh->cb)
wh->cb (wh->cb_cls,
&wr);
TALER_EXCHANGE_wire_cancel (wh);
}
struct TALER_EXCHANGE_WireHandle *
TALER_EXCHANGE_wire (
struct GNUNET_CURL_Context *ctx,
const char *url,
struct TALER_EXCHANGE_Keys *keys,
TALER_EXCHANGE_WireCallback wire_cb,
void *wire_cb_cls)
{
struct TALER_EXCHANGE_WireHandle *wh;
CURL *eh;
wh = GNUNET_new (struct TALER_EXCHANGE_WireHandle);
wh->cb = wire_cb;
wh->cb_cls = wire_cb_cls;
wh->url = TALER_url_join (url,
"wire",
NULL);
if (NULL == wh->url)
{
GNUNET_free (wh);
return NULL;
}
eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
if (NULL == eh)
{
GNUNET_break (0);
GNUNET_free (wh->url);
GNUNET_free (wh);
return NULL;
}
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_TIMEOUT,
60 /* seconds */));
wh->keys = TALER_EXCHANGE_keys_incref (keys);
wh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
eh,
&handle_wire_finished,
wh);
return wh;
}
void
TALER_EXCHANGE_wire_cancel (
struct TALER_EXCHANGE_WireHandle *wh)
{
if (NULL != wh->job)
{
GNUNET_CURL_job_cancel (wh->job);
wh->job = NULL;
}
GNUNET_free (wh->url);
TALER_EXCHANGE_keys_decref (wh->keys);
GNUNET_free (wh);
}
/* end of exchange_api_wire.c */

View File

@ -162,8 +162,8 @@ TALER_MHD_daemon_trigger (void)
if (NULL != mhd_task)
{
GNUNET_SCHEDULER_cancel (mhd_task);
mhd_task = NULL;
run_daemon (NULL);
mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon,
NULL);
}
else
{

View File

@ -113,7 +113,6 @@ libtalertesting_la_SOURCES = \
testing_api_cmd_take_aml_decision.c \
testing_api_cmd_transfer_get.c \
testing_api_cmd_wait.c \
testing_api_cmd_wire.c \
testing_api_cmd_wire_add.c \
testing_api_cmd_wire_del.c \
testing_api_cmd_withdraw.c \

View File

@ -106,21 +106,6 @@ static void
run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
/**
* Checks made against /wire response.
*/
struct TALER_TESTING_Command wire[] = {
/**
* Check if 'x-taler-bank' wire method is offered
* by the exchange.
*/
TALER_TESTING_cmd_wire ("wire-taler-bank-1",
"x-taler-bank",
NULL,
MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};
/**
* Test withdrawal plus spending.
*/
@ -1238,8 +1223,6 @@ run (void *cls,
NULL,
true,
true),
TALER_TESTING_cmd_batch ("wire",
wire),
TALER_TESTING_cmd_batch ("withdraw",
withdraw),
TALER_TESTING_cmd_batch ("spend",

View File

@ -80,13 +80,6 @@ run (void *cls,
"get-exchange-1",
true,
true),
/**
* Use one of the deserialized keys.
*/
TALER_TESTING_cmd_wire ("wire-with-serialized-keys",
"x-taler-bank",
NULL,
MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};