towards support for new reserve history/status APIs

This commit is contained in:
Christian Grothoff 2022-03-20 13:20:45 +01:00
parent dee45bf022
commit 427417b835
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
19 changed files with 1482 additions and 577 deletions

View File

@ -280,31 +280,6 @@ struct SigningKey
}; };
/**
* Set of global fees (and options) for a time range.
*/
struct GlobalFee
{
/**
* Kept in a DLL.
*/
struct GlobalFee *next;
/**
* Kept in a DLL.
*/
struct GlobalFee *prev;
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
struct GNUNET_TIME_Relative purse_timeout;
struct GNUNET_TIME_Relative kyc_timeout;
struct GNUNET_TIME_Relative history_expiration;
struct TALER_MasterSignatureP master_sig;
struct TALER_GlobalFeeSet fees;
uint32_t purse_account_limit;
};
struct TEH_KeyStateHandle struct TEH_KeyStateHandle
{ {
@ -324,12 +299,12 @@ struct TEH_KeyStateHandle
/** /**
* Head of DLL of our global fees. * Head of DLL of our global fees.
*/ */
struct GlobalFee *gf_head; struct TEH_GlobalFee *gf_head;
/** /**
* Tail of DLL of our global fees. * Tail of DLL of our global fees.
*/ */
struct GlobalFee *gf_tail; struct TEH_GlobalFee *gf_tail;
/** /**
* json array with the auditors of this exchange. Contains exactly * json array with the auditors of this exchange. Contains exactly
@ -1215,7 +1190,7 @@ static void
destroy_key_state (struct TEH_KeyStateHandle *ksh, destroy_key_state (struct TEH_KeyStateHandle *ksh,
bool free_helper) bool free_helper)
{ {
struct GlobalFee *gf; struct TEH_GlobalFee *gf;
clear_response_cache (ksh); clear_response_cache (ksh);
while (NULL != (gf = ksh->gf_head)) while (NULL != (gf = ksh->gf_head))
@ -2282,9 +2257,9 @@ global_fee_info_cb (
const struct TALER_MasterSignatureP *master_sig) const struct TALER_MasterSignatureP *master_sig)
{ {
struct TEH_KeyStateHandle *ksh = cls; struct TEH_KeyStateHandle *ksh = cls;
struct GlobalFee *gf; struct TEH_GlobalFee *gf;
gf = GNUNET_new (struct GlobalFee); gf = GNUNET_new (struct TEH_GlobalFee);
gf->start_date = start_date; gf->start_date = start_date;
gf->end_date = end_date; gf->end_date = end_date;
gf->fees = *fees; gf->fees = *fees;
@ -2517,9 +2492,30 @@ TEH_keys_get_state (void)
} }
const struct TEH_GlobalFee *
TEH_keys_global_fee_by_time (
struct TEH_KeyStateHandle *ksh,
struct GNUNET_TIME_Timestamp ts)
{
for (const struct TEH_GlobalFee *gf = ksh->gf_head;
NULL != gf;
gf = gf->next)
{
if (GNUNET_TIME_timestamp_cmp (ts,
>=,
gf->start_date) &&
GNUNET_TIME_timestamp_cmp (ts,
<,
gf->end_date))
return gf;
}
return NULL;
}
struct TEH_DenominationKey * struct TEH_DenominationKey *
TEH_keys_denomination_by_hash (const struct TEH_keys_denomination_by_hash (
TALER_DenominationHashP *h_denom_pub, const struct TALER_DenominationHashP *h_denom_pub,
struct MHD_Connection *conn, struct MHD_Connection *conn,
MHD_RESULT *mret) MHD_RESULT *mret)
{ {

View File

@ -82,6 +82,64 @@ struct TEH_DenominationKey
}; };
/**
* Set of global fees (and options) for a time range.
*/
struct TEH_GlobalFee
{
/**
* Kept in a DLL.
*/
struct TEH_GlobalFee *next;
/**
* Kept in a DLL.
*/
struct TEH_GlobalFee *prev;
/**
* Beginning of the validity period (inclusive).
*/
struct GNUNET_TIME_Timestamp start_date;
/**
* End of the validity period (exclusive).
*/
struct GNUNET_TIME_Timestamp end_date;
/**
* How long do unmerged purses stay around at most?
*/
struct GNUNET_TIME_Relative purse_timeout;
/**
* How long do we keep accounts without KYC?
*/
struct GNUNET_TIME_Relative kyc_timeout;
/**
* What is the longest history we return?
*/
struct GNUNET_TIME_Relative history_expiration;
/**
* Signature affirming these details.
*/
struct TALER_MasterSignatureP master_sig;
/**
* Fee structure for operations that do not depend
* on a denomination or wire method.
*/
struct TALER_GlobalFeeSet fees;
/**
* Number of free purses per account.
*/
uint32_t purse_account_limit;
};
/** /**
* Snapshot of the (coin and signing) keys (including private keys) of * Snapshot of the (coin and signing) keys (including private keys) of
* the exchange. There can be multiple instances of this struct, as it is * the exchange. There can be multiple instances of this struct, as it is
@ -129,6 +187,20 @@ void
TEH_keys_update_states (void); TEH_keys_update_states (void);
/**
* Look up global fee structure by @a ts.
*
* @param ksh key state state to look in
* @param ts timestamp to lookup global fees at
* @return the global fee details, or
* NULL if none are configured for @a ts
*/
const struct TEH_GlobalFee *
TEH_keys_global_fee_by_time (
struct TEH_KeyStateHandle *ksh,
struct GNUNET_TIME_Timestamp ts);
/** /**
* Look up the issue for a denom public key. Note that the result * Look up the issue for a denom public key. Note that the result
* must only be used in this thread and only until another key or * must only be used in this thread and only until another key or
@ -141,8 +213,8 @@ TEH_keys_update_states (void);
* or NULL if @a h_denom_pub could not be found * or NULL if @a h_denom_pub could not be found
*/ */
struct TEH_DenominationKey * struct TEH_DenominationKey *
TEH_keys_denomination_by_hash (const struct TEH_keys_denomination_by_hash (
TALER_DenominationHashP *h_denom_pub, const struct TALER_DenominationHashP *h_denom_pub,
struct MHD_Connection *conn, struct MHD_Connection *conn,
MHD_RESULT *mret); MHD_RESULT *mret);

View File

@ -636,6 +636,19 @@ TALER_EXCHANGE_get_denomination_key (
const struct TALER_DenominationPublicKey *pk); const struct TALER_DenominationPublicKey *pk);
/**
* Obtain the global fee details from the exchange.
*
* @param keys the exchange's key set
* @param ts time for when to fetch the fees
* @return details about the fees, NULL if no fees are known at @a ts
*/
const struct TALER_EXCHANGE_GlobalFee *
TALER_EXCHANGE_get_global_fee (
const struct TALER_EXCHANGE_Keys *keys,
struct GNUNET_TIME_Timestamp ts);
/** /**
* Create a copy of a denomination public key. * Create a copy of a denomination public key.
* *
@ -1281,13 +1294,6 @@ TALER_EXCHANGE_csr_withdraw_cancel (
/* ********************* GET /reserves/$RESERVE_PUB *********************** */ /* ********************* GET /reserves/$RESERVE_PUB *********************** */
/**
* @brief A /reserves/ GET Handle
*/
struct TALER_EXCHANGE_ReservesGetHandle;
/** /**
* Ways how a reserve's balance may change. * Ways how a reserve's balance may change.
*/ */
@ -1320,7 +1326,7 @@ enum TALER_EXCHANGE_ReserveTransactionType
/** /**
* @brief Entry in the reserve's transaction history. * @brief Entry in the reserve's transaction history.
*/ */
struct TALER_EXCHANGE_ReserveHistory struct TALER_EXCHANGE_ReserveHistoryEntry
{ {
/** /**
@ -1453,23 +1459,59 @@ struct TALER_EXCHANGE_ReserveHistory
}; };
/**
* @brief A /reserves/ GET Handle
*/
struct TALER_EXCHANGE_ReservesGetHandle;
/**
* @brief Reserve summary.
*/
struct TALER_EXCHANGE_ReserveSummary
{
/**
* High-level HTTP response details.
*/
struct TALER_EXCHANGE_HttpResponse hr;
/**
* Details depending on @e hr.http_status.
*/
union
{
/**
* Information returned on success, if
* @e hr.http_status is #MHD_HTTP_OK
*/
struct
{
/**
* Reserve balance.
*/
struct TALER_Amount balance;
} ok;
} details;
};
/** /**
* Callbacks of this type are used to serve the result of submitting a * Callbacks of this type are used to serve the result of submitting a
* reserve status request to a exchange. * reserve status request to a exchange.
* *
* @param cls closure * @param cls closure
* @param hr HTTP response data * @param rs HTTP response data
* @param balance current balance in the reserve, NULL on error
* @param history_length number of entries in the transaction history, 0 on error
* @param history detailed transaction history, NULL on error
*/ */
typedef void typedef void
(*TALER_EXCHANGE_ReservesGetCallback) ( (*TALER_EXCHANGE_ReservesGetCallback) (
void *cls, void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr, const struct TALER_EXCHANGE_ReserveSummary *rs);
const struct TALER_Amount *balance,
unsigned int history_length,
const struct TALER_EXCHANGE_ReserveHistory *history);
/** /**
@ -1510,6 +1552,214 @@ TALER_EXCHANGE_reserves_get_cancel (
struct TALER_EXCHANGE_ReservesGetHandle *rgh); struct TALER_EXCHANGE_ReservesGetHandle *rgh);
/**
* @brief A /reserves/$RID/status Handle
*/
struct TALER_EXCHANGE_ReservesStatusHandle;
/**
* @brief Reserve status details.
*/
struct TALER_EXCHANGE_ReserveStatus
{
/**
* High-level HTTP response details.
*/
struct TALER_EXCHANGE_HttpResponse hr;
/**
* Details depending on @e hr.http_status.
*/
union
{
/**
* Information returned on success, if
* @e hr.http_status is #MHD_HTTP_OK
*/
struct
{
/**
* Reserve balance.
*/
struct TALER_Amount balance;
/**
* Reserve history.
*/
const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
/**
* Length of the @e history array.
*/
unsigned int history_len;
/**
* KYC passed?
*/
bool kyc_ok;
/**
* KYC required to withdraw?
*/
bool kyc_required;
} ok;
} details;
};
/**
* Callbacks of this type are used to serve the result of submitting a
* reserve status request to a exchange.
*
* @param cls closure
* @param rs HTTP response data
*/
typedef void
(*TALER_EXCHANGE_ReservesStatusCallback) (
void *cls,
const struct TALER_EXCHANGE_ReserveStatus *rs);
/**
* Submit a request to obtain the reserve status.
*
* @param exchange the exchange handle; the exchange must be ready to operate
* @param reserve_priv private key of the reserve to inspect
* @param cb the callback to call when a reply for this request is available
* @param cb_cls closure for the above callback
* @return a handle for this request; NULL if the inputs are invalid (i.e.
* signatures fail to verify). In this case, the callback is not called.
*/
struct TALER_EXCHANGE_ReservesStatusHandle *
TALER_EXCHANGE_reserves_status (
struct TALER_EXCHANGE_Handle *exchange,
const struct TALER_ReservePrivateKeyP *reserve_priv,
TALER_EXCHANGE_ReservesStatusCallback cb,
void *cb_cls);
/**
* Cancel a reserve status request. This function cannot be used
* on a request handle if a response is already served for it.
*
* @param rsh the reserve request handle
*/
void
TALER_EXCHANGE_reserves_status_cancel (
struct TALER_EXCHANGE_ReservesStatusHandle *rsh);
/**
* @brief A /reserves/$RID/history Handle
*/
struct TALER_EXCHANGE_ReservesHistoryHandle;
/**
* @brief Reserve history details.
*/
struct TALER_EXCHANGE_ReserveHistory
{
/**
* High-level HTTP response details.
*/
struct TALER_EXCHANGE_HttpResponse hr;
/**
* Details depending on @e hr.http_status.
*/
union
{
/**
* Information returned on success, if
* @e hr.http_status is #MHD_HTTP_OK
*/
struct
{
/**
* Reserve balance.
*/
struct TALER_Amount balance;
/**
* Reserve history.
*/
const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
/**
* Length of the @e history array.
*/
unsigned int history_len;
/**
* KYC passed?
*/
bool kyc_ok;
/**
* KYC required to withdraw?
*/
bool kyc_required;
} ok;
} details;
};
/**
* Callbacks of this type are used to serve the result of submitting a
* reserve history request to a exchange.
*
* @param cls closure
* @param rs HTTP response data
*/
typedef void
(*TALER_EXCHANGE_ReservesHistoryCallback) (
void *cls,
const struct TALER_EXCHANGE_ReserveHistory *rs);
/**
* Submit a request to obtain the reserve history.
*
* @param exchange the exchange handle; the exchange must be ready to operate
* @param reserve_priv private key of the reserve to inspect
* @param cb the callback to call when a reply for this request is available
* @param cb_cls closure for the above callback
* @return a handle for this request; NULL if the inputs are invalid (i.e.
* signatures fail to verify). In this case, the callback is not called.
*/
struct TALER_EXCHANGE_ReservesHistoryHandle *
TALER_EXCHANGE_reserves_history (
struct TALER_EXCHANGE_Handle *exchange,
const struct TALER_ReservePrivateKeyP *reserve_priv,
TALER_EXCHANGE_ReservesHistoryCallback cb,
void *cb_cls);
/**
* Cancel a reserve history request. This function cannot be used
* on a request handle if a response is already served for it.
*
* @param rsh the reserve request handle
*/
void
TALER_EXCHANGE_reserves_history_cancel (
struct TALER_EXCHANGE_ReservesHistoryHandle *rsh);
/* ********************* POST /reserves/$RESERVE_PUB/withdraw *********************** */ /* ********************* POST /reserves/$RESERVE_PUB/withdraw *********************** */
@ -2451,7 +2701,7 @@ TALER_EXCHANGE_parse_reserve_history (
const char *currency, const char *currency,
struct TALER_Amount *balance, struct TALER_Amount *balance,
unsigned int history_length, unsigned int history_length,
struct TALER_EXCHANGE_ReserveHistory *rhistory); struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory);
/** /**
@ -2462,7 +2712,7 @@ TALER_EXCHANGE_parse_reserve_history (
*/ */
void void
TALER_EXCHANGE_free_reserve_history ( TALER_EXCHANGE_free_reserve_history (
struct TALER_EXCHANGE_ReserveHistory *rhistory, struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory,
unsigned int len); unsigned int len);

View File

@ -1382,15 +1382,38 @@ TALER_TESTING_cmd_status (const char *label,
const char *expected_balance, const char *expected_balance,
unsigned int expected_response_code); unsigned int expected_response_code);
/**
* Index of the deposit value trait of a deposit command.
*/
#define TALER_TESTING_CMD_DEPOSIT_TRAIT_IDX_DEPOSIT_VALUE 0
/** /**
* Index of the deposit fee trait of a deposit command. * Create a POST "/reserves/$RID/history" command.
*
* @param label the command label.
* @param reserve_reference reference to the reserve to check.
* @param expected_balance expected balance for the reserve.
* @param expected_response_code expected HTTP response code.
* @return the command.
*/ */
#define TALER_TESTING_CMD_DEPOSIT_TRAIT_IDX_DEPOSIT_FEE 1 struct TALER_TESTING_Command
TALER_TESTING_cmd_reserve_history (const char *label,
const char *reserve_reference,
const char *expected_balance,
unsigned int expected_response_code);
/**
* Create a POST "/reserves/$RID/status" command.
*
* @param label the command label.
* @param reserve_reference reference to the reserve to check.
* @param expected_balance expected balance for the reserve.
* @param expected_response_code expected HTTP response code.
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_reserve_status (const char *label,
const char *reserve_reference,
const char *expected_balance,
unsigned int expected_response_code);
/** /**
* Create a "deposit" command. * Create a "deposit" command.
@ -2455,7 +2478,7 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
op (contract_terms, const json_t) \ op (contract_terms, const json_t) \
op (wire_details, const json_t) \ op (wire_details, const json_t) \
op (exchange_keys, const json_t) \ op (exchange_keys, const json_t) \
op (reserve_history, const struct TALER_EXCHANGE_ReserveHistory) \ op (reserve_history, const struct TALER_EXCHANGE_ReserveHistoryEntry) \
op (exchange_url, const char *) \ op (exchange_url, const char *) \
op (exchange_bank_account_url, const char *) \ op (exchange_bank_account_url, const char *) \
op (taler_uri, const char *) \ op (taler_uri, const char *) \

View File

@ -18,7 +18,7 @@ lib_LTLIBRARIES = \
libtalerexchange.la libtalerexchange.la
libtalerexchange_la_LDFLAGS = \ libtalerexchange_la_LDFLAGS = \
-version-info 4:0:0 \ -version-info 5:0:0 \
-no-undefined -no-undefined
libtalerexchange_la_SOURCES = \ libtalerexchange_la_SOURCES = \
exchange_api_auditor_add_denomination.c \ exchange_api_auditor_add_denomination.c \
@ -51,6 +51,8 @@ libtalerexchange_la_SOURCES = \
exchange_api_refreshes_reveal.c \ exchange_api_refreshes_reveal.c \
exchange_api_refund.c \ exchange_api_refund.c \
exchange_api_reserves_get.c \ exchange_api_reserves_get.c \
exchange_api_reserves_history.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 \

View File

@ -34,7 +34,7 @@ TALER_EXCHANGE_parse_reserve_history (
const char *currency, const char *currency,
struct TALER_Amount *balance, struct TALER_Amount *balance,
unsigned int history_length, unsigned int history_length,
struct TALER_EXCHANGE_ReserveHistory *rhistory) struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory)
{ {
struct GNUNET_HashCode uuid[history_length]; struct GNUNET_HashCode uuid[history_length];
unsigned int uuid_off; unsigned int uuid_off;
@ -50,7 +50,7 @@ TALER_EXCHANGE_parse_reserve_history (
uuid_off = 0; uuid_off = 0;
for (unsigned int off = 0; off<history_length; off++) for (unsigned int off = 0; off<history_length; off++)
{ {
struct TALER_EXCHANGE_ReserveHistory *rh = &rhistory[off]; struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
json_t *transaction; json_t *transaction;
struct TALER_Amount amount; struct TALER_Amount amount;
const char *type; const char *type;
@ -368,6 +368,11 @@ TALER_EXCHANGE_parse_reserve_history (
} }
/* check balance = total_in - total_out < withdraw-amount */ /* check balance = total_in - total_out < withdraw-amount */
if (NULL != balance)
{
/* if balance is NULL, we may have a partial history
in which case the subtraction may fail, so we do
not even check that invariant in this case. */
if (0 > if (0 >
TALER_amount_subtract (balance, TALER_amount_subtract (balance,
&total_in, &total_in,
@ -377,13 +382,14 @@ TALER_EXCHANGE_parse_reserve_history (
GNUNET_break_op (0); GNUNET_break_op (0);
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
}
return GNUNET_OK; return GNUNET_OK;
} }
void void
TALER_EXCHANGE_free_reserve_history ( TALER_EXCHANGE_free_reserve_history (
struct TALER_EXCHANGE_ReserveHistory *rhistory, struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory,
unsigned int len) unsigned int len)
{ {
for (unsigned int i = 0; i<len; i++) for (unsigned int i = 0; i<len; i++)

View File

@ -2181,6 +2181,27 @@ TALER_EXCHANGE_get_denomination_key (
} }
const struct TALER_EXCHANGE_GlobalFee *
TALER_EXCHANGE_get_global_fee (
const struct TALER_EXCHANGE_Keys *keys,
struct GNUNET_TIME_Timestamp ts)
{
for (unsigned int i = 0; i<keys->num_global_fees; i++)
{
const struct TALER_EXCHANGE_GlobalFee *gf = &keys->global_fees[i];
if (GNUNET_TIME_timestamp_cmp (ts,
>=,
gf->start_date) &&
GNUNET_TIME_timestamp_cmp (ts,
<,
gf->end_date))
return gf;
}
return NULL;
}
struct TALER_EXCHANGE_DenomPublicKey * struct TALER_EXCHANGE_DenomPublicKey *
TALER_EXCHANGE_copy_denomination_key ( TALER_EXCHANGE_copy_denomination_key (
const struct TALER_EXCHANGE_DenomPublicKey *key) const struct TALER_EXCHANGE_DenomPublicKey *key)

View File

@ -83,19 +83,15 @@ static enum GNUNET_GenericReturnValue
handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh, handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh,
const json_t *j) const json_t *j)
{ {
json_t *history; struct TALER_EXCHANGE_ReserveSummary rs = {
unsigned int len; .hr.reply = j,
struct TALER_Amount balance; .hr.http_status = MHD_HTTP_OK
struct TALER_Amount balance_from_history; };
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount_any ("balance", TALER_JSON_spec_amount_any ("balance",
&balance), &rs.details.ok.balance),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
struct TALER_EXCHANGE_HttpResponse hr = {
.reply = j,
.http_status = MHD_HTTP_OK
};
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_JSON_parse (j, GNUNET_JSON_parse (j,
@ -106,55 +102,9 @@ handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh,
GNUNET_break_op (0); GNUNET_break_op (0);
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
history = json_object_get (j,
"history");
if (NULL == history)
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
len = json_array_size (history);
{
struct TALER_EXCHANGE_ReserveHistory *rhistory;
rhistory = GNUNET_new_array (len,
struct TALER_EXCHANGE_ReserveHistory);
if (GNUNET_OK !=
TALER_EXCHANGE_parse_reserve_history (rgh->exchange,
history,
&rgh->reserve_pub,
balance.currency,
&balance_from_history,
len,
rhistory))
{
GNUNET_break_op (0);
TALER_EXCHANGE_free_reserve_history (rhistory,
len);
return GNUNET_SYSERR;
}
if (0 !=
TALER_amount_cmp (&balance_from_history,
&balance))
{
/* exchange cannot add up balances!? */
GNUNET_break_op (0);
TALER_EXCHANGE_free_reserve_history (rhistory,
len);
return GNUNET_SYSERR;
}
if (NULL != rgh->cb)
{
rgh->cb (rgh->cb_cls, rgh->cb (rgh->cb_cls,
&hr, &rs);
&balance,
len,
rhistory);
rgh->cb = NULL; rgh->cb = NULL;
}
TALER_EXCHANGE_free_reserve_history (rhistory,
len);
}
return GNUNET_OK; return GNUNET_OK;
} }
@ -174,61 +124,59 @@ handle_reserves_get_finished (void *cls,
{ {
struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls; struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls;
const json_t *j = response; const json_t *j = response;
struct TALER_EXCHANGE_HttpResponse hr = { struct TALER_EXCHANGE_ReserveSummary rs = {
.reply = j, .hr.reply = j,
.http_status = (unsigned int) response_code .hr.http_status = (unsigned int) response_code
}; };
rgh->job = NULL; rgh->job = NULL;
switch (response_code) switch (response_code)
{ {
case 0: case 0:
hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break; break;
case MHD_HTTP_OK: case MHD_HTTP_OK:
if (GNUNET_OK != if (GNUNET_OK !=
handle_reserves_get_ok (rgh, handle_reserves_get_ok (rgh,
j)) j))
{ {
hr.http_status = 0; rs.hr.http_status = 0;
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
} }
break; break;
case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy /* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */ (or API version conflict); just pass JSON reply to the application */
hr.ec = TALER_JSON_get_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_NOT_FOUND: case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never /* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */ happen, we should pass the JSON reply to the application */
hr.ec = TALER_JSON_get_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_INTERNAL_SERVER_ERROR: case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API /* Server had an internal issue; we should retry, but this API
leaves this to the application */ leaves this to the application */
hr.ec = TALER_JSON_get_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
default: default:
/* unexpected response code */ /* unexpected response code */
GNUNET_break_op (0); GNUNET_break_op (0);
hr.ec = TALER_JSON_get_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for reserves get\n", "Unexpected response code %u/%d for reserves get\n",
(unsigned int) response_code, (unsigned int) response_code,
(int) hr.ec); (int) rs.hr.ec);
break; break;
} }
if (NULL != rgh->cb) if (NULL != rgh->cb)
{ {
rgh->cb (rgh->cb_cls, rgh->cb (rgh->cb_cls,
&hr, &rs);
NULL,
0, NULL);
rgh->cb = NULL; rgh->cb = NULL;
} }
TALER_EXCHANGE_reserves_get_cancel (rgh); TALER_EXCHANGE_reserves_get_cancel (rgh);

View File

@ -81,35 +81,32 @@ struct TALER_EXCHANGE_ReservesHistoryHandle
* We received an #MHD_HTTP_OK history code. Handle the JSON * We received an #MHD_HTTP_OK history code. Handle the JSON
* response. * response.
* *
* @param rgh handle of the request * @param rsh handle of the request
* @param j JSON response * @param j JSON response
* @return #GNUNET_OK on success * @return #GNUNET_OK on success
*/ */
static enum GNUNET_GenericReturnValue static enum GNUNET_GenericReturnValue
handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rgh, handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
const json_t *j) const json_t *j)
{ {
json_t *history; json_t *history;
unsigned int len; unsigned int len;
bool kyc_ok; struct TALER_Amount history_balance;
bool kyc_required; struct TALER_EXCHANGE_ReserveHistory rs = {
struct TALER_Amount balance; .hr.reply = j,
struct TALER_Amount balance_from_history; .hr.http_status = MHD_HTTP_OK
};
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount_any ("balance", TALER_JSON_spec_amount_any ("balance",
&balance), &rs.details.ok.balance),
GNUNET_JSON_spec_bool ("kyc_passed", GNUNET_JSON_spec_bool ("kyc_passed",
&kyc_ok), &rs.details.ok.kyc_ok),
GNUNET_JSON_spec_bool ("kyc_required", GNUNET_JSON_spec_bool ("kyc_required",
&kyc_required), &rs.details.ok.kyc_required),
GNUNET_JSON_spec_json ("history", GNUNET_JSON_spec_json ("history",
&history), &history),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
struct TALER_EXCHANGE_HttpResponse hr = {
.reply = j,
.http_history = MHD_HTTP_OK
};
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_JSON_parse (j, GNUNET_JSON_parse (j,
@ -122,16 +119,19 @@ handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rgh,
} }
len = json_array_size (history); len = json_array_size (history);
{ {
struct TALER_EXCHANGE_ReserveHistory *rhistory; struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
rhistory = GNUNET_new_array (len, rhistory = GNUNET_new_array (len,
struct TALER_EXCHANGE_ReserveHistory); struct TALER_EXCHANGE_ReserveHistoryEntry);
// FIXME: even this history could be partial
// (if the reserve is too old!); update API
// and return incoming & outgoing totals separately?
if (GNUNET_OK != if (GNUNET_OK !=
TALER_EXCHANGE_parse_reserve_history (rgh->exchange, TALER_EXCHANGE_parse_reserve_history (rsh->exchange,
history, history,
&rgh->reserve_pub, &rsh->reserve_pub,
balance.currency, rs.details.ok.balance.currency,
&balance_from_history, &history_balance,
len, len,
rhistory)) rhistory))
{ {
@ -141,25 +141,13 @@ handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rgh,
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
if (0 != if (NULL != rsh->cb)
TALER_amount_cmp (&balance_from_history,
&balance))
{ {
/* exchange cannot add up balances!? */ rs.details.ok.history = rhistory;
GNUNET_break_op (0); rs.details.ok.history_len = len;
TALER_EXCHANGE_free_reserve_history (rhistory, rsh->cb (rsh->cb_cls,
len); &rs);
GNUNET_JSON_parse_free (spec); rsh->cb = NULL;
return GNUNET_SYSERR;
}
if (NULL != rgh->cb)
{
rgh->cb (rgh->cb_cls,
&hr,
&balance,
len,
rhistory);
rgh->cb = NULL;
} }
TALER_EXCHANGE_free_reserve_history (rhistory, TALER_EXCHANGE_free_reserve_history (rhistory,
len); len);
@ -182,75 +170,72 @@ handle_reserves_history_finished (void *cls,
long response_code, long response_code,
const void *response) const void *response)
{ {
struct TALER_EXCHANGE_ReservesHistoryHandle *rgh = cls; struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls;
const json_t *j = response; const json_t *j = response;
struct TALER_EXCHANGE_HttpResponse hr = { struct TALER_EXCHANGE_ReserveHistory rs = {
.reply = j, .hr.reply = j,
.http_history = (unsigned int) response_code .hr.http_status = (unsigned int) response_code
}; };
rgh->job = NULL; rsh->job = NULL;
switch (response_code) switch (response_code)
{ {
case 0: case 0:
hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break; break;
case MHD_HTTP_OK: case MHD_HTTP_OK:
if (GNUNET_OK != if (GNUNET_OK !=
handle_reserves_history_ok (rgh, handle_reserves_history_ok (rsh,
j)) j))
{ {
hr.http_history = 0; rs.hr.http_status = 0;
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
} }
break; break;
case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy /* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */ (or API version conflict); just pass JSON reply to the application */
GNUNET_break (0); GNUNET_break (0);
hr.ec = TALER_JSON_history_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_history_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_FORBIDDEN: case MHD_HTTP_FORBIDDEN:
/* This should never happen, either us or the exchange is buggy /* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */ (or API version conflict); just pass JSON reply to the application */
GNUNET_break (0); GNUNET_break (0);
hr.ec = TALER_JSON_history_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_history_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_NOT_FOUND: case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never /* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */ happen, we should pass the JSON reply to the application */
hr.ec = TALER_JSON_history_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_history_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_INTERNAL_SERVER_ERROR: case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API /* Server had an internal issue; we should retry, but this API
leaves this to the application */ leaves this to the application */
hr.ec = TALER_JSON_history_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_history_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
default: default:
/* unexpected response code */ /* unexpected response code */
GNUNET_break_op (0); GNUNET_break_op (0);
hr.ec = TALER_JSON_history_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_history_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for reserves history\n", "Unexpected response code %u/%d for reserves history\n",
(unsigned int) response_code, (unsigned int) response_code,
(int) hr.ec); (int) rs.hr.ec);
break; break;
} }
if (NULL != rgh->cb) if (NULL != rsh->cb)
{ {
rgh->cb (rgh->cb_cls, rsh->cb (rsh->cb_cls,
&hr, &rs);
NULL, rsh->cb = NULL;
0,
NULL);
rgh->cb = NULL;
} }
TALER_EXCHANGE_reserves_history_cancel (rgh); TALER_EXCHANGE_reserves_history_cancel (rsh);
} }
@ -261,12 +246,11 @@ TALER_EXCHANGE_reserves_history (
TALER_EXCHANGE_ReservesHistoryCallback cb, TALER_EXCHANGE_ReservesHistoryCallback cb,
void *cb_cls) void *cb_cls)
{ {
struct TALER_EXCHANGE_ReservesHistoryHandle *rgh; struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
struct GNUNET_CURL_Context *ctx; struct GNUNET_CURL_Context *ctx;
CURL *eh; CURL *eh;
char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
const struct TALER_Amount *history_fee; struct TALER_ReserveSignatureP reserve_sig;
const struct TALER_EXCHANGE_Keys *keys;
struct GNUNET_TIME_Timestamp ts struct GNUNET_TIME_Timestamp ts
= GNUNET_TIME_timestamp_get (); = GNUNET_TIME_timestamp_get ();
@ -276,22 +260,19 @@ TALER_EXCHANGE_reserves_history (
GNUNET_break (0); GNUNET_break (0);
return NULL; return NULL;
} }
keys = TALER_EXCHANGE_get_keys (exchange); rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
// FIXME: extract history_fee from keys! rsh->exchange = exchange;
history_fee = FIXME; rsh->cb = cb;
rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle); rsh->cb_cls = cb_cls;
rgh->exchange = exchange;
rgh->cb = cb;
rgh->cb_cls = cb_cls;
GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
&rgh->reserve_pub.eddsa_pub); &rsh->reserve_pub.eddsa_pub);
{ {
char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
char *end; char *end;
end = GNUNET_STRINGS_data_to_string ( end = GNUNET_STRINGS_data_to_string (
&rgh->reserve_pub, &rsh->reserve_pub,
sizeof (rgh->reserve_pub), sizeof (rsh->reserve_pub),
pub_str, pub_str,
sizeof (pub_str)); sizeof (pub_str));
*end = '\0'; *end = '\0';
@ -300,68 +281,67 @@ TALER_EXCHANGE_reserves_history (
"/reserves/%s/history", "/reserves/%s/history",
pub_str); pub_str);
} }
rgh->url = TEAH_path_to_url (exchange, rsh->url = TEAH_path_to_url (exchange,
arg_str); arg_str);
if (NULL == rgh->url) if (NULL == rsh->url)
{ {
GNUNET_free (rgh); GNUNET_free (rsh);
return NULL; return NULL;
} }
eh = TALER_EXCHANGE_curl_easy_history_ (rgh->url); eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
if (NULL == eh) if (NULL == eh)
{ {
GNUNET_break (0); GNUNET_break (0);
GNUNET_free (rgh->url); GNUNET_free (rsh->url);
GNUNET_free (rgh); GNUNET_free (rsh);
return NULL; return NULL;
} }
TALER_wallet_reserve_history_sign (ts, TALER_wallet_reserve_history_sign (ts,
history_fee, NULL, /* FIXME: fee! */
reserve_priv, reserve_priv,
&reserve_sig); &reserve_sig);
{ {
json_t *history_obj = GNUNET_JSON_PACK ( json_t *history_obj = GNUNET_JSON_PACK (
GNUNET_JSON_pack_timestamp ("request_timestamp", GNUNET_JSON_pack_timestamp ("request_timestamp",
&ts), ts),
GNUNET_JSON_pack_data_auto ("reserve_sig", GNUNET_JSON_pack_data_auto ("reserve_sig",
&reserve_sig)); &reserve_sig));
if (GNUNET_OK != if (GNUNET_OK !=
TALER_curl_easy_post (&rgh->post_ctx, TALER_curl_easy_post (&rsh->post_ctx,
eh, eh,
history_obj)) history_obj))
)
{ {
GNUNET_break (0); GNUNET_break (0);
curl_easy_cleanup (eh); curl_easy_cleanup (eh);
json_decref (history_obj); json_decref (history_obj);
GNUNET_free (rgh->url); GNUNET_free (rsh->url);
GNUNET_free (rgh); GNUNET_free (rsh);
return NULL; return NULL;
} }
json_decref (history_obj); json_decref (history_obj);
} }
ctx = TEAH_handle_to_context (exchange); ctx = TEAH_handle_to_context (exchange);
rgh->job = GNUNET_CURL_job_add (ctx, rsh->job = GNUNET_CURL_job_add (ctx,
eh, eh,
&handle_reserves_history_finished, &handle_reserves_history_finished,
rgh); rsh);
return rgh; return rsh;
} }
void void
TALER_EXCHANGE_reserves_history_cancel ( TALER_EXCHANGE_reserves_history_cancel (
struct TALER_EXCHANGE_ReservesHistoryHandle *rgh) struct TALER_EXCHANGE_ReservesHistoryHandle *rsh)
{ {
if (NULL != rgh->job) if (NULL != rsh->job)
{ {
GNUNET_CURL_job_cancel (rgh->job); GNUNET_CURL_job_cancel (rsh->job);
rgh->job = NULL; rsh->job = NULL;
} }
TALER_curl_easy_post_finished (&rgh->post_ctx); TALER_curl_easy_post_finished (&rsh->post_ctx);
GNUNET_free (rgh->url); GNUNET_free (rsh->url);
GNUNET_free (rgh); GNUNET_free (rsh);
} }

View File

@ -81,35 +81,31 @@ struct TALER_EXCHANGE_ReservesStatusHandle
* We received an #MHD_HTTP_OK status code. Handle the JSON * We received an #MHD_HTTP_OK status code. Handle the JSON
* response. * response.
* *
* @param rgh handle of the request * @param rsh handle of the request
* @param j JSON response * @param j JSON response
* @return #GNUNET_OK on success * @return #GNUNET_OK on success
*/ */
static enum GNUNET_GenericReturnValue static enum GNUNET_GenericReturnValue
handle_reserves_status_ok (struct TALER_EXCHANGE_ReservesStatusHandle *rgh, handle_reserves_status_ok (struct TALER_EXCHANGE_ReservesStatusHandle *rsh,
const json_t *j) const json_t *j)
{ {
json_t *history; json_t *history;
unsigned int len; unsigned int len;
bool kyc_ok; struct TALER_EXCHANGE_ReserveStatus rs = {
bool kyc_required; .hr.reply = j,
struct TALER_Amount balance; .hr.http_status = MHD_HTTP_OK
struct TALER_Amount balance_from_history; };
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount_any ("balance", TALER_JSON_spec_amount_any ("balance",
&balance), &rs.details.ok.balance),
GNUNET_JSON_spec_bool ("kyc_passed", GNUNET_JSON_spec_bool ("kyc_passed",
&kyc_ok), &rs.details.ok.kyc_ok),
GNUNET_JSON_spec_bool ("kyc_required", GNUNET_JSON_spec_bool ("kyc_required",
&kyc_required), &rs.details.ok.kyc_required),
GNUNET_JSON_spec_json ("history", GNUNET_JSON_spec_json ("history",
&history), &history),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
struct TALER_EXCHANGE_HttpResponse hr = {
.reply = j,
.http_status = MHD_HTTP_OK
};
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_JSON_parse (j, GNUNET_JSON_parse (j,
@ -122,16 +118,16 @@ handle_reserves_status_ok (struct TALER_EXCHANGE_ReservesStatusHandle *rgh,
} }
len = json_array_size (history); len = json_array_size (history);
{ {
struct TALER_EXCHANGE_ReserveHistory *rhistory; struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
rhistory = GNUNET_new_array (len, rhistory = GNUNET_new_array (len,
struct TALER_EXCHANGE_ReserveHistory); struct TALER_EXCHANGE_ReserveHistoryEntry);
if (GNUNET_OK != if (GNUNET_OK !=
TALER_EXCHANGE_parse_reserve_history (rgh->exchange, TALER_EXCHANGE_parse_reserve_history (rsh->exchange,
history, history,
&rgh->reserve_pub, &rsh->reserve_pub,
balance.currency, rs.details.ok.balance.currency,
&balance_from_history, NULL,
len, len,
rhistory)) rhistory))
{ {
@ -141,27 +137,13 @@ handle_reserves_status_ok (struct TALER_EXCHANGE_ReservesStatusHandle *rgh,
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
// FIXME: status history is allowed to be if (NULL != rsh->cb)
// partial, so this is NOT ok...
if (0 !=
TALER_amount_cmp (&balance_from_history,
&balance))
{ {
/* exchange cannot add up balances!? */ rs.details.ok.history = rhistory;
GNUNET_break_op (0); rs.details.ok.history_len = len;
TALER_EXCHANGE_free_reserve_history (rhistory, rsh->cb (rsh->cb_cls,
len); &rs);
GNUNET_JSON_parse_free (spec); rsh->cb = NULL;
return GNUNET_SYSERR;
}
if (NULL != rgh->cb)
{
rgh->cb (rgh->cb_cls,
&hr,
&balance,
len,
rhistory);
rgh->cb = NULL;
} }
TALER_EXCHANGE_free_reserve_history (rhistory, TALER_EXCHANGE_free_reserve_history (rhistory,
len); len);
@ -184,75 +166,72 @@ handle_reserves_status_finished (void *cls,
long response_code, long response_code,
const void *response) const void *response)
{ {
struct TALER_EXCHANGE_ReservesStatusHandle *rgh = cls; struct TALER_EXCHANGE_ReservesStatusHandle *rsh = cls;
const json_t *j = response; const json_t *j = response;
struct TALER_EXCHANGE_HttpResponse hr = { struct TALER_EXCHANGE_ReserveStatus rs = {
.reply = j, .hr.reply = j,
.http_status = (unsigned int) response_code .hr.http_status = (unsigned int) response_code
}; };
rgh->job = NULL; rsh->job = NULL;
switch (response_code) switch (response_code)
{ {
case 0: case 0:
hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break; break;
case MHD_HTTP_OK: case MHD_HTTP_OK:
if (GNUNET_OK != if (GNUNET_OK !=
handle_reserves_status_ok (rgh, handle_reserves_status_ok (rsh,
j)) j))
{ {
hr.http_status = 0; rs.hr.http_status = 0;
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
} }
break; break;
case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy /* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */ (or API version conflict); just pass JSON reply to the application */
GNUNET_break (0); GNUNET_break (0);
hr.ec = TALER_JSON_status_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_status_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_FORBIDDEN: case MHD_HTTP_FORBIDDEN:
/* This should never happen, either us or the exchange is buggy /* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */ (or API version conflict); just pass JSON reply to the application */
GNUNET_break (0); GNUNET_break (0);
hr.ec = TALER_JSON_status_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_status_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_NOT_FOUND: case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never /* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */ happen, we should pass the JSON reply to the application */
hr.ec = TALER_JSON_status_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_status_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_INTERNAL_SERVER_ERROR: case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API /* Server had an internal issue; we should retry, but this API
leaves this to the application */ leaves this to the application */
hr.ec = TALER_JSON_status_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_status_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
default: default:
/* unexpected response code */ /* unexpected response code */
GNUNET_break_op (0); GNUNET_break_op (0);
hr.ec = TALER_JSON_status_error_code (j); rs.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_status_error_hint (j); rs.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for reserves status\n", "Unexpected response code %u/%d for reserves status\n",
(unsigned int) response_code, (unsigned int) response_code,
(int) hr.ec); (int) rs.hr.ec);
break; break;
} }
if (NULL != rgh->cb) if (NULL != rsh->cb)
{ {
rgh->cb (rgh->cb_cls, rsh->cb (rsh->cb_cls,
&hr, &rs);
NULL, rsh->cb = NULL;
0,
NULL);
rgh->cb = NULL;
} }
TALER_EXCHANGE_reserves_status_cancel (rgh); TALER_EXCHANGE_reserves_status_cancel (rsh);
} }
@ -263,10 +242,11 @@ TALER_EXCHANGE_reserves_status (
TALER_EXCHANGE_ReservesStatusCallback cb, TALER_EXCHANGE_ReservesStatusCallback cb,
void *cb_cls) void *cb_cls)
{ {
struct TALER_EXCHANGE_ReservesStatusHandle *rgh; struct TALER_EXCHANGE_ReservesStatusHandle *rsh;
struct GNUNET_CURL_Context *ctx; struct GNUNET_CURL_Context *ctx;
CURL *eh; CURL *eh;
char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
struct TALER_ReserveSignatureP reserve_sig;
struct GNUNET_TIME_Timestamp ts struct GNUNET_TIME_Timestamp ts
= GNUNET_TIME_timestamp_get (); = GNUNET_TIME_timestamp_get ();
@ -276,19 +256,19 @@ TALER_EXCHANGE_reserves_status (
GNUNET_break (0); GNUNET_break (0);
return NULL; return NULL;
} }
rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesStatusHandle); rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesStatusHandle);
rgh->exchange = exchange; rsh->exchange = exchange;
rgh->cb = cb; rsh->cb = cb;
rgh->cb_cls = cb_cls; rsh->cb_cls = cb_cls;
GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
&rgh->reserve_pub.eddsa_pub); &rsh->reserve_pub.eddsa_pub);
{ {
char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
char *end; char *end;
end = GNUNET_STRINGS_data_to_string ( end = GNUNET_STRINGS_data_to_string (
&rgh->reserve_pub, &rsh->reserve_pub,
sizeof (rgh->reserve_pub), sizeof (rsh->reserve_pub),
pub_str, pub_str,
sizeof (pub_str)); sizeof (pub_str));
*end = '\0'; *end = '\0';
@ -297,19 +277,19 @@ TALER_EXCHANGE_reserves_status (
"/reserves/%s/status", "/reserves/%s/status",
pub_str); pub_str);
} }
rgh->url = TEAH_path_to_url (exchange, rsh->url = TEAH_path_to_url (exchange,
arg_str); arg_str);
if (NULL == rgh->url) if (NULL == rsh->url)
{ {
GNUNET_free (rgh); GNUNET_free (rsh);
return NULL; return NULL;
} }
eh = TALER_EXCHANGE_curl_easy_status_ (rgh->url); eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
if (NULL == eh) if (NULL == eh)
{ {
GNUNET_break (0); GNUNET_break (0);
GNUNET_free (rgh->url); GNUNET_free (rsh->url);
GNUNET_free (rgh); GNUNET_free (rsh);
return NULL; return NULL;
} }
TALER_wallet_reserve_status_sign (ts, TALER_wallet_reserve_status_sign (ts,
@ -318,46 +298,45 @@ TALER_EXCHANGE_reserves_status (
{ {
json_t *status_obj = GNUNET_JSON_PACK ( json_t *status_obj = GNUNET_JSON_PACK (
GNUNET_JSON_pack_timestamp ("request_timestamp", GNUNET_JSON_pack_timestamp ("request_timestamp",
&ts), ts),
GNUNET_JSON_pack_data_auto ("reserve_sig", GNUNET_JSON_pack_data_auto ("reserve_sig",
&reserve_sig)); &reserve_sig));
if (GNUNET_OK != if (GNUNET_OK !=
TALER_curl_easy_post (&rgh->post_ctx, TALER_curl_easy_post (&rsh->post_ctx,
eh, eh,
status_obj)) status_obj))
)
{ {
GNUNET_break (0); GNUNET_break (0);
curl_easy_cleanup (eh); curl_easy_cleanup (eh);
json_decref (status_obj); json_decref (status_obj);
GNUNET_free (rgh->url); GNUNET_free (rsh->url);
GNUNET_free (rgh); GNUNET_free (rsh);
return NULL; return NULL;
} }
json_decref (status_obj); json_decref (status_obj);
} }
ctx = TEAH_handle_to_context (exchange); ctx = TEAH_handle_to_context (exchange);
rgh->job = GNUNET_CURL_job_add (ctx, rsh->job = GNUNET_CURL_job_add (ctx,
eh, eh,
&handle_reserves_status_finished, &handle_reserves_status_finished,
rgh); rsh);
return rgh; return rsh;
} }
void void
TALER_EXCHANGE_reserves_status_cancel ( TALER_EXCHANGE_reserves_status_cancel (
struct TALER_EXCHANGE_ReservesStatusHandle *rgh) struct TALER_EXCHANGE_ReservesStatusHandle *rsh)
{ {
if (NULL != rgh->job) if (NULL != rsh->job)
{ {
GNUNET_CURL_job_cancel (rgh->job); GNUNET_CURL_job_cancel (rsh->job);
rgh->job = NULL; rsh->job = NULL;
} }
TALER_curl_easy_post_finished (&rgh->post_ctx); TALER_curl_easy_post_finished (&rsh->post_ctx);
GNUNET_free (rgh->url); GNUNET_free (rsh->url);
GNUNET_free (rgh); GNUNET_free (rsh);
} }

View File

@ -177,14 +177,14 @@ reserve_withdraw_payment_required (
total incoming and outgoing amounts */ total incoming and outgoing amounts */
len = json_array_size (history); len = json_array_size (history);
{ {
struct TALER_EXCHANGE_ReserveHistory *rhistory; struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
/* Use heap allocation as "len" may be very big and thus this may /* Use heap allocation as "len" may be very big and thus this may
not fit on the stack. Use "GNUNET_malloc_large" as a malicious not fit on the stack. Use "GNUNET_malloc_large" as a malicious
exchange may theoretically try to crash us by giving a history exchange may theoretically try to crash us by giving a history
that does not fit into our memory. */ that does not fit into our memory. */
rhistory = GNUNET_malloc_large ( rhistory = GNUNET_malloc_large (
sizeof (struct TALER_EXCHANGE_ReserveHistory) sizeof (struct TALER_EXCHANGE_ReserveHistoryEntry)
* len); * len);
if (NULL == rhistory) if (NULL == rhistory)
{ {

View File

@ -74,6 +74,8 @@ libtalertesting_la_SOURCES = \
testing_api_cmd_recoup_refresh.c \ testing_api_cmd_recoup_refresh.c \
testing_api_cmd_refund.c \ testing_api_cmd_refund.c \
testing_api_cmd_refresh.c \ testing_api_cmd_refresh.c \
testing_api_cmd_reserve_history.c \
testing_api_cmd_reserve_status.c \
testing_api_cmd_revoke.c \ testing_api_cmd_revoke.c \
testing_api_cmd_revoke_denom_key.c \ testing_api_cmd_revoke_denom_key.c \
testing_api_cmd_revoke_sign_key.c \ testing_api_cmd_revoke_sign_key.c \

View File

@ -107,7 +107,7 @@ struct AdminAddIncomingState
* the "sender_url" field is set to a 'const char *' and * the "sender_url" field is set to a 'const char *' and
* MUST NOT be free()'ed. * MUST NOT be free()'ed.
*/ */
struct TALER_EXCHANGE_ReserveHistory reserve_history; struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
/** /**
* Set to the wire transfer's unique ID. * Set to the wire transfer's unique ID.

View File

@ -49,7 +49,7 @@ struct CloserState
* expect_close is true. Will be of type * expect_close is true. Will be of type
* #TALER_EXCHANGE_RTT_RESERVE_CLOSED. * #TALER_EXCHANGE_RTT_RESERVE_CLOSED.
*/ */
struct TALER_EXCHANGE_ReserveHistory reserve_history; struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
/** /**
* If the closer filled a reserve (@e expect_close is set), this is set to * If the closer filled a reserve (@e expect_close is set), this is set to

View File

@ -62,7 +62,7 @@ struct RecoupState
* Reserve history entry, set if this recoup actually filled up a reserve. * Reserve history entry, set if this recoup actually filled up a reserve.
* Otherwise `reserve_history.type` will be zero. * Otherwise `reserve_history.type` will be zero.
*/ */
struct TALER_EXCHANGE_ReserveHistory reserve_history; struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
}; };

View File

@ -0,0 +1,413 @@
/*
This file is part of TALER
Copyright (C) 2014-2020 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 testing/testing_api_cmd_history.c
* @brief Implement the /reserve/history test command.
* @author Marcello Stanisci
*/
#include "platform.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_curl_lib.h>
#include "taler_testing_lib.h"
/**
* State for a "history" CMD.
*/
struct HistoryState
{
/**
* Label to the command which created the reserve to check,
* needed to resort the reserve key.
*/
const char *reserve_reference;
/**
* Handle to the "reserve history" operation.
*/
struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
/**
* Expected reserve balance.
*/
const char *expected_balance;
/**
* Private key of the reserve being analyzed.
*/
const struct TALER_ReservePrivateKeyP *reserve_priv;
/**
* Public key of the reserve being analyzed.
*/
struct TALER_ReservePublicKeyP reserve_pub;
/**
* Expected HTTP response code.
*/
unsigned int expected_response_code;
/**
* Interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
};
/**
* Compare @a h1 and @a h2.
*
* @param h1 a history entry
* @param h2 a history entry
* @return 0 if @a h1 and @a h2 are equal
*/
static int
history_entry_cmp (const struct TALER_EXCHANGE_ReserveHistoryEntry *h1,
const struct TALER_EXCHANGE_ReserveHistoryEntry *h2)
{
if (h1->type != h2->type)
return 1;
switch (h1->type)
{
case TALER_EXCHANGE_RTT_CREDIT:
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 == strcasecmp (h1->details.in_details.sender_url,
h2->details.in_details.sender_url)) &&
(h1->details.in_details.wire_reference ==
h2->details.in_details.wire_reference) &&
(GNUNET_TIME_timestamp_cmp (h1->details.in_details.timestamp,
==,
h2->details.in_details.timestamp)) )
return 0;
return 1;
case TALER_EXCHANGE_RTT_WITHDRAWAL:
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 ==
TALER_amount_cmp (&h1->details.withdraw.fee,
&h2->details.withdraw.fee)) )
/* testing_api_cmd_withdraw doesn't set the out_authorization_sig,
so we cannot test for it here. but if the amount matches,
that should be good enough. */
return 0;
return 1;
case TALER_EXCHANGE_RTT_RECOUP:
/* exchange_sig, exchange_pub and timestamp are NOT available
from the original recoup response, hence here NOT check(able/ed) */
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 ==
GNUNET_memcmp (&h1->details.recoup_details.coin_pub,
&h2->details.recoup_details.coin_pub)) )
return 0;
return 1;
case TALER_EXCHANGE_RTT_CLOSE:
/* testing_api_cmd_exec_closer doesn't set the
receiver_account_details, exchange_sig, exchange_pub or wtid or timestamp
so we cannot test for it here. but if the amount matches,
that should be good enough. */
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 ==
TALER_amount_cmp (&h1->details.close_details.fee,
&h2->details.close_details.fee)) )
return 0;
return 1;
}
GNUNET_assert (0);
return 1;
}
/**
* Check if @a cmd changed the reserve, if so, find the
* entry in @a history and set the respective index in @a found
* to #GNUNET_YES. If the entry is not found, return #GNUNET_SYSERR.
*
* @param reserve_pub public key of the reserve for which we have the @a history
* @param cmd command to analyze for impact on history
* @param history_length number of entries in @a history and @a found
* @param history history to check
* @param[in,out] found array to update
* @return #GNUNET_OK if @a cmd action on reserve was found in @a history
*/
static enum GNUNET_GenericReturnValue
analyze_command (const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_TESTING_Command *cmd,
unsigned int history_length,
const struct TALER_EXCHANGE_ReserveHistoryEntry *history,
int *found)
{
if (TALER_TESTING_cmd_is_batch (cmd))
{
struct TALER_TESTING_Command *cur;
struct TALER_TESTING_Command **bcmd;
cur = TALER_TESTING_cmd_batch_get_current (cmd);
if (GNUNET_OK !=
TALER_TESTING_get_trait_batch_cmds (cmd,
&bcmd))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
for (unsigned int i = 0; NULL != (*bcmd)[i].label; i++)
{
struct TALER_TESTING_Command *step = &(*bcmd)[i];
if (step == cur)
break; /* if *we* are in a batch, make sure not to analyze commands past 'now' */
if (GNUNET_OK !=
analyze_command (reserve_pub,
step,
history_length,
history,
found))
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
else
{
const struct TALER_ReservePublicKeyP *rp;
const struct TALER_EXCHANGE_ReserveHistoryEntry *he;
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_pub (cmd,
&rp))
return GNUNET_OK; /* command does nothing for reserves */
if (0 !=
GNUNET_memcmp (rp,
reserve_pub))
return GNUNET_OK; /* command affects some _other_ reserve */
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_history (cmd,
&he))
{
/* NOTE: only for debugging... */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Command `%s' has the reserve_pub trait, but does not reserve history trait\n",
cmd->label);
return GNUNET_OK; /* command does nothing for reserves */
}
for (unsigned int i = 0; i<history_length; i++)
{
if (found[i])
continue; /* already found, skip */
if (0 ==
history_entry_cmp (he,
&history[i]))
{
found[i] = GNUNET_YES;
return GNUNET_OK;
}
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Command `%s' reserve history entry not found\n",
cmd->label);
return GNUNET_SYSERR;
}
}
/**
* Check that the reserve balance and HTTP response code are
* both acceptable.
*
* @param cls closure.
* @param rs HTTP response details
*/
static void
reserve_history_cb (void *cls,
const struct TALER_EXCHANGE_ReserveHistory *rs)
{
struct HistoryState *ss = cls;
struct TALER_TESTING_Interpreter *is = ss->is;
struct TALER_Amount eb;
ss->rsh = NULL;
if (ss->expected_response_code != rs->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected HTTP response code: %d in %s:%u\n",
rs->hr.http_status,
__FILE__,
__LINE__);
json_dumpf (rs->hr.reply,
stderr,
0);
TALER_TESTING_interpreter_fail (ss->is);
return;
}
if (MHD_HTTP_OK != rs->hr.http_status)
{
TALER_TESTING_interpreter_next (is);
return;
}
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (ss->expected_balance,
&eb));
if (0 != TALER_amount_cmp (&eb,
&rs->details.ok.balance))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected amount in reserve: %s\n",
TALER_amount_to_string (&rs->details.ok.balance));
TALER_TESTING_interpreter_fail (ss->is);
return;
}
{
int found[rs->details.ok.history_len];
memset (found,
0,
sizeof (found));
for (unsigned int i = 0; i<= (unsigned int) is->ip; i++)
{
struct TALER_TESTING_Command *cmd = &is->commands[i];
if (GNUNET_OK !=
analyze_command (&ss->reserve_pub,
cmd,
rs->details.ok.history_len,
rs->details.ok.history,
found))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Entry for command `%s' missing in history\n",
cmd->label);
TALER_TESTING_interpreter_fail (ss->is);
return;
}
}
for (unsigned int i = 0; i<rs->details.ok.history_len; i++)
if (! found[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"History entry at index %u of type %d not justified by command history\n",
i,
rs->details.ok.history[i].type);
TALER_TESTING_interpreter_fail (ss->is);
return;
}
}
TALER_TESTING_interpreter_next (is);
}
/**
* Run the command.
*
* @param cls closure.
* @param cmd the command being executed.
* @param is the interpreter state.
*/
static void
history_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
struct HistoryState *ss = cls;
const struct TALER_TESTING_Command *create_reserve;
ss->is = is;
create_reserve
= TALER_TESTING_interpreter_lookup_command (is,
ss->reserve_reference);
if (NULL == create_reserve)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_priv (create_reserve,
&ss->reserve_priv))
{
GNUNET_break (0);
TALER_LOG_ERROR ("Failed to find reserve_priv for history query\n");
TALER_TESTING_interpreter_fail (is);
return;
}
GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
&ss->reserve_pub.eddsa_pub);
ss->rsh = TALER_EXCHANGE_reserves_history (is->exchange,
ss->reserve_priv,
&reserve_history_cb,
ss);
}
/**
* Cleanup the state from a "reserve history" CMD, and possibly
* cancel a pending operation thereof.
*
* @param cls closure.
* @param cmd the command which is being cleaned up.
*/
static void
history_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
struct HistoryState *ss = cls;
if (NULL != ss->rsh)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Command %u (%s) did not complete\n",
ss->is->ip,
cmd->label);
TALER_EXCHANGE_reserves_history_cancel (ss->rsh);
ss->rsh = NULL;
}
GNUNET_free (ss);
}
struct TALER_TESTING_Command
TALER_TESTING_cmd_reserve_history (const char *label,
const char *reserve_reference,
const char *expected_balance,
unsigned int expected_response_code)
{
struct HistoryState *ss;
GNUNET_assert (NULL != reserve_reference);
ss = GNUNET_new (struct HistoryState);
ss->reserve_reference = reserve_reference;
ss->expected_balance = expected_balance;
ss->expected_response_code = expected_response_code;
{
struct TALER_TESTING_Command cmd = {
.cls = ss,
.label = label,
.run = &history_run,
.cleanup = &history_cleanup
};
return cmd;
}
}

View File

@ -0,0 +1,413 @@
/*
This file is part of TALER
Copyright (C) 2014-2020 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 testing/testing_api_cmd_status.c
* @brief Implement the /reserve/$RID/status test command.
* @author Marcello Stanisci
*/
#include "platform.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_curl_lib.h>
#include "taler_testing_lib.h"
/**
* State for a "status" CMD.
*/
struct StatusState
{
/**
* Label to the command which created the reserve to check,
* needed to resort the reserve key.
*/
const char *reserve_reference;
/**
* Handle to the "reserve status" operation.
*/
struct TALER_EXCHANGE_ReservesStatusHandle *rsh;
/**
* Expected reserve balance.
*/
const char *expected_balance;
/**
* Private key of the reserve being analyzed.
*/
const struct TALER_ReservePrivateKeyP *reserve_priv;
/**
* Public key of the reserve being analyzed.
*/
struct TALER_ReservePublicKeyP reserve_pub;
/**
* Expected HTTP response code.
*/
unsigned int expected_response_code;
/**
* Interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
};
/**
* Compare @a h1 and @a h2.
*
* @param h1 a history entry
* @param h2 a history entry
* @return 0 if @a h1 and @a h2 are equal
*/
static int
history_entry_cmp (const struct TALER_EXCHANGE_ReserveHistoryEntry *h1,
const struct TALER_EXCHANGE_ReserveHistoryEntry *h2)
{
if (h1->type != h2->type)
return 1;
switch (h1->type)
{
case TALER_EXCHANGE_RTT_CREDIT:
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 == strcasecmp (h1->details.in_details.sender_url,
h2->details.in_details.sender_url)) &&
(h1->details.in_details.wire_reference ==
h2->details.in_details.wire_reference) &&
(GNUNET_TIME_timestamp_cmp (h1->details.in_details.timestamp,
==,
h2->details.in_details.timestamp)) )
return 0;
return 1;
case TALER_EXCHANGE_RTT_WITHDRAWAL:
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 ==
TALER_amount_cmp (&h1->details.withdraw.fee,
&h2->details.withdraw.fee)) )
/* testing_api_cmd_withdraw doesn't set the out_authorization_sig,
so we cannot test for it here. but if the amount matches,
that should be good enough. */
return 0;
return 1;
case TALER_EXCHANGE_RTT_RECOUP:
/* exchange_sig, exchange_pub and timestamp are NOT available
from the original recoup response, hence here NOT check(able/ed) */
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 ==
GNUNET_memcmp (&h1->details.recoup_details.coin_pub,
&h2->details.recoup_details.coin_pub)) )
return 0;
return 1;
case TALER_EXCHANGE_RTT_CLOSE:
/* testing_api_cmd_exec_closer doesn't set the
receiver_account_details, exchange_sig, exchange_pub or wtid or timestamp
so we cannot test for it here. but if the amount matches,
that should be good enough. */
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 ==
TALER_amount_cmp (&h1->details.close_details.fee,
&h2->details.close_details.fee)) )
return 0;
return 1;
}
GNUNET_assert (0);
return 1;
}
/**
* Check if @a cmd changed the reserve, if so, find the
* entry in @a history and set the respective index in @a found
* to #GNUNET_YES. If the entry is not found, return #GNUNET_SYSERR.
*
* @param reserve_pub public key of the reserve for which we have the @a history
* @param cmd command to analyze for impact on history
* @param history_length number of entries in @a history and @a found
* @param history history to check
* @param[in,out] found array to update
* @return #GNUNET_OK if @a cmd action on reserve was found in @a history
*/
static enum GNUNET_GenericReturnValue
analyze_command (const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_TESTING_Command *cmd,
unsigned int history_length,
const struct TALER_EXCHANGE_ReserveHistoryEntry *history,
int *found)
{
if (TALER_TESTING_cmd_is_batch (cmd))
{
struct TALER_TESTING_Command *cur;
struct TALER_TESTING_Command **bcmd;
cur = TALER_TESTING_cmd_batch_get_current (cmd);
if (GNUNET_OK !=
TALER_TESTING_get_trait_batch_cmds (cmd,
&bcmd))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
for (unsigned int i = 0; NULL != (*bcmd)[i].label; i++)
{
struct TALER_TESTING_Command *step = &(*bcmd)[i];
if (step == cur)
break; /* if *we* are in a batch, make sure not to analyze commands past 'now' */
if (GNUNET_OK !=
analyze_command (reserve_pub,
step,
history_length,
history,
found))
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
else
{
const struct TALER_ReservePublicKeyP *rp;
const struct TALER_EXCHANGE_ReserveHistoryEntry *he;
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_pub (cmd,
&rp))
return GNUNET_OK; /* command does nothing for reserves */
if (0 !=
GNUNET_memcmp (rp,
reserve_pub))
return GNUNET_OK; /* command affects some _other_ reserve */
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_history (cmd,
&he))
{
/* NOTE: only for debugging... */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Command `%s' has the reserve_pub trait, but does not reserve history trait\n",
cmd->label);
return GNUNET_OK; /* command does nothing for reserves */
}
for (unsigned int i = 0; i<history_length; i++)
{
if (found[i])
continue; /* already found, skip */
if (0 ==
history_entry_cmp (he,
&history[i]))
{
found[i] = GNUNET_YES;
return GNUNET_OK;
}
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Command `%s' reserve history entry not found\n",
cmd->label);
return GNUNET_SYSERR;
}
}
/**
* Check that the reserve balance and HTTP response code are
* both acceptable.
*
* @param cls closure.
* @param rs HTTP response details
*/
static void
reserve_status_cb (void *cls,
const struct TALER_EXCHANGE_ReserveStatus *rs)
{
struct StatusState *ss = cls;
struct TALER_TESTING_Interpreter *is = ss->is;
struct TALER_Amount eb;
ss->rsh = NULL;
if (ss->expected_response_code != rs->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected HTTP response code: %d in %s:%u\n",
rs->hr.http_status,
__FILE__,
__LINE__);
json_dumpf (rs->hr.reply,
stderr,
0);
TALER_TESTING_interpreter_fail (ss->is);
return;
}
if (MHD_HTTP_OK != rs->hr.http_status)
{
TALER_TESTING_interpreter_next (is);
return;
}
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (ss->expected_balance,
&eb));
if (0 != TALER_amount_cmp (&eb,
&rs->details.ok.balance))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected amount in reserve: %s\n",
TALER_amount_to_string (&rs->details.ok.balance));
TALER_TESTING_interpreter_fail (ss->is);
return;
}
{
int found[rs->details.ok.history_len];
memset (found,
0,
sizeof (found));
for (unsigned int i = 0; i<= (unsigned int) is->ip; i++)
{
struct TALER_TESTING_Command *cmd = &is->commands[i];
if (GNUNET_OK !=
analyze_command (&ss->reserve_pub,
cmd,
rs->details.ok.history_len,
rs->details.ok.history,
found))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Entry for command `%s' missing in history\n",
cmd->label);
TALER_TESTING_interpreter_fail (ss->is);
return;
}
}
for (unsigned int i = 0; i<rs->details.ok.history_len; i++)
if (! found[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"History entry at index %u of type %d not justified by command status\n",
i,
rs->details.ok.history[i].type);
TALER_TESTING_interpreter_fail (ss->is);
return;
}
}
TALER_TESTING_interpreter_next (is);
}
/**
* Run the command.
*
* @param cls closure.
* @param cmd the command being executed.
* @param is the interpreter state.
*/
static void
status_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
struct StatusState *ss = cls;
const struct TALER_TESTING_Command *create_reserve;
ss->is = is;
create_reserve
= TALER_TESTING_interpreter_lookup_command (is,
ss->reserve_reference);
if (NULL == create_reserve)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_priv (create_reserve,
&ss->reserve_priv))
{
GNUNET_break (0);
TALER_LOG_ERROR ("Failed to find reserve_priv for status query\n");
TALER_TESTING_interpreter_fail (is);
return;
}
GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
&ss->reserve_pub.eddsa_pub);
ss->rsh = TALER_EXCHANGE_reserves_status (is->exchange,
ss->reserve_priv,
&reserve_status_cb,
ss);
}
/**
* Cleanup the state from a "reserve status" CMD, and possibly
* cancel a pending operation thereof.
*
* @param cls closure.
* @param cmd the command which is being cleaned up.
*/
static void
status_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
struct StatusState *ss = cls;
if (NULL != ss->rsh)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Command %u (%s) did not complete\n",
ss->is->ip,
cmd->label);
TALER_EXCHANGE_reserves_status_cancel (ss->rsh);
ss->rsh = NULL;
}
GNUNET_free (ss);
}
struct TALER_TESTING_Command
TALER_TESTING_cmd_reserve_status (const char *label,
const char *reserve_reference,
const char *expected_balance,
unsigned int expected_response_code)
{
struct StatusState *ss;
GNUNET_assert (NULL != reserve_reference);
ss = GNUNET_new (struct StatusState);
ss->reserve_reference = reserve_reference;
ss->expected_balance = expected_balance;
ss->expected_response_code = expected_response_code;
{
struct TALER_TESTING_Command cmd = {
.cls = ss,
.label = label,
.run = &status_run,
.cleanup = &status_cleanup
};
return cmd;
}
}

View File

@ -18,7 +18,7 @@
*/ */
/** /**
* @file testing/testing_api_cmd_status.c * @file testing/testing_api_cmd_status.c
* @brief Implement the /reserve/status test command. * @brief Implement the GET /reserve/$RID test command.
* @author Marcello Stanisci * @author Marcello Stanisci
*/ */
#include "platform.h" #include "platform.h"
@ -65,252 +65,52 @@ struct StatusState
}; };
/**
* Compare @a h1 and @a h2.
*
* @param h1 a history entry
* @param h2 a history entry
* @return 0 if @a h1 and @a h2 are equal
*/
static int
history_entry_cmp (const struct TALER_EXCHANGE_ReserveHistory *h1,
const struct TALER_EXCHANGE_ReserveHistory *h2)
{
if (h1->type != h2->type)
return 1;
switch (h1->type)
{
case TALER_EXCHANGE_RTT_CREDIT:
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 == strcasecmp (h1->details.in_details.sender_url,
h2->details.in_details.sender_url)) &&
(h1->details.in_details.wire_reference ==
h2->details.in_details.wire_reference) &&
(GNUNET_TIME_timestamp_cmp (h1->details.in_details.timestamp,
==,
h2->details.in_details.timestamp)) )
return 0;
return 1;
case TALER_EXCHANGE_RTT_WITHDRAWAL:
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 ==
TALER_amount_cmp (&h1->details.withdraw.fee,
&h2->details.withdraw.fee)) )
/* testing_api_cmd_withdraw doesn't set the out_authorization_sig,
so we cannot test for it here. but if the amount matches,
that should be good enough. */
return 0;
return 1;
case TALER_EXCHANGE_RTT_RECOUP:
/* exchange_sig, exchange_pub and timestamp are NOT available
from the original recoup response, hence here NOT check(able/ed) */
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 ==
GNUNET_memcmp (&h1->details.recoup_details.coin_pub,
&h2->details.recoup_details.coin_pub)) )
return 0;
return 1;
case TALER_EXCHANGE_RTT_CLOSE:
/* testing_api_cmd_exec_closer doesn't set the
receiver_account_details, exchange_sig, exchange_pub or wtid or timestamp
so we cannot test for it here. but if the amount matches,
that should be good enough. */
if ( (0 ==
TALER_amount_cmp (&h1->amount,
&h2->amount)) &&
(0 ==
TALER_amount_cmp (&h1->details.close_details.fee,
&h2->details.close_details.fee)) )
return 0;
return 1;
}
GNUNET_assert (0);
return 1;
}
/**
* Check if @a cmd changed the reserve, if so, find the
* entry in @a history and set the respective index in @a found
* to #GNUNET_YES. If the entry is not found, return #GNUNET_SYSERR.
*
* @param reserve_pub public key of the reserve for which we have the @a history
* @param cmd command to analyze for impact on history
* @param history_length number of entries in @a history and @a found
* @param history history to check
* @param[in,out] found array to update
* @return #GNUNET_OK if @a cmd action on reserve was found in @a history
*/
static enum GNUNET_GenericReturnValue
analyze_command (const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_TESTING_Command *cmd,
unsigned int history_length,
const struct TALER_EXCHANGE_ReserveHistory *history,
int *found)
{
if (TALER_TESTING_cmd_is_batch (cmd))
{
struct TALER_TESTING_Command *cur;
struct TALER_TESTING_Command **bcmd;
cur = TALER_TESTING_cmd_batch_get_current (cmd);
if (GNUNET_OK !=
TALER_TESTING_get_trait_batch_cmds (cmd,
&bcmd))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
for (unsigned int i = 0; NULL != (*bcmd)[i].label; i++)
{
struct TALER_TESTING_Command *step = &(*bcmd)[i];
if (step == cur)
break; /* if *we* are in a batch, make sure not to analyze commands past 'now' */
if (GNUNET_OK !=
analyze_command (reserve_pub,
step,
history_length,
history,
found))
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
else
{
const struct TALER_ReservePublicKeyP *rp;
const struct TALER_EXCHANGE_ReserveHistory *he;
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_pub (cmd,
&rp))
return GNUNET_OK; /* command does nothing for reserves */
if (0 !=
GNUNET_memcmp (rp,
reserve_pub))
return GNUNET_OK; /* command affects some _other_ reserve */
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_history (cmd,
&he))
{
/* NOTE: only for debugging... */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Command `%s' has the reserve_pub trait, but does not reserve history trait\n",
cmd->label);
return GNUNET_OK; /* command does nothing for reserves */
}
for (unsigned int i = 0; i<history_length; i++)
{
if (found[i])
continue; /* already found, skip */
if (0 ==
history_entry_cmp (he,
&history[i]))
{
found[i] = GNUNET_YES;
return GNUNET_OK;
}
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Command `%s' reserve history entry not found\n",
cmd->label);
return GNUNET_SYSERR;
}
}
/** /**
* Check that the reserve balance and HTTP response code are * Check that the reserve balance and HTTP response code are
* both acceptable. * both acceptable.
* *
* @param cls closure. * @param cls closure.
* @param hr HTTP response details * @param rs HTTP response details
* @param balance current balance in the reserve, NULL on error.
* @param history_length number of entries in the transaction
* history, 0 on error.
* @param history detailed transaction history, NULL on error.
*/ */
static void static void
reserve_status_cb (void *cls, reserve_status_cb (void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr, const struct TALER_EXCHANGE_ReserveSummary *rs)
const struct TALER_Amount *balance,
unsigned int history_length,
const struct TALER_EXCHANGE_ReserveHistory *history)
{ {
struct StatusState *ss = cls; struct StatusState *ss = cls;
struct TALER_TESTING_Interpreter *is = ss->is; struct TALER_TESTING_Interpreter *is = ss->is;
struct TALER_Amount eb; struct TALER_Amount eb;
ss->rsh = NULL; ss->rsh = NULL;
if (ss->expected_response_code != hr->http_status) if (ss->expected_response_code != rs->hr.http_status)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected HTTP response code: %d in %s:%u\n", "Unexpected HTTP response code: %d in %s:%u\n",
hr->http_status, rs->hr.http_status,
__FILE__, __FILE__,
__LINE__); __LINE__);
json_dumpf (hr->reply, json_dumpf (rs->hr.reply,
stderr, stderr,
0); 0);
TALER_TESTING_interpreter_fail (ss->is); TALER_TESTING_interpreter_fail (ss->is);
return; return;
} }
if (MHD_HTTP_OK != ss->expected_response_code)
{
TALER_TESTING_interpreter_next (is);
return;
}
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (ss->expected_balance, TALER_string_to_amount (ss->expected_balance,
&eb)); &eb));
if (0 != TALER_amount_cmp (&eb, if (0 != TALER_amount_cmp (&eb,
balance)) &rs->details.ok.balance))
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected amount in reserve: %s\n", "Unexpected amount in reserve: %s\n",
TALER_amount_to_string (balance)); TALER_amount_to_string (&rs->details.ok.balance));
TALER_TESTING_interpreter_fail (ss->is); TALER_TESTING_interpreter_fail (ss->is);
return; return;
} }
{
int found[history_length];
memset (found,
0,
sizeof (found));
for (unsigned int i = 0; i<= (unsigned int) is->ip; i++)
{
struct TALER_TESTING_Command *cmd = &is->commands[i];
if (GNUNET_OK !=
analyze_command (ss->reserve_pubp,
cmd,
history_length,
history,
found))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Entry for command `%s' missing in history\n",
cmd->label);
TALER_TESTING_interpreter_fail (ss->is);
return;
}
}
for (unsigned int i = 0; i<history_length; i++)
if (! found[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"History entry at index %u of type %d not justified by command history\n",
i,
history[i].type);
TALER_TESTING_interpreter_fail (ss->is);
return;
}
}
TALER_TESTING_interpreter_next (is); TALER_TESTING_interpreter_next (is);
} }

View File

@ -148,7 +148,7 @@ struct WithdrawState
* Reserve history entry that corresponds to this operation. * Reserve history entry that corresponds to this operation.
* Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL. * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
*/ */
struct TALER_EXCHANGE_ReserveHistory reserve_history; struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
/** /**
* Withdraw handle (while operation is running). * Withdraw handle (while operation is running).