Merge branch 'master' of git+ssh://taler.net/var/git/exchange
This commit is contained in:
commit
07449ce578
@ -13,9 +13,9 @@ taler_exchange_benchmark_SOURCES = \
|
|||||||
taler-exchange-benchmark.c
|
taler-exchange-benchmark.c
|
||||||
taler_exchange_benchmark_LDADD = \
|
taler_exchange_benchmark_LDADD = \
|
||||||
$(LIBGCRYPT_LIBS) \
|
$(LIBGCRYPT_LIBS) \
|
||||||
-ltalerexchange \
|
$(top_builddir)/src/json/libtalerjson.la \
|
||||||
-ltalerjson \
|
$(top_builddir)/src/util/libtalerutil.la \
|
||||||
-ltalerutil \
|
$(top_builddir)/src/exchange-lib/libtalerexchange.la \
|
||||||
-lgnunetjson \
|
-lgnunetjson \
|
||||||
-lgnunetcurl \
|
-lgnunetcurl \
|
||||||
-lgnunetutil \
|
-lgnunetutil \
|
||||||
|
Binary file not shown.
@ -108,6 +108,11 @@ struct TALER_EXCHANGE_Handle
|
|||||||
*/
|
*/
|
||||||
struct TALER_EXCHANGE_Keys key_data;
|
struct TALER_EXCHANGE_Keys key_data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When does @e key_data expire?
|
||||||
|
*/
|
||||||
|
struct GNUNET_TIME_Absolute key_data_expiration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Raw key data of the exchange, only valid if
|
* Raw key data of the exchange, only valid if
|
||||||
* @e handshake_complete is past stage #MHS_CERT.
|
* @e handshake_complete is past stage #MHS_CERT.
|
||||||
@ -144,6 +149,12 @@ struct KeysRequest
|
|||||||
*/
|
*/
|
||||||
struct GNUNET_CURL_Job *job;
|
struct GNUNET_CURL_Job *job;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expiration time according to "Expire:" header.
|
||||||
|
* 0 if not provided by the server.
|
||||||
|
*/
|
||||||
|
struct GNUNET_TIME_Absolute expire;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -485,6 +496,7 @@ decode_keys_json (const json_t *resp_obj,
|
|||||||
struct GNUNET_HashContext *hash_context;
|
struct GNUNET_HashContext *hash_context;
|
||||||
struct TALER_ExchangePublicKeyP pub;
|
struct TALER_ExchangePublicKeyP pub;
|
||||||
|
|
||||||
|
memset (key_data, 0, sizeof (struct TALER_EXCHANGE_Keys));
|
||||||
if (JSON_OBJECT != json_typeof (resp_obj))
|
if (JSON_OBJECT != json_typeof (resp_obj))
|
||||||
return GNUNET_SYSERR;
|
return GNUNET_SYSERR;
|
||||||
|
|
||||||
@ -604,6 +616,58 @@ decode_keys_json (const json_t *resp_obj,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free key data object.
|
||||||
|
*
|
||||||
|
* @param key_data data to free (pointer itself excluded)
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
free_key_data (struct TALER_EXCHANGE_Keys *key_data)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
GNUNET_array_grow (key_data->sign_keys,
|
||||||
|
key_data->num_sign_keys,
|
||||||
|
0);
|
||||||
|
for (i=0;i<key_data->num_denom_keys;i++)
|
||||||
|
GNUNET_CRYPTO_rsa_public_key_free (key_data->denom_keys[i].key.rsa_public_key);
|
||||||
|
GNUNET_array_grow (key_data->denom_keys,
|
||||||
|
key_data->num_denom_keys,
|
||||||
|
0);
|
||||||
|
GNUNET_array_grow (key_data->auditors,
|
||||||
|
key_data->num_auditors,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate download of /keys from the exchange.
|
||||||
|
*
|
||||||
|
* @param exchange where to download /keys from
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
request_keys (struct TALER_EXCHANGE_Handle *exchange);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if our current response for /keys is valid, and if
|
||||||
|
* not trigger download.
|
||||||
|
*
|
||||||
|
* @param exchange exchange to check keys for
|
||||||
|
* @return until when the response is current, 0 if we are re-downloading
|
||||||
|
*/
|
||||||
|
struct GNUNET_TIME_Absolute
|
||||||
|
TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange)
|
||||||
|
{
|
||||||
|
if (NULL != exchange->kr)
|
||||||
|
return GNUNET_TIME_UNIT_ZERO_ABS;
|
||||||
|
if (0 < GNUNET_TIME_absolute_get_remaining (exchange->key_data_expiration).rel_value_us)
|
||||||
|
return exchange->key_data_expiration;
|
||||||
|
request_keys (exchange);
|
||||||
|
return GNUNET_TIME_UNIT_ZERO_ABS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback used when downloading the reply to a /keys request
|
* Callback used when downloading the reply to a /keys request
|
||||||
* is complete.
|
* is complete.
|
||||||
@ -619,13 +683,16 @@ keys_completed_cb (void *cls,
|
|||||||
{
|
{
|
||||||
struct KeysRequest *kr = cls;
|
struct KeysRequest *kr = cls;
|
||||||
struct TALER_EXCHANGE_Handle *exchange = kr->exchange;
|
struct TALER_EXCHANGE_Handle *exchange = kr->exchange;
|
||||||
TALER_EXCHANGE_CertificationCallback cb;
|
struct TALER_EXCHANGE_Keys kd;
|
||||||
|
struct TALER_EXCHANGE_Keys kd_old;
|
||||||
|
|
||||||
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
||||||
"Received keys from URL `%s' with status %ld.\n",
|
"Received keys from URL `%s' with status %ld.\n",
|
||||||
kr->url,
|
kr->url,
|
||||||
response_code);
|
response_code);
|
||||||
switch (response_code) {
|
kd_old = exchange->key_data;
|
||||||
|
switch (response_code)
|
||||||
|
{
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
case MHD_HTTP_OK:
|
case MHD_HTTP_OK:
|
||||||
@ -635,11 +702,14 @@ keys_completed_cb (void *cls,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (GNUNET_OK !=
|
if (GNUNET_OK !=
|
||||||
decode_keys_json (resp_obj, &kr->exchange->key_data))
|
decode_keys_json (resp_obj,
|
||||||
|
&kd))
|
||||||
{
|
{
|
||||||
response_code = 0;
|
response_code = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
exchange->key_data = kd;
|
||||||
|
json_decref (exchange->key_data_raw);
|
||||||
exchange->key_data_raw = json_deep_copy (resp_obj);
|
exchange->key_data_raw = json_deep_copy (resp_obj);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -655,24 +725,25 @@ keys_completed_cb (void *cls,
|
|||||||
free_keys_request (kr);
|
free_keys_request (kr);
|
||||||
exchange->state = MHS_FAILED;
|
exchange->state = MHS_FAILED;
|
||||||
/* notify application that we failed */
|
/* notify application that we failed */
|
||||||
if (NULL != (cb = exchange->cert_cb))
|
exchange->cert_cb (exchange->cert_cb_cls,
|
||||||
{
|
NULL);
|
||||||
exchange->cert_cb = NULL;
|
if (NULL != exchange->key_data_raw)
|
||||||
cb (exchange->cert_cb_cls,
|
{
|
||||||
NULL);
|
json_decref (exchange->key_data_raw);
|
||||||
}
|
exchange->key_data_raw = NULL;
|
||||||
|
}
|
||||||
|
free_key_data (&kd_old);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
exchange->kr = NULL;
|
exchange->kr = NULL;
|
||||||
|
exchange->key_data_expiration = kr->expire;
|
||||||
free_keys_request (kr);
|
free_keys_request (kr);
|
||||||
exchange->state = MHS_CERT;
|
exchange->state = MHS_CERT;
|
||||||
/* notify application about the key information */
|
/* notify application about the key information */
|
||||||
if (NULL != (cb = exchange->cert_cb))
|
exchange->cert_cb (exchange->cert_cb_cls,
|
||||||
{
|
&exchange->key_data);
|
||||||
exchange->cert_cb = NULL;
|
free_key_data (&kd_old);
|
||||||
cb (exchange->cert_cb_cls,
|
|
||||||
&exchange->key_data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -730,6 +801,108 @@ MAH_path_to_url (struct TALER_EXCHANGE_Handle *h,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse HTTP timestamp.
|
||||||
|
*
|
||||||
|
* @param date header to parse header
|
||||||
|
* @param at where to write the result
|
||||||
|
* @return #GNUNET_OK on success
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
parse_date_string (const char *date,
|
||||||
|
struct GNUNET_TIME_Absolute *at)
|
||||||
|
{
|
||||||
|
static const char *const days[] =
|
||||||
|
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
||||||
|
static const char *const mons[] =
|
||||||
|
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||||
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
||||||
|
struct tm now;
|
||||||
|
time_t t;
|
||||||
|
char day[3];
|
||||||
|
char mon[3];
|
||||||
|
unsigned int i;
|
||||||
|
unsigned int mday;
|
||||||
|
unsigned int year;
|
||||||
|
unsigned int h;
|
||||||
|
unsigned int m;
|
||||||
|
unsigned int s;
|
||||||
|
|
||||||
|
if (7 != sscanf (date,
|
||||||
|
"%3s, %02u %3s %04u %02u:%02u:%02u GMT",
|
||||||
|
day,
|
||||||
|
&mday,
|
||||||
|
mon,
|
||||||
|
&year,
|
||||||
|
&h,
|
||||||
|
&m,
|
||||||
|
&s))
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
memset (&now, 0, sizeof (now));
|
||||||
|
now.tm_year = year - 1900;
|
||||||
|
now.tm_mday = mday;
|
||||||
|
now.tm_hour = h;
|
||||||
|
now.tm_min = m;
|
||||||
|
now.tm_sec = s;
|
||||||
|
now.tm_wday = 7;
|
||||||
|
for (i=0;i<7;i++)
|
||||||
|
if (0 == strcasecmp (days[i], day))
|
||||||
|
now.tm_wday = i;
|
||||||
|
now.tm_mon = 12;
|
||||||
|
for (i=0;i<12;i++)
|
||||||
|
if (0 == strcasecmp (mons[i], mon))
|
||||||
|
now.tm_mon = i;
|
||||||
|
if ( (7 == now.tm_mday) ||
|
||||||
|
(12 == now.tm_mon) )
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
t = mktime (&now);
|
||||||
|
at->abs_value_us = 1000LL * 1000LL * t;
|
||||||
|
return GNUNET_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called for each header in the HTTP /keys response.
|
||||||
|
* Finds the "Expire:" header and parses it, storing the result
|
||||||
|
* in the "expire" field fo the keys request.
|
||||||
|
*
|
||||||
|
* @param buffer header data received
|
||||||
|
* @param size size of an item in @a buffer
|
||||||
|
* @param nitems number of items in @a buffer
|
||||||
|
* @param userdata the `struct KeysRequest`
|
||||||
|
* @return `size * nitems` on success (everything else aborts)
|
||||||
|
*/
|
||||||
|
static size_t
|
||||||
|
header_cb (char *buffer,
|
||||||
|
size_t size,
|
||||||
|
size_t nitems,
|
||||||
|
void *userdata)
|
||||||
|
{
|
||||||
|
struct KeysRequest *kr = userdata;
|
||||||
|
size_t total = size * nitems;
|
||||||
|
char *val;
|
||||||
|
|
||||||
|
if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": "))
|
||||||
|
return total;
|
||||||
|
if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ",
|
||||||
|
buffer,
|
||||||
|
strlen (MHD_HTTP_HEADER_EXPIRES ": ")))
|
||||||
|
return total;
|
||||||
|
val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")],
|
||||||
|
total - strlen (MHD_HTTP_HEADER_EXPIRES ": "));
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
parse_date_string (val,
|
||||||
|
&kr->expire))
|
||||||
|
{
|
||||||
|
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
|
||||||
|
"Failed to parse %s-header `%s'\n",
|
||||||
|
MHD_HTTP_HEADER_EXPIRES,
|
||||||
|
val);
|
||||||
|
}
|
||||||
|
GNUNET_free (val);
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
/* ********************* public API ******************* */
|
/* ********************* public API ******************* */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -755,40 +928,62 @@ TALER_EXCHANGE_connect (struct GNUNET_CURL_Context *ctx,
|
|||||||
...)
|
...)
|
||||||
{
|
{
|
||||||
struct TALER_EXCHANGE_Handle *exchange;
|
struct TALER_EXCHANGE_Handle *exchange;
|
||||||
struct KeysRequest *kr;
|
|
||||||
CURL *c;
|
|
||||||
|
|
||||||
exchange = GNUNET_new (struct TALER_EXCHANGE_Handle);
|
exchange = GNUNET_new (struct TALER_EXCHANGE_Handle);
|
||||||
exchange->ctx = ctx;
|
exchange->ctx = ctx;
|
||||||
exchange->url = GNUNET_strdup (url);
|
exchange->url = GNUNET_strdup (url);
|
||||||
exchange->cert_cb = cert_cb;
|
exchange->cert_cb = cert_cb;
|
||||||
exchange->cert_cb_cls = cert_cb_cls;
|
exchange->cert_cb_cls = cert_cb_cls;
|
||||||
|
request_keys (exchange);
|
||||||
|
return exchange;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate download of /keys from the exchange.
|
||||||
|
*
|
||||||
|
* @param exchange where to download /keys from
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
request_keys (struct TALER_EXCHANGE_Handle *exchange)
|
||||||
|
{
|
||||||
|
struct KeysRequest *kr;
|
||||||
|
CURL *eh;
|
||||||
|
|
||||||
|
GNUNET_assert (NULL == exchange->kr);
|
||||||
kr = GNUNET_new (struct KeysRequest);
|
kr = GNUNET_new (struct KeysRequest);
|
||||||
kr->exchange = exchange;
|
kr->exchange = exchange;
|
||||||
kr->url = MAH_path_to_url (exchange, "/keys");
|
kr->url = MAH_path_to_url (exchange, "/keys");
|
||||||
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
||||||
"Requesting keys with URL `%s'.\n",
|
"Requesting keys with URL `%s'.\n",
|
||||||
kr->url);
|
kr->url);
|
||||||
c = curl_easy_init ();
|
eh = curl_easy_init ();
|
||||||
GNUNET_assert (CURLE_OK ==
|
GNUNET_assert (CURLE_OK ==
|
||||||
curl_easy_setopt (c,
|
curl_easy_setopt (eh,
|
||||||
CURLOPT_VERBOSE,
|
CURLOPT_VERBOSE,
|
||||||
0));
|
0));
|
||||||
GNUNET_assert (CURLE_OK ==
|
GNUNET_assert (CURLE_OK ==
|
||||||
curl_easy_setopt (c,
|
curl_easy_setopt (eh,
|
||||||
CURLOPT_STDERR,
|
CURLOPT_TIMEOUT,
|
||||||
stdout));
|
(long) 300));
|
||||||
GNUNET_assert (CURLE_OK ==
|
GNUNET_assert (CURLE_OK ==
|
||||||
curl_easy_setopt (c,
|
curl_easy_setopt (eh,
|
||||||
|
CURLOPT_HEADERFUNCTION,
|
||||||
|
&header_cb));
|
||||||
|
GNUNET_assert (CURLE_OK ==
|
||||||
|
curl_easy_setopt (eh,
|
||||||
|
CURLOPT_HEADERDATA,
|
||||||
|
kr));
|
||||||
|
GNUNET_assert (CURLE_OK ==
|
||||||
|
curl_easy_setopt (eh,
|
||||||
CURLOPT_URL,
|
CURLOPT_URL,
|
||||||
kr->url));
|
kr->url));
|
||||||
kr->job = GNUNET_CURL_job_add (exchange->ctx,
|
kr->job = GNUNET_CURL_job_add (exchange->ctx,
|
||||||
c,
|
eh,
|
||||||
GNUNET_NO,
|
GNUNET_NO,
|
||||||
&keys_completed_cb,
|
&keys_completed_cb,
|
||||||
kr);
|
kr);
|
||||||
exchange->kr = kr;
|
exchange->kr = kr;
|
||||||
return exchange;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -800,26 +995,18 @@ TALER_EXCHANGE_connect (struct GNUNET_CURL_Context *ctx,
|
|||||||
void
|
void
|
||||||
TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange)
|
TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange)
|
||||||
{
|
{
|
||||||
unsigned int i;
|
|
||||||
|
|
||||||
if (NULL != exchange->kr)
|
if (NULL != exchange->kr)
|
||||||
{
|
{
|
||||||
GNUNET_CURL_job_cancel (exchange->kr->job);
|
GNUNET_CURL_job_cancel (exchange->kr->job);
|
||||||
free_keys_request (exchange->kr);
|
free_keys_request (exchange->kr);
|
||||||
exchange->kr = NULL;
|
exchange->kr = NULL;
|
||||||
}
|
}
|
||||||
GNUNET_array_grow (exchange->key_data.sign_keys,
|
free_key_data (&exchange->key_data);
|
||||||
exchange->key_data.num_sign_keys,
|
if (NULL != exchange->key_data_raw)
|
||||||
0);
|
{
|
||||||
for (i=0;i<exchange->key_data.num_denom_keys;i++)
|
json_decref (exchange->key_data_raw);
|
||||||
GNUNET_CRYPTO_rsa_public_key_free (exchange->key_data.denom_keys[i].key.rsa_public_key);
|
exchange->key_data_raw = NULL;
|
||||||
GNUNET_array_grow (exchange->key_data.denom_keys,
|
}
|
||||||
exchange->key_data.num_denom_keys,
|
|
||||||
0);
|
|
||||||
GNUNET_array_grow (exchange->key_data.auditors,
|
|
||||||
exchange->key_data.num_auditors,
|
|
||||||
0);
|
|
||||||
json_decref (exchange->key_data_raw);
|
|
||||||
GNUNET_free (exchange->url);
|
GNUNET_free (exchange->url);
|
||||||
GNUNET_free (exchange);
|
GNUNET_free (exchange);
|
||||||
}
|
}
|
||||||
@ -863,7 +1050,7 @@ TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys,
|
|||||||
*/
|
*/
|
||||||
const struct TALER_EXCHANGE_DenomPublicKey *
|
const struct TALER_EXCHANGE_DenomPublicKey *
|
||||||
TALER_EXCHANGE_get_denomination_key (const struct TALER_EXCHANGE_Keys *keys,
|
TALER_EXCHANGE_get_denomination_key (const struct TALER_EXCHANGE_Keys *keys,
|
||||||
const struct TALER_DenominationPublicKey *pk)
|
const struct TALER_DenominationPublicKey *pk)
|
||||||
{
|
{
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
|
||||||
@ -884,7 +1071,7 @@ TALER_EXCHANGE_get_denomination_key (const struct TALER_EXCHANGE_Keys *keys,
|
|||||||
*/
|
*/
|
||||||
const struct TALER_EXCHANGE_DenomPublicKey *
|
const struct TALER_EXCHANGE_DenomPublicKey *
|
||||||
TALER_EXCHANGE_get_denomination_key_by_hash (const struct TALER_EXCHANGE_Keys *keys,
|
TALER_EXCHANGE_get_denomination_key_by_hash (const struct TALER_EXCHANGE_Keys *keys,
|
||||||
const struct GNUNET_HashCode *hc)
|
const struct GNUNET_HashCode *hc)
|
||||||
{
|
{
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
|
||||||
@ -904,8 +1091,9 @@ TALER_EXCHANGE_get_denomination_key_by_hash (const struct TALER_EXCHANGE_Keys *k
|
|||||||
* @return the exchange's key set
|
* @return the exchange's key set
|
||||||
*/
|
*/
|
||||||
const struct TALER_EXCHANGE_Keys *
|
const struct TALER_EXCHANGE_Keys *
|
||||||
TALER_EXCHANGE_get_keys (const struct TALER_EXCHANGE_Handle *exchange)
|
TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange)
|
||||||
{
|
{
|
||||||
|
(void) TALER_EXCHANGE_check_keys_current (exchange);
|
||||||
return &exchange->key_data;
|
return &exchange->key_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -918,8 +1106,9 @@ TALER_EXCHANGE_get_keys (const struct TALER_EXCHANGE_Handle *exchange)
|
|||||||
* @return the exchange's keys in raw JSON
|
* @return the exchange's keys in raw JSON
|
||||||
*/
|
*/
|
||||||
json_t *
|
json_t *
|
||||||
TALER_EXCHANGE_get_keys_raw (const struct TALER_EXCHANGE_Handle *exchange)
|
TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange)
|
||||||
{
|
{
|
||||||
|
(void) TALER_EXCHANGE_check_keys_current (exchange);
|
||||||
return json_deep_copy (exchange->key_data_raw);
|
return json_deep_copy (exchange->key_data_raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +88,11 @@ struct TMH_KS_StateHandle
|
|||||||
*/
|
*/
|
||||||
struct GNUNET_TIME_Absolute next_reload;
|
struct GNUNET_TIME_Absolute next_reload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When does the first active denomination key expire (for deposit)?
|
||||||
|
*/
|
||||||
|
struct GNUNET_TIME_Absolute min_dk_expire;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exchange signing key that should be used currently.
|
* Exchange signing key that should be used currently.
|
||||||
*/
|
*/
|
||||||
@ -217,6 +222,7 @@ reload_keys_denom_iter (void *cls,
|
|||||||
struct TMH_KS_StateHandle *ctx = cls;
|
struct TMH_KS_StateHandle *ctx = cls;
|
||||||
struct GNUNET_TIME_Absolute now;
|
struct GNUNET_TIME_Absolute now;
|
||||||
struct GNUNET_TIME_Absolute horizon;
|
struct GNUNET_TIME_Absolute horizon;
|
||||||
|
struct GNUNET_TIME_Absolute expire_deposit;
|
||||||
struct GNUNET_HashCode denom_key_hash;
|
struct GNUNET_HashCode denom_key_hash;
|
||||||
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *d2;
|
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *d2;
|
||||||
struct TALER_EXCHANGEDB_Session *session;
|
struct TALER_EXCHANGEDB_Session *session;
|
||||||
@ -235,8 +241,8 @@ reload_keys_denom_iter (void *cls,
|
|||||||
return GNUNET_OK;
|
return GNUNET_OK;
|
||||||
}
|
}
|
||||||
now = GNUNET_TIME_absolute_get ();
|
now = GNUNET_TIME_absolute_get ();
|
||||||
if (GNUNET_TIME_absolute_ntoh (dki->issue.properties.expire_deposit).abs_value_us <
|
expire_deposit = GNUNET_TIME_absolute_ntoh (dki->issue.properties.expire_deposit);
|
||||||
now.abs_value_us)
|
if (expire_deposit.abs_value_us < now.abs_value_us)
|
||||||
{
|
{
|
||||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||||
"Skipping expired denomination key `%s'\n",
|
"Skipping expired denomination key `%s'\n",
|
||||||
@ -339,6 +345,8 @@ reload_keys_denom_iter (void *cls,
|
|||||||
GNUNET_free (d2);
|
GNUNET_free (d2);
|
||||||
return GNUNET_OK;
|
return GNUNET_OK;
|
||||||
}
|
}
|
||||||
|
ctx->min_dk_expire = GNUNET_TIME_absolute_min (ctx->min_dk_expire,
|
||||||
|
expire_deposit);
|
||||||
json_array_append_new (ctx->denom_keys_array,
|
json_array_append_new (ctx->denom_keys_array,
|
||||||
denom_key_issue_to_json (&dki->denom_pub,
|
denom_key_issue_to_json (&dki->denom_pub,
|
||||||
&dki->issue));
|
&dki->issue));
|
||||||
@ -643,7 +651,7 @@ TMH_KS_acquire_ (const char *location)
|
|||||||
{
|
{
|
||||||
key_state = GNUNET_new (struct TMH_KS_StateHandle);
|
key_state = GNUNET_new (struct TMH_KS_StateHandle);
|
||||||
key_state->hash_context = GNUNET_CRYPTO_hash_context_start ();
|
key_state->hash_context = GNUNET_CRYPTO_hash_context_start ();
|
||||||
|
key_state->min_dk_expire = GNUNET_TIME_UNIT_FOREVER_ABS;
|
||||||
|
|
||||||
key_state->denom_keys_array = json_array ();
|
key_state->denom_keys_array = json_array ();
|
||||||
GNUNET_assert (NULL != key_state->denom_keys_array);
|
GNUNET_assert (NULL != key_state->denom_keys_array);
|
||||||
@ -680,7 +688,6 @@ TMH_KS_acquire_ (const char *location)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ks.purpose.size = htonl (sizeof (ks));
|
ks.purpose.size = htonl (sizeof (ks));
|
||||||
ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET);
|
ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET);
|
||||||
ks.list_issue_date = GNUNET_TIME_absolute_hton (key_state->reload_time);
|
ks.list_issue_date = GNUNET_TIME_absolute_hton (key_state->reload_time);
|
||||||
@ -691,7 +698,9 @@ TMH_KS_acquire_ (const char *location)
|
|||||||
GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv.eddsa_priv,
|
GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv.eddsa_priv,
|
||||||
&ks.purpose,
|
&ks.purpose,
|
||||||
&sig.eddsa_signature));
|
&sig.eddsa_signature));
|
||||||
key_state->next_reload = GNUNET_TIME_absolute_ntoh (key_state->current_sign_key_issue.issue.expire);
|
key_state->next_reload =
|
||||||
|
GNUNET_TIME_absolute_min (GNUNET_TIME_absolute_ntoh (key_state->current_sign_key_issue.issue.expire),
|
||||||
|
key_state->min_dk_expire);
|
||||||
if (0 == key_state->next_reload.abs_value_us)
|
if (0 == key_state->next_reload.abs_value_us)
|
||||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||||
"No valid signing key found!\n");
|
"No valid signing key found!\n");
|
||||||
@ -1001,6 +1010,58 @@ TMH_KS_sign (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce HTTP "Date:" header.
|
||||||
|
*
|
||||||
|
* @param at time to write to @a date
|
||||||
|
* @param[out] date where to write the header, with
|
||||||
|
* at least 128 bytes available space.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
get_date_string (struct GNUNET_TIME_Absolute at,
|
||||||
|
char *date)
|
||||||
|
{
|
||||||
|
static const char *const days[] =
|
||||||
|
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
||||||
|
static const char *const mons[] =
|
||||||
|
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
|
||||||
|
"Nov", "Dec"
|
||||||
|
};
|
||||||
|
struct tm now;
|
||||||
|
time_t t;
|
||||||
|
#if !defined(HAVE_C11_GMTIME_S) && !defined(HAVE_W32_GMTIME_S) && !defined(HAVE_GMTIME_R)
|
||||||
|
struct tm* pNow;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
date[0] = 0;
|
||||||
|
t = (time_t) (at.abs_value_us / 1000LL / 1000LL);
|
||||||
|
#if defined(HAVE_C11_GMTIME_S)
|
||||||
|
if (NULL == gmtime_s (&t, &now))
|
||||||
|
return;
|
||||||
|
#elif defined(HAVE_W32_GMTIME_S)
|
||||||
|
if (0 != gmtime_s (&now, &t))
|
||||||
|
return;
|
||||||
|
#elif defined(HAVE_GMTIME_R)
|
||||||
|
if (NULL == gmtime_r(&t, &now))
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
pNow = gmtime(&t);
|
||||||
|
if (NULL == pNow)
|
||||||
|
return;
|
||||||
|
now = *pNow;
|
||||||
|
#endif
|
||||||
|
sprintf (date,
|
||||||
|
"%3s, %02u %3s %04u %02u:%02u:%02u GMT",
|
||||||
|
days[now.tm_wday % 7],
|
||||||
|
(unsigned int) now.tm_mday,
|
||||||
|
mons[now.tm_mon % 12],
|
||||||
|
(unsigned int) (1900 + now.tm_year),
|
||||||
|
(unsigned int) now.tm_hour,
|
||||||
|
(unsigned int) now.tm_min,
|
||||||
|
(unsigned int) now.tm_sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to call to handle the request by sending
|
* Function to call to handle the request by sending
|
||||||
* back static data from the @a rh.
|
* back static data from the @a rh.
|
||||||
@ -1022,6 +1083,7 @@ TMH_KS_handler_keys (struct TMH_RequestHandler *rh,
|
|||||||
struct TMH_KS_StateHandle *key_state;
|
struct TMH_KS_StateHandle *key_state;
|
||||||
struct MHD_Response *response;
|
struct MHD_Response *response;
|
||||||
int ret;
|
int ret;
|
||||||
|
char dat[128];
|
||||||
|
|
||||||
key_state = TMH_KS_acquire ();
|
key_state = TMH_KS_acquire ();
|
||||||
response = MHD_create_response_from_buffer (strlen (key_state->keys_json),
|
response = MHD_create_response_from_buffer (strlen (key_state->keys_json),
|
||||||
@ -1034,9 +1096,22 @@ TMH_KS_handler_keys (struct TMH_RequestHandler *rh,
|
|||||||
return MHD_NO;
|
return MHD_NO;
|
||||||
}
|
}
|
||||||
TMH_RESPONSE_add_global_headers (response);
|
TMH_RESPONSE_add_global_headers (response);
|
||||||
(void) MHD_add_response_header (response,
|
GNUNET_break (MHD_YES ==
|
||||||
"Content-Type",
|
MHD_add_response_header (response,
|
||||||
rh->mime_type);
|
MHD_HTTP_HEADER_CONTENT_TYPE,
|
||||||
|
rh->mime_type));
|
||||||
|
get_date_string (key_state->reload_time,
|
||||||
|
dat);
|
||||||
|
GNUNET_break (MHD_YES ==
|
||||||
|
MHD_add_response_header (response,
|
||||||
|
MHD_HTTP_HEADER_LAST_MODIFIED,
|
||||||
|
dat));
|
||||||
|
get_date_string (key_state->next_reload,
|
||||||
|
dat);
|
||||||
|
GNUNET_break (MHD_YES ==
|
||||||
|
MHD_add_response_header (response,
|
||||||
|
MHD_HTTP_HEADER_EXPIRES,
|
||||||
|
dat));
|
||||||
ret = MHD_queue_response (connection,
|
ret = MHD_queue_response (connection,
|
||||||
rh->response_code,
|
rh->response_code,
|
||||||
response);
|
response);
|
||||||
|
@ -40,9 +40,10 @@ void
|
|||||||
TMH_RESPONSE_add_global_headers (struct MHD_Response *response)
|
TMH_RESPONSE_add_global_headers (struct MHD_Response *response)
|
||||||
{
|
{
|
||||||
if (TMH_exchange_connection_close)
|
if (TMH_exchange_connection_close)
|
||||||
(void) MHD_add_response_header (response,
|
GNUNET_break (MHD_YES ==
|
||||||
MHD_HTTP_HEADER_CONNECTION,
|
MHD_add_response_header (response,
|
||||||
"close");
|
MHD_HTTP_HEADER_CONNECTION,
|
||||||
|
"close"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -4298,6 +4298,12 @@ postgres_gc (void *cls)
|
|||||||
conn = connect_to_postgres (pc);
|
conn = connect_to_postgres (pc);
|
||||||
if (NULL == conn)
|
if (NULL == conn)
|
||||||
return GNUNET_SYSERR;
|
return GNUNET_SYSERR;
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
postgres_prepare (conn))
|
||||||
|
{
|
||||||
|
PQfinish (conn);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
result = GNUNET_PQ_exec_prepared (conn,
|
result = GNUNET_PQ_exec_prepared (conn,
|
||||||
"gc_prewire",
|
"gc_prewire",
|
||||||
params_none);
|
params_none);
|
||||||
|
@ -202,11 +202,17 @@ destroy_denom_key_pair (struct DenomKeyPair *dkp)
|
|||||||
*
|
*
|
||||||
* @param size the size of the denomination key
|
* @param size the size of the denomination key
|
||||||
* @param session the DB session
|
* @param session the DB session
|
||||||
|
* @param now time to use for key generation, legal expiration will be 3h later.
|
||||||
|
* @param fee_withdraw withdraw fee to use
|
||||||
|
* @param fee_deposit deposit fee to use
|
||||||
|
* @param fee_refresh refresh fee to use
|
||||||
|
* @param fee_refund refund fee to use
|
||||||
* @return the denominaiton key pair; NULL upon error
|
* @return the denominaiton key pair; NULL upon error
|
||||||
*/
|
*/
|
||||||
static struct DenomKeyPair *
|
static struct DenomKeyPair *
|
||||||
create_denom_key_pair (unsigned int size,
|
create_denom_key_pair (unsigned int size,
|
||||||
struct TALER_EXCHANGEDB_Session *session,
|
struct TALER_EXCHANGEDB_Session *session,
|
||||||
|
struct GNUNET_TIME_Absolute now,
|
||||||
const struct TALER_Amount *value,
|
const struct TALER_Amount *value,
|
||||||
const struct TALER_Amount *fee_withdraw,
|
const struct TALER_Amount *fee_withdraw,
|
||||||
const struct TALER_Amount *fee_deposit,
|
const struct TALER_Amount *fee_deposit,
|
||||||
@ -216,7 +222,6 @@ create_denom_key_pair (unsigned int size,
|
|||||||
struct DenomKeyPair *dkp;
|
struct DenomKeyPair *dkp;
|
||||||
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation dki;
|
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation dki;
|
||||||
struct TALER_EXCHANGEDB_DenominationKeyInformationP issue2;
|
struct TALER_EXCHANGEDB_DenominationKeyInformationP issue2;
|
||||||
struct GNUNET_TIME_Absolute now;
|
|
||||||
|
|
||||||
dkp = GNUNET_new (struct DenomKeyPair);
|
dkp = GNUNET_new (struct DenomKeyPair);
|
||||||
dkp->priv.rsa_private_key = GNUNET_CRYPTO_rsa_private_key_create (size);
|
dkp->priv.rsa_private_key = GNUNET_CRYPTO_rsa_private_key_create (size);
|
||||||
@ -230,7 +235,6 @@ create_denom_key_pair (unsigned int size,
|
|||||||
0,
|
0,
|
||||||
sizeof (struct TALER_EXCHANGEDB_DenominationKeyIssueInformation));
|
sizeof (struct TALER_EXCHANGEDB_DenominationKeyIssueInformation));
|
||||||
dki.denom_pub = dkp->pub;
|
dki.denom_pub = dkp->pub;
|
||||||
now = GNUNET_TIME_absolute_get ();
|
|
||||||
GNUNET_TIME_round_abs (&now);
|
GNUNET_TIME_round_abs (&now);
|
||||||
dki.issue.properties.start = GNUNET_TIME_absolute_hton (now);
|
dki.issue.properties.start = GNUNET_TIME_absolute_hton (now);
|
||||||
dki.issue.properties.expire_withdraw = GNUNET_TIME_absolute_hton
|
dki.issue.properties.expire_withdraw = GNUNET_TIME_absolute_hton
|
||||||
@ -558,7 +562,7 @@ test_melting (struct TALER_EXCHANGEDB_Session *session)
|
|||||||
struct TALER_EXCHANGEDB_LinkDataList *ldl;
|
struct TALER_EXCHANGEDB_LinkDataList *ldl;
|
||||||
struct TALER_EXCHANGEDB_LinkDataList *ldlp;
|
struct TALER_EXCHANGEDB_LinkDataList *ldlp;
|
||||||
struct TALER_DenominationSignature ev_sigs[MELT_NEW_COINS];
|
struct TALER_DenominationSignature ev_sigs[MELT_NEW_COINS];
|
||||||
unsigned int cnt;
|
unsigned int cnt;
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
@ -575,6 +579,7 @@ test_melting (struct TALER_EXCHANGEDB_Session *session)
|
|||||||
/* create a denomination (value: 1; fraction: 100) */
|
/* create a denomination (value: 1; fraction: 100) */
|
||||||
dkp = create_denom_key_pair (512,
|
dkp = create_denom_key_pair (512,
|
||||||
session,
|
session,
|
||||||
|
GNUNET_TIME_absolute_get (),
|
||||||
&value,
|
&value,
|
||||||
&fee_withdraw,
|
&fee_withdraw,
|
||||||
&fee_deposit,
|
&fee_deposit,
|
||||||
@ -645,6 +650,7 @@ test_melting (struct TALER_EXCHANGEDB_Session *session)
|
|||||||
{
|
{
|
||||||
new_dkp[cnt] = create_denom_key_pair (1024,
|
new_dkp[cnt] = create_denom_key_pair (1024,
|
||||||
session,
|
session,
|
||||||
|
GNUNET_TIME_absolute_get (),
|
||||||
&value,
|
&value,
|
||||||
&fee_withdraw,
|
&fee_withdraw,
|
||||||
&fee_deposit,
|
&fee_deposit,
|
||||||
@ -974,6 +980,54 @@ deposit_cb (void *cls,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test garbage collection.
|
||||||
|
*
|
||||||
|
* @param session DB session to use
|
||||||
|
* @return #GNUNET_OK on success
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
test_gc (struct TALER_EXCHANGEDB_Session *session)
|
||||||
|
{
|
||||||
|
struct DenomKeyPair *dkp;
|
||||||
|
struct GNUNET_TIME_Absolute now;
|
||||||
|
struct GNUNET_TIME_Absolute past;
|
||||||
|
struct TALER_EXCHANGEDB_DenominationKeyInformationP issue2;
|
||||||
|
|
||||||
|
now = GNUNET_TIME_absolute_get ();
|
||||||
|
past = GNUNET_TIME_absolute_subtract (now,
|
||||||
|
GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS,
|
||||||
|
4));
|
||||||
|
dkp = create_denom_key_pair (1024,
|
||||||
|
session,
|
||||||
|
past,
|
||||||
|
&value,
|
||||||
|
&fee_withdraw,
|
||||||
|
&fee_deposit,
|
||||||
|
&fee_refresh,
|
||||||
|
&fee_refund);
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
plugin->gc (plugin->cls))
|
||||||
|
{
|
||||||
|
GNUNET_break(0);
|
||||||
|
destroy_denom_key_pair (dkp);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
|
if (GNUNET_OK ==
|
||||||
|
plugin->get_denomination_info (plugin->cls,
|
||||||
|
session,
|
||||||
|
&dkp->pub,
|
||||||
|
&issue2))
|
||||||
|
{
|
||||||
|
GNUNET_break(0);
|
||||||
|
destroy_denom_key_pair (dkp);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
|
destroy_denom_key_pair (dkp);
|
||||||
|
return GNUNET_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main function that will be run by the scheduler.
|
* Main function that will be run by the scheduler.
|
||||||
*
|
*
|
||||||
@ -1093,7 +1147,9 @@ run (void *cls)
|
|||||||
value.fraction * 2,
|
value.fraction * 2,
|
||||||
value.currency));
|
value.currency));
|
||||||
result = 5;
|
result = 5;
|
||||||
dkp = create_denom_key_pair (1024, session,
|
dkp = create_denom_key_pair (1024,
|
||||||
|
session,
|
||||||
|
GNUNET_TIME_absolute_get (),
|
||||||
&value,
|
&value,
|
||||||
&fee_withdraw,
|
&fee_withdraw,
|
||||||
&fee_deposit,
|
&fee_deposit,
|
||||||
@ -1427,6 +1483,9 @@ run (void *cls)
|
|||||||
transaction_id_wt,
|
transaction_id_wt,
|
||||||
&cb_wtid_check,
|
&cb_wtid_check,
|
||||||
&cb_wtid_never));
|
&cb_wtid_never));
|
||||||
|
FAILIF (GNUNET_OK !=
|
||||||
|
test_gc (session));
|
||||||
|
|
||||||
result = 0;
|
result = 0;
|
||||||
|
|
||||||
drop:
|
drop:
|
||||||
|
@ -220,7 +220,7 @@ struct TALER_EXCHANGE_Keys
|
|||||||
*
|
*
|
||||||
* @param cls closure
|
* @param cls closure
|
||||||
* @param keys information about the various keys used
|
* @param keys information about the various keys used
|
||||||
* by the exchange
|
* by the exchange, NULL if /keys failed
|
||||||
*/
|
*/
|
||||||
typedef void
|
typedef void
|
||||||
(*TALER_EXCHANGE_CertificationCallback) (void *cls,
|
(*TALER_EXCHANGE_CertificationCallback) (void *cls,
|
||||||
@ -244,7 +244,8 @@ struct TALER_EXCHANGE_Handle;
|
|||||||
*
|
*
|
||||||
* @param ctx the context
|
* @param ctx the context
|
||||||
* @param url HTTP base URL for the exchange
|
* @param url HTTP base URL for the exchange
|
||||||
* @param cert_cb function to call with the exchange's certification information
|
* @param cert_cb function to call with the exchange's certification information,
|
||||||
|
* possibly called repeatedly if the information changes
|
||||||
* @param cert_cb_cls closure for @a cert_cb
|
* @param cert_cb_cls closure for @a cert_cb
|
||||||
* @param ... list of additional arguments, terminated by #TALER_EXCHANGE_OPTION_END.
|
* @param ... list of additional arguments, terminated by #TALER_EXCHANGE_OPTION_END.
|
||||||
* @return the exchange handle; NULL upon error
|
* @return the exchange handle; NULL upon error
|
||||||
@ -273,18 +274,28 @@ TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange);
|
|||||||
* @return the exchange's key set
|
* @return the exchange's key set
|
||||||
*/
|
*/
|
||||||
const struct TALER_EXCHANGE_Keys *
|
const struct TALER_EXCHANGE_Keys *
|
||||||
TALER_EXCHANGE_get_keys (const struct TALER_EXCHANGE_Handle *exchange);
|
TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain the keys from the exchange in the
|
* Check if our current response for /keys is valid, and if
|
||||||
* raw JSON format
|
* not, trigger /keys download.
|
||||||
|
*
|
||||||
|
* @param exchange exchange to check keys for
|
||||||
|
* @return until when the response is current, 0 if we are re-downloading
|
||||||
|
*/
|
||||||
|
struct GNUNET_TIME_Absolute
|
||||||
|
TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain the keys from the exchange in the raw JSON format.
|
||||||
*
|
*
|
||||||
* @param exchange the exchange handle
|
* @param exchange the exchange handle
|
||||||
* @return the exchange's keys in raw JSON
|
* @return the exchange's keys in raw JSON
|
||||||
*/
|
*/
|
||||||
json_t *
|
json_t *
|
||||||
TALER_EXCHANGE_get_keys_raw (const struct TALER_EXCHANGE_Handle *exchange);
|
TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user