/*
This file is part of TALER
Copyright (C) 2014 Christian Grothoff (and other contributing authors)
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, If not, see
*/
/**
* @file mint/mint_api.c
* @brief Implementation of the client interface to mint's HTTP API
* @author Sree Harsha Totakura
*/
#include "platform.h"
#include
#include
#include
#include "taler_mint_service.h"
#include "taler_signatures.h"
#define CURL_STRERROR(TYPE, FUNCTION, CODE) \
GNUNET_log (TYPE, "cURL function `%s' has failed at `%s:%d' with error: %s", \
FUNCTION, __FILE__, __LINE__, curl_easy_strerror (CODE));
/**
* Print JSON parsing related error information
*/
#define JSON_WARN(error) \
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \
"JSON parsing failed at %s:%u: %s (%s)", \
__FILE__, __LINE__, error.text, error.source)
/**
* Failsafe flag
*/
static int fail;
/**
* Context
*/
struct TALER_MINT_Context
{
/**
* CURL multi handle
*/
CURLM *multi;
/**
* CURL share handle
*/
CURLSH *share;
/**
* Perform task handle
*/
struct GNUNET_SCHEDULER_Task *perform_task;
};
/**
* Type of requests we currently have
*/
enum RequestType
{
/**
* No request
*/
REQUEST_TYPE_NONE,
/**
* Current request is to receive mint's keys
*/
REQUEST_TYPE_KEYSGET,
/**
* Current request is to submit a deposit permission and get its status
*/
REQUEST_TYPE_DEPOSIT
};
/**
* Handle to the mint
*/
struct TALER_MINT_Handle
{
/**
* The context of this handle
*/
struct TALER_MINT_Context *ctx;
/**
* The hostname of the mint
*/
char *hostname;
/**
* The CURL handle
*/
CURL *curl;
/**
* Error buffer for CURL
*/
char emsg[CURL_ERROR_SIZE];
/**
* Download buffer
*/
void *buf;
/**
* The currently active request
*/
union {
/**
* Used to denote no request if set to NULL
*/
void *none;
/**
* Denom keys get request if REQUEST_TYPE_KEYSGET
*/
struct TALER_MINT_KeysGetHandle *keys_get;
/**
* Deposit request if REQUEST_TYPE_DEPOSIT
*/
struct TALER_MINT_DepositHandle *deposit;
} req;
/**
* The size of the download buffer
*/
size_t buf_size;
/**
* Active request type
*/
enum RequestType req_type;
/**
* The service port of the mint
*/
uint16_t port;
/**
* Are we connected to the mint?
*/
uint8_t connected;
};
/**
* A handle to get the keys of a mint
*/
struct TALER_MINT_KeysGetHandle
{
/**
* The connection to mint this request handle will use
*/
struct TALER_MINT_Handle *mint;
/**
* The url for this handle
*/
char *url;
TALER_MINT_KeysGetCallback cb;
void *cls;
TALER_MINT_ContinuationCallback cont_cb;
void *cont_cls;
};
/**
* A handle to submit a deposit permission and get its status
*/
struct TALER_MINT_DepositHandle
{
/**
*The connection to mint this request handle will use
*/
struct TALER_MINT_Handle *mint;
/**
* The url for this handle
*/
char *url;
TALER_MINT_DepositResultCallback cb;
void *cls;
char *json_enc;
struct curl_slist *headers;
};
/**
* Parses the timestamp encoded as ASCII string as UNIX timstamp.
*
* @param abs successfully parsed timestamp will be returned thru this parameter
* @param tstamp_enc the ASCII encoding of the timestamp
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
static int
parse_timestamp (struct GNUNET_TIME_Absolute *abs, const char *tstamp_enc)
{
unsigned long tstamp;
if (1 != sscanf (tstamp_enc, "%lu", &tstamp))
return GNUNET_SYSERR;
*abs = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get_zero_ (),
GNUNET_TIME_relative_multiply
(GNUNET_TIME_UNIT_SECONDS, tstamp));
return GNUNET_OK;
}
#define EXITIF(cond) \
do { \
if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
} while (0)
static int
parse_json_signkey (struct TALER_MINT_SigningPublicKey **_sign_key,
json_t *sign_key_obj,
struct GNUNET_CRYPTO_EddsaPublicKey *master_key)
{
json_t *valid_from_obj;
json_t *valid_until_obj;
json_t *key_obj;
json_t *sig_obj;
const char *valid_from_enc;
const char *valid_until_enc;
const char *key_enc;
const char *sig_enc;
struct TALER_MINT_SigningPublicKey *sign_key;
struct TALER_MINT_SignKeyIssue sign_key_issue;
struct GNUNET_CRYPTO_EddsaSignature sig;
struct GNUNET_TIME_Absolute valid_from;
struct GNUNET_TIME_Absolute valid_until;
EXITIF (JSON_OBJECT != json_typeof (sign_key_obj));
EXITIF (NULL == (valid_from_obj = json_object_get (sign_key_obj,
"stamp_start")));
EXITIF (NULL == (valid_until_obj = json_object_get (sign_key_obj,
"stamp_expire")));
EXITIF (NULL == (key_obj = json_object_get (sign_key_obj, "key")));
EXITIF (NULL == (sig_obj = json_object_get (sign_key_obj, "master_sig")));
EXITIF (NULL == (valid_from_enc = json_string_value (valid_from_obj)));
EXITIF (NULL == (valid_until_enc = json_string_value (valid_until_obj)));
EXITIF (NULL == (key_enc = json_string_value (key_obj)));
EXITIF (NULL == (sig_enc = json_string_value (sig_obj)));
EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_from,
valid_from_enc));
EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_until,
valid_until_enc));
EXITIF (52 != strlen (key_enc)); /* strlen(base32(char[32])) = 52 */
EXITIF (103 != strlen (sig_enc)); /* strlen(base32(char[64])) = 103 */
EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (sig_enc, 103,
&sig, sizeof (sig)));
(void) memset (&sign_key_issue, 0, sizeof (sign_key_issue));
EXITIF (GNUNET_SYSERR ==
GNUNET_CRYPTO_eddsa_public_key_from_string (key_enc,
52,
&sign_key_issue.signkey_pub));
sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNKEY);
sign_key_issue.purpose.size =
htonl (sizeof (sign_key_issue)
- offsetof (struct TALER_MINT_SignKeyIssue, purpose));
sign_key_issue.master_pub = *master_key;
sign_key_issue.start = GNUNET_TIME_absolute_hton (valid_from);
sign_key_issue.expire = GNUNET_TIME_absolute_hton (valid_until);
EXITIF (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNKEY,
&sign_key_issue.purpose,
&sig,
master_key));
sign_key = GNUNET_new (struct TALER_MINT_SigningPublicKey);
sign_key->valid_from = valid_from;
sign_key->valid_until = valid_until;
sign_key->key = sign_key_issue.signkey_pub;
*_sign_key = sign_key;
return GNUNET_OK;
EXITIF_exit:
return GNUNET_SYSERR;
}
static int
parse_json_amount (json_t *amount_obj, struct TALER_Amount *amt)
{
json_t *obj;
const char *currency_str;
int value;
int fraction;
EXITIF (NULL == (obj = json_object_get (amount_obj, "currency")));
EXITIF (NULL == (currency_str = json_string_value (obj)));
EXITIF (NULL == (obj = json_object_get (amount_obj, "value")));
EXITIF (JSON_INTEGER != json_typeof (obj));
EXITIF (0 > (value = json_integer_value (obj)));
EXITIF (NULL == (obj = json_object_get (amount_obj, "fraction")));
EXITIF (JSON_INTEGER != json_typeof (obj));
EXITIF (0 > (fraction = json_integer_value (obj)));
(void) memset (amt->currency, 0, sizeof (amt->currency));
(void) strncpy (amt->currency, currency_str, sizeof (amt->currency) - 1);
amt->value = (uint32_t) value;
amt->fraction = (uint32_t) fraction;
return GNUNET_OK;
EXITIF_exit:
return GNUNET_SYSERR;
}
/* FIXME: avoid useless ** for _denom_key! */
static int
parse_json_denomkey (struct TALER_MINT_DenomPublicKey **_denom_key,
json_t *denom_key_obj,
struct GNUNET_CRYPTO_EddsaPublicKey *master_key)
{
json_t *obj;
const char *sig_enc;
const char *deposit_valid_until_enc;
const char *withdraw_valid_until_enc;
const char *valid_from_enc;
const char *key_enc;
char *buf;
size_t buf_size;
struct TALER_MINT_DenomPublicKey *denom_key;
struct GNUNET_TIME_Absolute valid_from;
struct GNUNET_TIME_Absolute withdraw_valid_until;
struct GNUNET_TIME_Absolute deposit_valid_until;
struct TALER_Amount value;
struct TALER_Amount fee_withdraw;
struct TALER_Amount fee_deposit;
struct TALER_Amount fee_refresh;
struct TALER_MINT_DenomKeyIssue denom_key_issue;
struct GNUNET_CRYPTO_rsa_PublicKey *pk;
struct GNUNET_CRYPTO_EddsaSignature sig;
EXITIF (JSON_OBJECT != json_typeof (denom_key_obj));
EXITIF (NULL == (obj = json_object_get (denom_key_obj, "master_sig")));
EXITIF (NULL == (sig_enc = json_string_value (obj)));
EXITIF (103 != strlen (sig_enc));
EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (sig_enc, 103,
&sig, sizeof (sig)));
EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_expire_deposit")));
EXITIF (NULL == (deposit_valid_until_enc = json_string_value (obj)));
EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_expire_withdraw")));
EXITIF (NULL == (withdraw_valid_until_enc = json_string_value (obj)));
EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_start")));
EXITIF (NULL == (valid_from_enc = json_string_value (obj)));
EXITIF (NULL == (obj = json_object_get (denom_key_obj, "denom_pub")));
EXITIF (NULL == (key_enc = json_string_value (obj)));
EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_from, valid_from_enc));
EXITIF (GNUNET_SYSERR == parse_timestamp (&withdraw_valid_until,
withdraw_valid_until_enc));
EXITIF (GNUNET_SYSERR == parse_timestamp (&deposit_valid_until,
deposit_valid_until_enc));
memset (&denom_key_issue, 0, sizeof (denom_key_issue));
buf_size = (strlen (key_enc) * 5) / 8;
buf = GNUNET_malloc (buf_size);
EXITIF (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (key_enc, strlen (key_enc),
buf,
buf_size));
pk = GNUNET_CRYPTO_rsa_public_key_decode (buf, buf_size);
GNUNET_free (buf);
EXITIF (NULL == pk);
GNUNET_CRYPTO_rsa_public_key_hash (pk,
&denom_key_issue.denom_hash);
EXITIF (NULL == (obj = json_object_get (denom_key_obj, "value")));
EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &value));
EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_withdraw")));
EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_withdraw));
EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_deposit")));
EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_deposit));
EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_refresh")));
EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_refresh));
denom_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOM);
denom_key_issue.purpose.size = htonl
(sizeof (struct TALER_MINT_DenomKeyIssue) -
offsetof (struct TALER_MINT_DenomKeyIssue, purpose));
denom_key_issue.master = *master_key;
denom_key_issue.start = GNUNET_TIME_absolute_hton (valid_from);
denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (withdraw_valid_until);
denom_key_issue.expire_spend = GNUNET_TIME_absolute_hton (deposit_valid_until);
denom_key_issue.value = TALER_amount_hton (value);
denom_key_issue.fee_withdraw = TALER_amount_hton (fee_withdraw);
denom_key_issue.fee_deposit = TALER_amount_hton (fee_deposit);
denom_key_issue.fee_refresh = TALER_amount_hton (fee_refresh);
EXITIF (GNUNET_SYSERR ==
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOM,
&denom_key_issue.purpose,
&sig,
master_key));
denom_key = GNUNET_new (struct TALER_MINT_DenomPublicKey);
denom_key->key = pk;
denom_key->valid_from = valid_from;
denom_key->withdraw_valid_until = withdraw_valid_until;
denom_key->deposit_valid_until = deposit_valid_until;
denom_key->value = value;
denom_key->fee_withdraw = fee_withdraw;
denom_key->fee_deposit = fee_deposit;
denom_key->fee_refresh = fee_refresh;
*_denom_key = denom_key;
return GNUNET_OK;
EXITIF_exit:
return GNUNET_SYSERR;
}
static int
parse_response_keys_get (const char *in, size_t size,
struct TALER_MINT_SigningPublicKey ***_sign_keys,
unsigned int *_n_sign_keys,
struct TALER_MINT_DenomPublicKey ***_denom_keys,
unsigned int *_n_denom_keys)
{
json_t *resp_obj;
struct TALER_MINT_DenomPublicKey **denom_keys;
struct GNUNET_CRYPTO_EddsaPublicKey master_key;
struct GNUNET_TIME_Absolute list_issue_date;
struct TALER_MINT_SigningPublicKey **sign_keys;
unsigned int n_denom_keys;
unsigned int n_sign_keys;
json_error_t error;
unsigned int index;
int OK;
denom_keys = NULL;
n_denom_keys = 0;
sign_keys = NULL;
n_sign_keys = 0;
OK = 0;
resp_obj = json_loadb (in, size,
JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK,
&error);
if (NULL == resp_obj)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unable to parse received data as JSON object\n");
return GNUNET_SYSERR;
}
EXITIF (JSON_OBJECT != json_typeof (resp_obj));
{
/* parse the master public key */
json_t *master_key_obj;
const char *master_key_enc;
EXITIF (NULL == (master_key_obj = json_object_get (resp_obj, "master_pub")));
EXITIF (NULL == (master_key_enc = json_string_value (master_key_obj)));
EXITIF (52 != strlen (master_key_enc)); /* strlen(base32(char[32])) = 52 */
EXITIF (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_public_key_from_string (master_key_enc,
52,
&master_key));
}
{
/* parse the issue date of the response */
json_t *list_issue_date_obj;
const char *tstamp_enc;
EXITIF (NULL == (list_issue_date_obj =
json_object_get(resp_obj, "list_issue_date")));
EXITIF (NULL == (tstamp_enc = json_string_value (list_issue_date_obj)));
EXITIF (GNUNET_SYSERR == parse_timestamp (&list_issue_date, tstamp_enc));
}
{
/* parse the signing keys */
json_t *sign_keys_array;
json_t *sign_key_obj;
EXITIF (NULL == (sign_keys_array =
json_object_get (resp_obj, "signkeys")));
EXITIF (JSON_ARRAY != json_typeof (sign_keys_array));
EXITIF (0 == (n_sign_keys = json_array_size (sign_keys_array)));
sign_keys = GNUNET_malloc (sizeof (struct TALER_MINT_SigningPublicKey *)
* (n_sign_keys + 1));
index = 0;
json_array_foreach (sign_keys_array, index, sign_key_obj) {
EXITIF (GNUNET_SYSERR == parse_json_signkey (&sign_keys[index],
sign_key_obj,
&master_key));
}
}
{
/* parse the denomination keys */
json_t *denom_keys_array;
json_t *denom_key_obj;
EXITIF (NULL == (denom_keys_array = json_object_get (resp_obj, "denoms")));
EXITIF (JSON_ARRAY != json_typeof (denom_keys_array));
EXITIF (0 == (n_denom_keys = json_array_size (denom_keys_array)));
denom_keys = GNUNET_malloc (sizeof (struct TALER_MINT_DenomPublicKey *)
* (n_denom_keys + 1));
index = 0;
json_array_foreach (denom_keys_array, index, denom_key_obj) {
EXITIF (GNUNET_SYSERR == parse_json_denomkey (&denom_keys[index],
denom_key_obj,
&master_key));
}
}
OK = 1;
EXITIF_exit:
json_decref (resp_obj);
if (!OK)
{
if (NULL != sign_keys)
{
for (index=0; NULL != sign_keys[index]; index++)
GNUNET_free_non_null (sign_keys[index]);
GNUNET_free (sign_keys);
}
if (NULL != denom_keys)
{
for (index=0; NULL != denom_keys[index]; index++)
GNUNET_free_non_null (denom_keys[index]);
GNUNET_free (denom_keys);
}
return GNUNET_SYSERR;
}
*_sign_keys = sign_keys;
*_n_sign_keys = n_sign_keys;
*_denom_keys = denom_keys;
*_n_denom_keys = n_denom_keys;
return GNUNET_OK;
}
int
parse_deposit_response (void *buf, size_t size, int *r_status, json_t **r_obj)
{
json_t *obj;
const char *status_str;
json_error_t error;
status_str = NULL;
obj = NULL;
obj = json_loadb (buf, size,
JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
if (NULL == obj)
{
JSON_WARN (error);
return GNUNET_SYSERR;
}
EXITIF (-1 == json_unpack (obj, "{s:s}", "status", &status_str));
LOG_DEBUG ("Received deposit response: %s from mint\n", status_str);
if (0 == strcmp ("DEPOSIT_OK", status_str))
*r_status = 1;
else if (0 == strcmp ("DEPOSIT_QUEUED", status_str))
*r_status = 2;
else
*r_status = 0;
*r_obj = obj;
return GNUNET_OK;
EXITIF_exit:
json_decref (obj);
return GNUNET_SYSERR;
}
#undef EXITIF
static void
mint_connect (struct TALER_MINT_Handle *mint)
{
struct TALER_MINT_Context *ctx = mint->ctx;
GNUNET_assert (0 == mint->connected);
GNUNET_assert (CURLM_OK == curl_multi_add_handle (ctx->multi, mint->curl));
mint->connected = GNUNET_YES;
}
static void
mint_disconnect (struct TALER_MINT_Handle *mint)
{
struct TALER_MINT_Context *ctx = mint->ctx;
GNUNET_assert (GNUNET_YES == mint->connected);
GNUNET_break (CURLM_OK == curl_multi_remove_handle (ctx->multi,
mint->curl));
mint->connected = GNUNET_NO;
GNUNET_free_non_null (mint->buf);
mint->buf = NULL;
mint->buf_size = 0;
mint->req_type = REQUEST_TYPE_NONE;
mint->req.none = NULL;
}
static void
cleanup_keys_get (struct TALER_MINT_KeysGetHandle *gh)
{
GNUNET_free (gh->url);
GNUNET_free (gh);
}
static void
cleanup_deposit (struct TALER_MINT_DepositHandle *dh)
{
curl_slist_free_all (dh->headers);
GNUNET_free_non_null (dh->json_enc);
GNUNET_free (dh->url);
GNUNET_free (dh);
}
static void
request_failed (struct TALER_MINT_Handle *mint, long resp_code)
{
switch (mint->req_type)
{
case REQUEST_TYPE_NONE:
GNUNET_assert (0);
break;
case REQUEST_TYPE_KEYSGET:
{
struct TALER_MINT_KeysGetHandle *gh = mint->req.keys_get;
TALER_MINT_ContinuationCallback cont_cb;
void *cont_cls;
GNUNET_assert (NULL != gh);
cont_cb = gh->cont_cb;
cont_cls = gh->cont_cls;
cleanup_keys_get (gh);
mint_disconnect (mint);
cont_cb (cont_cls, mint->emsg);
}
break;
case REQUEST_TYPE_DEPOSIT:
{
struct TALER_MINT_DepositHandle *dh = mint->req.deposit;
TALER_MINT_DepositResultCallback cb = dh->cb;
void *cls = dh->cls;
GNUNET_assert (NULL != dh);
cleanup_deposit (dh);
mint_disconnect (mint);
cb (cls, 0, NULL, mint->emsg);
}
break;
}
}
static void
request_succeeded (struct TALER_MINT_Handle *mint, long resp_code)
{
char *emsg;
emsg = NULL;
switch (mint->req_type)
{
case REQUEST_TYPE_NONE:
GNUNET_assert (0);
break;
case REQUEST_TYPE_KEYSGET:
{
struct TALER_MINT_KeysGetHandle *gh = mint->req.keys_get;
TALER_MINT_ContinuationCallback cont_cb;
void *cont_cls;
struct TALER_MINT_SigningPublicKey **sign_keys;
struct TALER_MINT_DenomPublicKey **denom_keys;
unsigned int n_sign_keys;
unsigned int n_denom_keys;
GNUNET_assert (NULL != gh);
cont_cb = gh->cont_cb;
cont_cls = gh->cont_cls;
if (200 == resp_code)
{
/* parse JSON object from the mint->buf which is of size mint->buf_size */
if (GNUNET_OK ==
parse_response_keys_get (mint->buf, mint->buf_size,
&sign_keys, &n_sign_keys,
&denom_keys, &n_denom_keys))
gh->cb (gh->cls, sign_keys, denom_keys);
else
emsg = GNUNET_strdup ("Error parsing response");
}
else
GNUNET_asprintf (&emsg, "Failed with response code: %ld", resp_code);
cleanup_keys_get (gh);
mint_disconnect (mint);
cont_cb (cont_cls, emsg);
}
break;
case REQUEST_TYPE_DEPOSIT:
{
struct TALER_MINT_DepositHandle *dh = mint->req.deposit;
TALER_MINT_DepositResultCallback cb;
void *cls;
int status;
json_t *obj;
GNUNET_assert (NULL != dh);
obj = NULL;
cb = dh->cb;
cls = dh->cls;
status = 0;
if (200 == resp_code)
{
/* parse JSON object from the mint->buf which is of size mint->buf_size */
if (GNUNET_OK !=
parse_deposit_response (mint->buf, mint->buf_size,
&status, &obj))
emsg = GNUNET_strdup ("Error parsing response");
}
else
GNUNET_asprintf (&emsg, "Failed with response code: %ld", resp_code);
cleanup_deposit (dh);
mint_disconnect (mint);
cb (cls, status, obj, emsg);
}
break;
}
GNUNET_free_non_null (emsg);
}
static void
do_perform (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
static void
perform (struct TALER_MINT_Context *ctx)
{
fd_set fd_rs;
fd_set fd_ws;
struct GNUNET_NETWORK_FDSet rs;
struct GNUNET_NETWORK_FDSet ws;
CURLMsg *cmsg;
struct TALER_MINT_Handle *mint;
long timeout;
long resp_code;
static unsigned int n_old;
int n_running;
int n_completed;
int max_fd;
n_completed = 0;
curl_multi_perform (ctx->multi, &n_running);
GNUNET_assert (0 <= n_running);
if ((0 == n_running) || (n_running < n_old))
{
/* some requests were completed -- handle them */
while (NULL != (cmsg = curl_multi_info_read (ctx->multi, &n_completed)))
{
GNUNET_break (CURLMSG_DONE == cmsg->msg); /* curl only has CURLMSG_DONE */
GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle,
CURLINFO_PRIVATE,
(char *) &mint));
GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle,
CURLINFO_RESPONSE_CODE,
&resp_code));
GNUNET_assert (ctx == mint->ctx); /* did we get the correct one? */
if (CURLE_OK == cmsg->data.result)
request_succeeded (mint, resp_code);
else
request_failed (mint, resp_code);
}
}
n_old = n_running;
/* reschedule perform() */
if (0 != n_old)
{
FD_ZERO (&fd_rs);
FD_ZERO (&fd_ws);
GNUNET_assert (CURLM_OK == curl_multi_fdset (ctx->multi,
&fd_rs,
&fd_ws,
NULL,
&max_fd));
if (-1 == max_fd)
{
ctx->perform_task = GNUNET_SCHEDULER_add_delayed
(GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100),
&do_perform, ctx);
return;
}
GNUNET_assert (CURLM_OK == curl_multi_timeout (ctx->multi, &timeout));
if (-1 == timeout)
{
timeout = 1000 * 60 * 5;
}
GNUNET_NETWORK_fdset_zero (&rs);
GNUNET_NETWORK_fdset_zero (&ws);
GNUNET_NETWORK_fdset_copy_native (&rs, &fd_rs, max_fd + 1);
GNUNET_NETWORK_fdset_copy_native (&ws, &fd_ws, max_fd + 1);
ctx->perform_task = GNUNET_SCHEDULER_add_select
(GNUNET_SCHEDULER_PRIORITY_KEEP,
GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, timeout),
&rs, &ws,
&do_perform, ctx);
}
}
static void
do_perform (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
{
struct TALER_MINT_Context *ctx = cls;
GNUNET_assert (NULL != ctx->perform_task);
ctx->perform_task = NULL;
perform (ctx);
}
static void
perform_now (struct TALER_MINT_Context *ctx)
{
if (NULL != ctx->perform_task)
{
GNUNET_SCHEDULER_cancel (ctx->perform_task);
ctx->perform_task = NULL;
}
ctx->perform_task = GNUNET_SCHEDULER_add_now (&do_perform, ctx);
}
/* This function gets called by libcurl as soon as there is data received that */
/* needs to be saved. The size of the data pointed to by ptr is size */
/* multiplied with nmemb, it will not be zero terminated. Return the number */
/* of bytes actually taken care of. If that amount differs from the amount passed */
/* to your function, it'll signal an error to the library. This will abort the */
/* transfer and return CURLE_WRITE_ERROR. */
/* From 7.18.0, the function can return CURL_WRITEFUNC_PAUSE which then will */
/* cause writing to this connection to become paused. See */
/* curl_easy_pause(3) for further details. */
/* This function may be called with zero bytes data if the transferred file is */
/* empty. */
/* Set this option to NULL to get the internal default function. The internal */
/* default function will write the data to the FILE * given with */
/* CURLOPT_WRITEDATA. */
/* Set the userdata argument with the CURLOPT_WRITEDATA option. */
/* The callback function will be passed as much data as possible in all invokes, */
/* but you cannot possibly make any assumptions. It may be one byte, it may be */
/* thousands. The maximum amount of body data that can be passed to the write */
/* callback is defined in the curl.h header file: CURL_MAX_WRITE_SIZE (the usual */
/* default is 16K). If you however have CURLOPT_HEADER set, which sends */
/* header data to the write callback, you can get up to */
/* CURL_MAX_HTTP_HEADER bytes of header data passed into it. This usually */
/* means 100K. */
static size_t
download (char *bufptr, size_t size, size_t nitems, void *cls)
{
struct TALER_MINT_Handle *mint = cls;
size_t msize;
void *buf;
if (0 == size * nitems)
{
/* file is empty */
return 0;
}
msize = size * nitems;
mint->buf = GNUNET_realloc (mint->buf, mint->buf_size + msize);
buf = mint->buf + mint->buf_size;
memcpy (buf, bufptr, msize);
mint->buf_size += msize;
return msize;
}
/**
* Initialise a connection to the mint.
*
* @param ctx the context
* @param hostname the hostname of the mint
* @param port the point where the mint's HTTP service is running.
* @param mint_key the public key of the mint. This is used to verify the
* responses of the mint.
* @return the mint handle; NULL upon error
*/
struct TALER_MINT_Handle *
TALER_MINT_connect (struct TALER_MINT_Context *ctx,
const char *hostname,
uint16_t port,
struct GNUNET_CRYPTO_EddsaPublicKey *mint_key)
{
struct TALER_MINT_Handle *mint;
mint = GNUNET_new (struct TALER_MINT_Handle);
mint->ctx = ctx;
mint->hostname = GNUNET_strdup (hostname);
mint->port = (0 != port) ? port : 80;
mint->curl = curl_easy_init ();
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (mint->curl, CURLOPT_SHARE, ctx->share));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (mint->curl, CURLOPT_ERRORBUFFER, mint->emsg));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (mint->curl, CURLOPT_WRITEFUNCTION, &download));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (mint->curl, CURLOPT_WRITEDATA, mint));
GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_PRIVATE, mint));
return mint;
}
/**
* Disconnect from the mint
*
* @param mint the mint handle
*/
void
TALER_MINT_disconnect (struct TALER_MINT_Handle *mint)
{
if (GNUNET_YES == mint->connected)
mint_disconnect (mint);
curl_easy_cleanup (mint->curl);
GNUNET_free (mint->hostname);
GNUNET_free (mint);
}
/**
* Get the signing and denomination key of the mint.
*
* @param mint handle to the mint
* @param cb the callback to call with each retrieved denomination key
* @param cls closure for the above callback
* @param cont_cb the callback to call after completing this asynchronous call
* @param cont_cls the closure for the continuation callback
* @return a handle to this asynchronous call; NULL upon eror
*/
struct TALER_MINT_KeysGetHandle *
TALER_MINT_keys_get (struct TALER_MINT_Handle *mint,
TALER_MINT_KeysGetCallback cb, void *cls,
TALER_MINT_ContinuationCallback cont_cb, void *cont_cls)
{
struct TALER_MINT_KeysGetHandle *gh;
GNUNET_assert (REQUEST_TYPE_NONE == mint->req_type);
gh = GNUNET_new (struct TALER_MINT_KeysGetHandle);
gh->mint = mint;
mint->req_type = REQUEST_TYPE_KEYSGET;
mint->req.keys_get = gh;
gh->cb = cb;
gh->cls = cls;
gh->cont_cb = cont_cb;
gh->cont_cls = cont_cls;
GNUNET_asprintf (&gh->url, "http://%s:%hu/keys", mint->hostname, mint->port);
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (mint->curl, CURLOPT_URL, gh->url));
if (GNUNET_NO == mint->connected)
mint_connect (mint);
perform_now (mint->ctx);
return gh;
}
/**
* Cancel the asynchronous call initiated by TALER_MINT_keys_get(). This
* should not be called if either of the @a TALER_MINT_KeysGetCallback or
* @a TALER_MINT_ContinuationCallback passed to TALER_MINT_keys_get() have
* been called.
*
* @param get the handle for retrieving the keys
*/
void
TALER_MINT_keys_get_cancel (struct TALER_MINT_KeysGetHandle *get)
{
struct TALER_MINT_Handle *mint = get->mint;
mint_disconnect (mint);
cleanup_keys_get (get);
}
/**
* Submit a deposit permission to the mint and get the mint's response
*
* @param mint the mint handle
* @param cb the callback to call when a reply for this request is available
* @param cls closure for the above callback
* @param deposit_obj the deposit permission received from the customer along
* with the wireformat JSON object
* @return a handle for this request; NULL if the JSON object could not be
* parsed or is of incorrect format or any other error. In this case,
* the callback is not called.
*/
struct TALER_MINT_DepositHandle *
TALER_MINT_deposit_submit_json (struct TALER_MINT_Handle *mint,
TALER_MINT_DepositResultCallback cb,
void *cls,
json_t *deposit_obj)
{
struct TALER_MINT_DepositHandle *dh;
GNUNET_assert (REQUEST_TYPE_NONE == mint->req_type);
dh = GNUNET_new (struct TALER_MINT_DepositHandle);
dh->mint = mint;
mint->req_type = REQUEST_TYPE_DEPOSIT;
mint->req.deposit = dh;
dh->cb = cb;
dh->cls = cls;
GNUNET_asprintf (&dh->url, "http://%s:%hu/deposit", mint->hostname, mint->port);
GNUNET_assert (NULL != (dh->json_enc = json_dumps (deposit_obj, JSON_COMPACT)));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (mint->curl, CURLOPT_URL, dh->url));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (mint->curl, CURLOPT_POSTFIELDS,
dh->json_enc));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (mint->curl, CURLOPT_POSTFIELDSIZE,
strlen (dh->json_enc)));
GNUNET_assert (NULL != (dh->headers =
curl_slist_append (dh->headers, "Content-Type: application/json")));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (mint->curl, CURLOPT_HTTPHEADER, dh->headers));
if (GNUNET_NO == mint->connected)
mint_connect (mint);
perform_now (mint->ctx);
return dh;
}
/**
* Cancel a deposit permission request. This function cannot be used on a
* request handle if a response is already served for it.
*
* @param the deposit permission request handle
*/
void
TALER_MINT_deposit_submit_cancel (struct TALER_MINT_DepositHandle *deposit)
{
struct TALER_MINT_Handle *mint = deposit->mint;
mint_disconnect (mint);
cleanup_deposit (deposit);
}
/**
* Initialise this library. This function should be called before using any of
* the following functions.
*
* @return library context
*/
struct TALER_MINT_Context *
TALER_MINT_init ()
{
struct TALER_MINT_Context *ctx;
CURLM *multi;
CURLSH *share;
if (fail)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "cURL was not initialised properly\n");
return NULL;
}
if (NULL == (multi = curl_multi_init ()))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot create a cURL multi handle\n");
return NULL;
}
if (NULL == (share = curl_share_init ()))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot create a cURL share handle\n");
return NULL;
}
ctx = GNUNET_new (struct TALER_MINT_Context);
ctx->multi = multi;
ctx->share = share;
return ctx;
}
/**
* Cleanup library initialisation resources. This function should be called
* after using this library to cleanup the resources occupied during library's
* initialisation.
*
* @param ctx the library context
*/
void
TALER_MINT_cleanup (struct TALER_MINT_Context *ctx)
{
curl_share_cleanup (ctx->share);
curl_multi_cleanup (ctx->multi);
if (NULL != ctx->perform_task)
{
GNUNET_break (0); /* investigate why this happens */
GNUNET_SCHEDULER_cancel (ctx->perform_task);
}
GNUNET_free (ctx);
}
__attribute__ ((constructor))
void
TALER_MINT_constructor__ (void)
{
CURLcode ret;
if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT)))
{
CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR, "curl_global_init", ret);
fail = 1;
}
}
__attribute__ ((destructor))
void
TALER_MINT_destructor__ (void)
{
if (fail)
return;
curl_global_cleanup ();
}