Compare commits

...

8 Commits

Author SHA1 Message Date
ecea165db7
[age-withdraw] age-withdraw-reveal lib-API mostly finished 2023-07-15 09:39:01 +02:00
63efa1f135
Merge branch 'age-withdraw' of ssh://git.kesim.org/taler/exchange into age-withdraw 2023-07-14 10:02:01 +02:00
34f44ccb27
Merge branch 'master' into age-withdraw 2023-07-14 09:25:43 +02:00
Christian Grothoff
de24415e17
-avoid deep recursion issues 2023-07-14 05:27:14 +02:00
Christian Grothoff
e08fe4eff8
bump protocol version to 16 2023-07-13 23:11:10 +02:00
Christian Grothoff
b60b339ee4
merging /keys and /wire 2023-07-13 23:07:33 +02:00
Christian Grothoff
a5451527cb
implement 'lost' field for #7883 2023-07-11 20:36:52 +02:00
Christian Grothoff
e984dbd8f4
merge /wire into /keys response 2023-07-11 20:01:44 +02:00
25 changed files with 1496 additions and 1571 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_responses.c taler-exchange-httpd_responses.h \
taler-exchange-httpd_terms.c taler-exchange-httpd_terms.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_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_withdraw.c taler-exchange-httpd_withdraw.h
taler_exchange_httpd_LDADD = \ taler_exchange_httpd_LDADD = \

View File

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

View File

@ -41,7 +41,7 @@
* *
* Returned via both /config and /keys endpoints. * 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; 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. * Stores the latest generation of our key state.
*/ */
@ -465,6 +576,545 @@ static struct TALER_SecurityModulePublicKeyP esign_sm_pub;
static bool terminating; 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. * Function called to forcefully resume suspended keys requests.
* *
@ -1673,6 +2323,7 @@ add_denom_key_cb (void *cls,
*/ */
static enum GNUNET_GenericReturnValue static enum GNUNET_GenericReturnValue
setup_general_response_headers (struct TEH_KeyStateHandle *ksh, setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
struct WireStateHandle *wsh,
struct MHD_Response *response) struct MHD_Response *response)
{ {
char dat[128]; char dat[128];
@ -1692,12 +2343,17 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
{ {
struct GNUNET_TIME_Relative r; struct GNUNET_TIME_Relative r;
struct GNUNET_TIME_Absolute a; struct GNUNET_TIME_Absolute a;
struct GNUNET_TIME_Timestamp km;
struct GNUNET_TIME_Timestamp m; struct GNUNET_TIME_Timestamp m;
struct GNUNET_TIME_Timestamp we;
r = GNUNET_TIME_relative_min (TEH_max_keys_caching, r = GNUNET_TIME_relative_min (TEH_max_keys_caching,
ksh->rekey_frequency); ksh->rekey_frequency);
a = GNUNET_TIME_relative_to_absolute (r); 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, TALER_MHD_get_date_string (m.abs_time,
dat); dat);
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@ -1777,8 +2433,10 @@ create_krd (struct TEH_KeyStateHandle *ksh,
struct TALER_ExchangeSignatureP exchange_sig; struct TALER_ExchangeSignatureP exchange_sig;
struct TALER_ExchangePublicKeyP grouped_exchange_pub; struct TALER_ExchangePublicKeyP grouped_exchange_pub;
struct TALER_ExchangeSignatureP grouped_exchange_sig; struct TALER_ExchangeSignatureP grouped_exchange_sig;
struct WireStateHandle *wsh;
json_t *keys; json_t *keys;
wsh = get_wire_state ();
GNUNET_assert (! GNUNET_TIME_absolute_is_zero ( GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
last_cherry_pick_date.abs_time)); last_cherry_pick_date.abs_time));
GNUNET_assert (NULL != signkeys); GNUNET_assert (NULL != signkeys);
@ -1850,6 +2508,12 @@ create_krd (struct TEH_KeyStateHandle *ksh,
ksh->signature_expires); 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 ( keys = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("version", GNUNET_JSON_pack_string ("version",
EXCHANGE_PROTOCOL_VERSION), EXCHANGE_PROTOCOL_VERSION),
@ -1874,6 +2538,15 @@ create_krd (struct TEH_KeyStateHandle *ksh,
recoup), recoup),
GNUNET_JSON_pack_array_incref ("denoms", GNUNET_JSON_pack_array_incref ("denoms",
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", GNUNET_JSON_pack_array_incref ("denominations",
grouped_denominations), grouped_denominations),
GNUNET_JSON_pack_array_incref ("auditors", GNUNET_JSON_pack_array_incref ("auditors",
@ -2010,6 +2683,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
GNUNET_assert (NULL != krd.response_uncompressed); GNUNET_assert (NULL != krd.response_uncompressed);
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
setup_general_response_headers (ksh, setup_general_response_headers (ksh,
wsh,
krd.response_uncompressed)); krd.response_uncompressed));
GNUNET_break (MHD_YES == GNUNET_break (MHD_YES ==
MHD_add_response_header (krd.response_uncompressed, MHD_add_response_header (krd.response_uncompressed,
@ -2032,7 +2706,18 @@ create_krd (struct TEH_KeyStateHandle *ksh,
"deflate")) ); "deflate")) );
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
setup_general_response_headers (ksh, setup_general_response_headers (ksh,
wsh,
krd.response_compressed)); 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 == GNUNET_break (MHD_YES ==
MHD_add_response_header (krd.response_compressed, MHD_add_response_header (krd.response_compressed,
MHD_HTTP_HEADER_ETAG, 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 /* Now that we have found/created the right group, add the
denomination to the list */ denomination to the list */
{ {
struct HelperDenomination *hd;
struct GNUNET_JSON_PackSpec key_spec; 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) switch (meta.cipher)
{ {
case TALER_DENOMINATION_RSA: case TALER_DENOMINATION_RSA:
@ -2314,6 +3009,12 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
entry = GNUNET_JSON_PACK ( entry = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("master_sig", GNUNET_JSON_pack_data_auto ("master_sig",
&dk->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", GNUNET_JSON_pack_timestamp ("stamp_start",
dk->meta.start), dk->meta.start),
GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw", GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
@ -2666,7 +3367,7 @@ keys_get_state (bool management_only)
if ( (old_ksh->key_generation < key_generation) || if ( (old_ksh->key_generation < key_generation) ||
(GNUNET_TIME_absolute_is_past (old_ksh->signature_expires.abs_time)) ) (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", "Rebuilding /keys, generation upgrade from %llu to %llu\n",
(unsigned long long) old_ksh->key_generation, (unsigned long long) old_ksh->key_generation,
(unsigned long long) 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; struct GNUNET_TIME_Timestamp last_issue_date;
const char *etag; const char *etag;
struct WireStateHandle *wsh;
wsh = get_wire_state ();
etag = MHD_lookup_connection_value (rc->connection, etag = MHD_lookup_connection_value (rc->connection,
MHD_HEADER_KIND, MHD_HEADER_KIND,
MHD_HTTP_HEADER_IF_NONE_MATCH); MHD_HTTP_HEADER_IF_NONE_MATCH);
@ -3293,6 +3996,7 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc,
TALER_MHD_add_global_headers (resp); TALER_MHD_add_global_headers (resp);
GNUNET_break (GNUNET_OK == GNUNET_break (GNUNET_OK ==
setup_general_response_headers (ksh, setup_general_response_headers (ksh,
wsh,
resp)); resp));
GNUNET_break (MHD_YES == GNUNET_break (MHD_YES ==
MHD_add_response_header (resp, MHD_add_response_header (resp,

View File

@ -154,6 +154,60 @@ struct TEH_KeyStateHandle;
void void
TEH_check_invariants (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 * 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_mhd_lib.h"
#include "taler-exchange-httpd_management.h" #include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.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_signatures.h"
#include "taler-exchange-httpd_management.h" #include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.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_signatures.h"
#include "taler-exchange-httpd_management.h" #include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.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-exchange-httpd_responses.h"
#include "taler_exchangedb_lib.h" #include "taler_exchangedb_lib.h"
#include "taler-exchange-httpd_keys.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_mhd_lib.h"
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_dbevents.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_reserves_close.h"
#include "taler-exchange-httpd_responses.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; 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 * Set to true if this denomination key has been
* revoked by the exchange. * 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. * @brief Information about keys from the exchange.
*/ */
@ -303,6 +477,16 @@ struct TALER_EXCHANGE_Keys
*/ */
struct TALER_Amount *wallet_balance_limit_without_kyc; 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? * How long after a reserve went idle will the exchange close it?
* This is an approximate number, not cryptographically signed by * This is an approximate number, not cryptographically signed by
@ -332,6 +516,16 @@ struct TALER_EXCHANGE_Keys
*/ */
struct TALER_AgeMask age_mask; 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 * Length of the @e wallet_balance_limit_without_kyc
* array. * array.
@ -702,174 +896,7 @@ TALER_EXCHANGE_get_signing_key_info (
const struct TALER_ExchangePublicKeyP *exchange_pub); const struct TALER_ExchangePublicKeyP *exchange_pub);
/* ********************* /wire *********************** */ /* ********************* wire helpers *********************** */
/**
* 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;
};
/** /**
@ -901,116 +928,6 @@ TALER_EXCHANGE_free_accounts (
struct TALER_EXCHANGE_WireAccount was[static was_len]); 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 *********************** */ /* ********************* /coins/$COIN_PUB/deposit *********************** */
@ -2944,6 +2861,7 @@ typedef void
* @param alg_values The algorithm specific parameters per coin, from the result to the previous call to /age-withdraw * @param alg_values The algorithm specific parameters per coin, from the result to the previous call to /age-withdraw
* @param noreveal_index The index into each of the kappa coin candidates, that should not be revealed to the exchange * @param noreveal_index The index into each of the kappa coin candidates, that should not be revealed to the exchange
* @param h_commitment The commmitment from the previous call to /age-withdraw * @param h_commitment The commmitment from the previous call to /age-withdraw
* @param max_age maximum age, as used in the to /age-withdraw
* @param res_cb A callback for the result, maybe NULL * @param res_cb A callback for the result, maybe NULL
* @param res_cb_cls A closure for @e res_cb, maybe NULL * @param res_cb_cls A closure for @e res_cb, maybe NULL
* @return a handle for this request; NULL if the argument was invalid. * @return a handle for this request; NULL if the argument was invalid.
@ -2959,6 +2877,7 @@ TALER_EXCHANGE_age_withdraw_reveal (
const struct TALER_ExchangeWithdrawValues alg_values[static num_coins], const struct TALER_ExchangeWithdrawValues alg_values[static num_coins],
uint8_t noreveal_index, uint8_t noreveal_index,
const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
uint8_t max_age,
TALER_EXCHANGE_AgeWithdrawRevealCallback res_cb, TALER_EXCHANGE_AgeWithdrawRevealCallback res_cb,
void *res_cb_cls); void *res_cb_cls);

View File

@ -1174,24 +1174,6 @@ struct TALER_TESTING_Command
TALER_TESTING_cmd_withdraw_with_retry (struct TALER_TESTING_Command cmd); 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. * Create a GET "reserves" command.
* *

View File

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

View File

@ -709,7 +709,7 @@ csr_withdraw_done (
{ {
case MHD_HTTP_OK: case MHD_HTTP_OK:
{ {
bool success = true; bool success = false;
/* Complete the initialization of the coin with CS denomination */ /* Complete the initialization of the coin with CS denomination */
can->alg_values = csrr->details.ok.alg_values; can->alg_values = csrr->details.ok.alg_values;
TALER_planchet_setup_coin_priv (&can->secret, TALER_planchet_setup_coin_priv (&can->secret,
@ -720,29 +720,33 @@ csr_withdraw_done (
&can->blinding_key); &can->blinding_key);
/* This initializes the 2nd half of the /* This initializes the 2nd half of the
can->planchet_detail.blinded_planchet! */ can->planchet_detail.blinded_planchet! */
if (GNUNET_OK != do {
TALER_planchet_prepare (&can->denom_pub->key, if (GNUNET_OK !=
&can->alg_values, TALER_planchet_prepare (&can->denom_pub->key,
&can->blinding_key, &can->alg_values,
&can->coin_priv, &can->blinding_key,
&can->h_age_commitment, &can->coin_priv,
&can->h_coin_pub, &can->h_age_commitment,
&can->planchet_detail)) &can->h_coin_pub,
{ &can->planchet_detail))
GNUNET_break (0); {
success = false; GNUNET_break (0);
TALER_EXCHANGE_age_withdraw_cancel (awh); TALER_EXCHANGE_age_withdraw_cancel (awh);
} break;
}
if (GNUNET_OK != if (GNUNET_OK !=
TALER_coin_ev_hash (&can->planchet_detail.blinded_planchet, TALER_coin_ev_hash (&can->planchet_detail.blinded_planchet,
&can->planchet_detail.denom_pub_hash, &can->planchet_detail.denom_pub_hash,
&can->blinded_coin_h)) &can->blinded_coin_h))
{ {
GNUNET_break (0); GNUNET_break (0);
success = false; TALER_EXCHANGE_age_withdraw_cancel (awh);
TALER_EXCHANGE_age_withdraw_cancel (awh); break;
} }
success = true;
} while(0);
awh->csr_pending--; awh->csr_pending--;

View File

@ -47,6 +47,9 @@ struct TALER_EXCHANGE_AgeWithdrawRevealHandle
/* The age-withdraw commitment */ /* The age-withdraw commitment */
struct TALER_AgeWithdrawCommitmentHashP h_commitment; struct TALER_AgeWithdrawCommitmentHashP h_commitment;
/* The maximum age */
uint8_t max_age;
/* Number of coins */ /* Number of coins */
size_t num_coins; size_t num_coins;
@ -57,9 +60,6 @@ struct TALER_EXCHANGE_AgeWithdrawRevealHandle
* previous call to /age-withdraw. */ * previous call to /age-withdraw. */
const struct TALER_ExchangeWithdrawValues *alg_values; const struct TALER_ExchangeWithdrawValues *alg_values;
/* The curl context for the request */
struct GNUNET_CURL_Context *curl_ctx;
/* The url for the reveal request */ /* The url for the reveal request */
const char *request_url; const char *request_url;
@ -81,6 +81,82 @@ struct TALER_EXCHANGE_AgeWithdrawRevealHandle
}; };
static enum GNUNET_GenericReturnValue
reveal_coin (
const struct TALER_PlanchetMasterSecretP *secret,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_ExchangeWithdrawValues *alg_values,
const struct TALER_BlindedDenominationSignature *blind_sig,
uint8_t max_age,
struct TALER_EXCHANGE_RevealedCoinInfo *revealed_coin)
{
enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
#define BREAK_ON_FAILURE(call) \
do { \
if (GNUNET_OK != (call)) { GNUNET_break (0); break; } \
} while(0)
do {
struct TALER_CoinPubHashP h_coin_pub;
struct TALER_PlanchetDetail planchet_detail;
const struct TALER_AgeCommitmentHash *hac = NULL;
struct TALER_FreshCoin fresh_coin;
TALER_planchet_setup_coin_priv (secret,
alg_values,
&revealed_coin->coin_priv);
TALER_planchet_blinding_secret_create (secret,
alg_values,
&revealed_coin->bks);
revealed_coin->age_commitment_proof = NULL;
if (0 < max_age)
{
BREAK_ON_FAILURE (
TALER_age_restriction_from_secret (
secret,
&denom_pub->age_mask,
max_age,
revealed_coin->age_commitment_proof));
TALER_age_commitment_hash (
&revealed_coin->age_commitment_proof->commitment,
&revealed_coin->h_age_commitment);
hac = &revealed_coin->h_age_commitment;
}
BREAK_ON_FAILURE (
TALER_planchet_prepare (denom_pub,
alg_values,
&revealed_coin->bks,
&revealed_coin->coin_priv,
hac,
&h_coin_pub,
&planchet_detail));
BREAK_ON_FAILURE (
TALER_planchet_to_coin (denom_pub,
blind_sig,
&revealed_coin->bks,
&revealed_coin->coin_priv,
&revealed_coin->h_age_commitment,
&h_coin_pub,
alg_values,
&fresh_coin));
/* success */
revealed_coin->sig = fresh_coin.sig;
ret = GNUNET_OK;
} while(0);
return ret;
#undef BREAK_ON_FAILURE
}
/** /**
* We got a 200 OK response for the /age-withdraw/$ACH/reveal operation. * We got a 200 OK response for the /age-withdraw/$ACH/reveal operation.
* Extract the signed blindedcoins and return it to the caller. * Extract the signed blindedcoins and return it to the caller.
@ -94,9 +170,7 @@ struct TALER_EXCHANGE_AgeWithdrawRevealHandle
static enum GNUNET_GenericReturnValue static enum GNUNET_GenericReturnValue
age_withdraw_reveal_ok ( age_withdraw_reveal_ok (
struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh, struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh,
const json_t *j_response, const json_t *j_response)
size_t num_coins,
struct TALER_EXCHANGE_RevealedCoinInfo revealed_coins[static num_coins])
{ {
struct TALER_EXCHANGE_AgeWithdrawRevealResponse response = { struct TALER_EXCHANGE_AgeWithdrawRevealResponse response = {
.hr.reply = j_response, .hr.reply = j_response,
@ -109,35 +183,61 @@ age_withdraw_reveal_ok (
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
if (GNUNET_OK!= if (GNUNET_OK != GNUNET_JSON_parse (j_response,
GNUNET_JSON_parse (j_response, spec,
spec, NULL, NULL))
NULL, NULL))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
GNUNET_assert (num_coins == awrh->num_coins); if (awrh->num_coins != json_array_size (j_sigs))
if (num_coins != json_array_size (j_sigs))
{ {
/* Number of coins generated does not match our expectation */ /* Number of coins generated does not match our expectation */
GNUNET_break_op (0); GNUNET_break_op (0);
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
for (size_t n = 0; n < num_coins; n++)
{ {
// TODO[oec] extract the individual coins. struct TALER_EXCHANGE_RevealedCoinInfo revealed_coins[awrh->num_coins];
}
response.details.ok.num_coins = num_coins; /* Reconstruct the coins and unblind the signatures */
response.details.ok.revealed_coins = revealed_coins; for (size_t n = 0; n < awrh->num_coins; n++)
awrh->callback (awrh->callback_cls, {
&response); enum GNUNET_GenericReturnValue ret;
/* make sure the callback isn't called again */ struct TALER_BlindedDenominationSignature blinded_sig;
awrh->callback = NULL; json_t *j_sig = json_array_get (j_sigs, n);
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("", &blinded_sig),
GNUNET_JSON_spec_end ()
};
GNUNET_assert (NULL != j_sig);
if (GNUNET_OK != GNUNET_JSON_parse (j_sig,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
ret = reveal_coin (&awrh->coins_input[n].secrets[awrh->noreveal_index],
&awrh->coins_input[n].denom_pub->key,
&awrh->alg_values[n],
&blinded_sig,
awrh->max_age,
&revealed_coins[n]);
if (GNUNET_OK != ret)
return ret;
}
response.details.ok.num_coins = awrh->num_coins;
response.details.ok.revealed_coins = revealed_coins;
awrh->callback (awrh->callback_cls,
&response);
/* Make sure the callback isn't called again */
awrh->callback = NULL;
}
return GNUNET_OK; return GNUNET_OK;
} }
@ -165,6 +265,7 @@ handle_age_withdraw_reveal_finished (
}; };
awrh->job = NULL; awrh->job = NULL;
/* FIXME[oec]: Only handle response-codes that are in the spec */
switch (response_code) switch (response_code)
{ {
case 0: case 0:
@ -173,12 +274,9 @@ handle_age_withdraw_reveal_finished (
case MHD_HTTP_OK: case MHD_HTTP_OK:
{ {
enum GNUNET_GenericReturnValue ret; enum GNUNET_GenericReturnValue ret;
struct TALER_EXCHANGE_RevealedCoinInfo revealed_coins[awrh->num_coins];
ret = age_withdraw_reveal_ok (awrh, ret = age_withdraw_reveal_ok (awrh,
j_response, j_response);
awrh->num_coins,
revealed_coins);
if (GNUNET_OK != ret) if (GNUNET_OK != ret)
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
@ -317,13 +415,14 @@ prepare_url (
/** /**
* Call /age-withdraw/$ACH/reveal * Call /age-withdraw/$ACH/reveal
* *
* @param curl_ctx The context for CURL
* @param awrh The handler * @param awrh The handler
* @param num_coins Number of coin candidates in reveal_inputs
* @param reveal_inputs The secrets of the coin candidates * @param reveal_inputs The secrets of the coin candidates
*/ */
static static
void void
perform_protocol ( perform_protocol (
struct GNUNET_CURL_Context *curl_ctx,
struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh) struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
{ {
CURL *curlh = NULL; CURL *curlh = NULL;
@ -380,7 +479,7 @@ perform_protocol (
json_decref (j_request_body); json_decref (j_request_body);
j_request_body = NULL; j_request_body = NULL;
awrh->job = GNUNET_CURL_job_add2 (awrh->curl_ctx, awrh->job = GNUNET_CURL_job_add2 (curl_ctx,
curlh, curlh,
awrh->post_ctx.headers, awrh->post_ctx.headers,
&handle_age_withdraw_reveal_finished, &handle_age_withdraw_reveal_finished,
@ -417,12 +516,12 @@ TALER_EXCHANGE_age_withdraw_reveal (
const struct TALER_ExchangeWithdrawValues alg_values[static num_coins], const struct TALER_ExchangeWithdrawValues alg_values[static num_coins],
uint8_t noreveal_index, uint8_t noreveal_index,
const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
uint8_t max_age,
TALER_EXCHANGE_AgeWithdrawRevealCallback reveal_cb, TALER_EXCHANGE_AgeWithdrawRevealCallback reveal_cb,
void *reveal_cb_cls) void *reveal_cb_cls)
{ {
struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh = struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh =
GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawRevealHandle); GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawRevealHandle);
awrh->curl_ctx = curl_ctx;
awrh->noreveal_index = noreveal_index; awrh->noreveal_index = noreveal_index;
awrh->callback = reveal_cb; awrh->callback = reveal_cb;
awrh->callback_cls = reveal_cb_cls; awrh->callback_cls = reveal_cb_cls;
@ -430,6 +529,7 @@ TALER_EXCHANGE_age_withdraw_reveal (
awrh->num_coins = num_coins; awrh->num_coins = num_coins;
awrh->coins_input = coins_input; awrh->coins_input = coins_input;
awrh->alg_values = alg_values; awrh->alg_values = alg_values;
awrh->max_age = max_age;
if (GNUNET_OK != if (GNUNET_OK !=
@ -437,7 +537,7 @@ TALER_EXCHANGE_age_withdraw_reveal (
awrh)) awrh))
return NULL; return NULL;
perform_protocol (awrh); perform_protocol (curl_ctx, awrh);
return awrh; return awrh;
} }
@ -447,9 +547,14 @@ void
TALER_EXCHANGE_age_withdraw_reveal_cancel ( TALER_EXCHANGE_age_withdraw_reveal_cancel (
struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh) struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
{ {
/* FIXME[oec] */ if (NULL != awrh->job)
(void) awrh; {
#pragma message "need to implement TALER_EXCHANGE_age_withdraw_reveal_cancel" GNUNET_CURL_job_cancel (awrh->job);
awrh->job = NULL;
}
TALER_curl_easy_post_finished (&awrh->post_ctx);
/* FIXME[oec]: anything else left to cleanup!? */
GNUNET_free (awrh);
} }

View File

@ -2241,15 +2241,17 @@ parse_restrictions (const json_t *jresta,
if (0 == strcmp (type, if (0 == strcmp (type,
"regex")) "regex"))
{ {
const char *regex;
const char *hint;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ( GNUNET_JSON_spec_string (
"payto_regex", "payto_regex",
&ar->details.regex.posix_egrep), &regex),
GNUNET_JSON_spec_string ( GNUNET_JSON_spec_string (
"human_hint", "human_hint",
&ar->details.regex.human_hint), &hint),
GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_object_const ( GNUNET_JSON_spec_json (
"human_hint_i18n", "human_hint_i18n",
&ar->details.regex.human_hint_i18n), &ar->details.regex.human_hint_i18n),
NULL), NULL),
@ -2266,6 +2268,8 @@ parse_restrictions (const json_t *jresta,
goto fail; goto fail;
} }
ar->type = TALER_EXCHANGE_AR_REGEX; ar->type = TALER_EXCHANGE_AR_REGEX;
ar->details.regex.posix_egrep = GNUNET_strdup (regex);
ar->details.regex.human_hint = GNUNET_strdup (hint);
continue; continue;
} }
/* unsupported type */ /* unsupported type */
@ -2297,14 +2301,16 @@ TALER_EXCHANGE_parse_accounts (
i++) i++)
{ {
struct TALER_EXCHANGE_WireAccount *wa = &was[i]; struct TALER_EXCHANGE_WireAccount *wa = &was[i];
const char *payto_uri;
const char *conversion_url;
const json_t *credit_restrictions; const json_t *credit_restrictions;
const json_t *debit_restrictions; const json_t *debit_restrictions;
struct GNUNET_JSON_Specification spec_account[] = { struct GNUNET_JSON_Specification spec_account[] = {
GNUNET_JSON_spec_string ("payto_uri", GNUNET_JSON_spec_string ("payto_uri",
&wa->payto_uri), &payto_uri),
GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("conversion_url", GNUNET_JSON_spec_string ("conversion_url",
&wa->conversion_url), &conversion_url),
NULL), NULL),
GNUNET_JSON_spec_array_const ("credit_restrictions", GNUNET_JSON_spec_array_const ("credit_restrictions",
&credit_restrictions), &credit_restrictions),
@ -2330,7 +2336,7 @@ TALER_EXCHANGE_parse_accounts (
{ {
char *err; char *err;
err = TALER_payto_validate (wa->payto_uri); err = TALER_payto_validate (payto_uri);
if (NULL != err) if (NULL != err)
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
@ -2341,12 +2347,13 @@ TALER_EXCHANGE_parse_accounts (
if ( (NULL != master_pub) && if ( (NULL != master_pub) &&
(GNUNET_OK != (GNUNET_OK !=
TALER_exchange_wire_signature_check (wa->payto_uri, TALER_exchange_wire_signature_check (
wa->conversion_url, payto_uri,
debit_restrictions, conversion_url,
credit_restrictions, debit_restrictions,
master_pub, credit_restrictions,
&wa->master_sig)) ) master_pub,
&wa->master_sig)) )
{ {
/* bogus reply */ /* bogus reply */
GNUNET_break_op (0); GNUNET_break_op (0);
@ -2365,11 +2372,44 @@ TALER_EXCHANGE_parse_accounts (
GNUNET_break_op (0); GNUNET_break_op (0);
return GNUNET_SYSERR; 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 */ } /* end 'for all accounts */
return GNUNET_OK; 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 void
TALER_EXCHANGE_free_accounts ( TALER_EXCHANGE_free_accounts (
unsigned int was_len, unsigned int was_len,
@ -2379,8 +2419,18 @@ TALER_EXCHANGE_free_accounts (
{ {
struct TALER_EXCHANGE_WireAccount *wa = &was[i]; struct TALER_EXCHANGE_WireAccount *wa = &was[i];
GNUNET_free (wa->credit_restrictions); GNUNET_free (wa->payto_uri);
GNUNET_free (wa->debit_restrictions); 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 * Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility. * 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? * 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 void
TEAH_get_auditors_for_dc ( TEAH_get_auditors_for_dc (
struct TALER_EXCHANGE_Keys *keys, struct TALER_EXCHANGE_Keys *keys,
@ -246,6 +355,10 @@ parse_json_denomkey_partially (
&denom_key->valid_from), &denom_key->valid_from),
GNUNET_JSON_spec_timestamp ("stamp_expire_legal", GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
&denom_key->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, TALER_JSON_spec_denom_pub_cipher (NULL,
cipher, cipher,
&denom_key->key), &denom_key->key),
@ -537,6 +650,9 @@ decode_keys_json (const json_t *resp_obj,
const json_t *manifests = NULL; const json_t *manifests = NULL;
bool no_extensions = false; bool no_extensions = false;
bool no_signature = false; bool no_signature = false;
const json_t *accounts;
const json_t *fees;
const json_t *wads;
if (JSON_OBJECT != json_typeof (resp_obj)) if (JSON_OBJECT != json_typeof (resp_obj))
{ {
@ -608,6 +724,12 @@ decode_keys_json (const json_t *resp_obj,
GNUNET_JSON_spec_fixed_auto ( GNUNET_JSON_spec_fixed_auto (
"master_public_key", "master_public_key",
&key_data->master_pub), &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 ( GNUNET_JSON_spec_timestamp (
"list_issue_date", "list_issue_date",
&key_data->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. */ /* Parse the supported extension(s): age-restriction. */
/* TODO: maybe lift all this into a FP in TALER_Extension ? */ /* TODO: maybe lift all this into a FP in TALER_Extension ? */
if (! no_extensions) if (! no_extensions)
@ -1542,6 +1684,13 @@ TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys)
GNUNET_array_grow (keys->auditors, GNUNET_array_grow (keys->auditors,
keys->auditors_size, keys->auditors_size,
0); 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); json_decref (keys->extensions);
GNUNET_free (keys->wallet_balance_limit_without_kyc); GNUNET_free (keys->wallet_balance_limit_without_kyc);
GNUNET_free (keys->version); 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 * json_t *
TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) 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 *denominations_by_group;
json_t *auditors; json_t *auditors;
json_t *recoup; json_t *recoup;
json_t *wire_fees;
json_t *accounts;
json_t *global_fees; json_t *global_fees;
json_t *wblwk = NULL; json_t *wblwk = NULL;
now = GNUNET_TIME_timestamp_get (); now = GNUNET_TIME_timestamp_get ();
signkeys = json_array (); signkeys = json_array ();
if (NULL == signkeys) GNUNET_assert (NULL != signkeys);
{
GNUNET_break (0);
return NULL;
}
for (unsigned int i = 0; i<kd->num_sign_keys; i++) for (unsigned int i = 0; i<kd->num_sign_keys; i++)
{ {
const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->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), sk->valid_until),
GNUNET_JSON_pack_timestamp ("stamp_end", GNUNET_JSON_pack_timestamp ("stamp_end",
sk->valid_legal)); sk->valid_legal));
if (NULL == signkey) GNUNET_assert (NULL != signkey);
{ GNUNET_assert (0 ==
GNUNET_break (0); json_array_append_new (signkeys,
continue; signkey));
}
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;
} }
denominations_by_group = json_array ();
GNUNET_assert (NULL != denominations_by_group);
{ {
struct GNUNET_CONTAINER_MultiHashMap *dbg; 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", GNUNET_JSON_pack_data_auto ("master_sig",
&gf->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 (); recoup = json_array ();
GNUNET_assert (NULL != recoup); GNUNET_assert (NULL != recoup);
for (unsigned int i = 0; i<kd->num_denom_keys; i++) 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), global_fees),
GNUNET_JSON_pack_array_steal ("signkeys", GNUNET_JSON_pack_array_steal ("signkeys",
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", GNUNET_JSON_pack_array_steal ("denominations",
denominations_by_group), denominations_by_group),
GNUNET_JSON_pack_allow_null ( 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) if (NULL != mhd_task)
{ {
GNUNET_SCHEDULER_cancel (mhd_task); GNUNET_SCHEDULER_cancel (mhd_task);
mhd_task = NULL; mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon,
run_daemon (NULL); NULL);
} }
else else
{ {

View File

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

View File

@ -106,21 +106,6 @@ static void
run (void *cls, run (void *cls,
struct TALER_TESTING_Interpreter *is) 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. * Test withdrawal plus spending.
*/ */
@ -1238,8 +1223,6 @@ run (void *cls,
NULL, NULL,
true, true,
true), true),
TALER_TESTING_cmd_batch ("wire",
wire),
TALER_TESTING_cmd_batch ("withdraw", TALER_TESTING_cmd_batch ("withdraw",
withdraw), withdraw),
TALER_TESTING_cmd_batch ("spend", TALER_TESTING_cmd_batch ("spend",

View File

@ -80,13 +80,6 @@ run (void *cls,
"get-exchange-1", "get-exchange-1",
true, true,
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 () TALER_TESTING_cmd_end ()
}; };