diff options
author | Christian Grothoff <christian@grothoff.org> | 2016-01-24 15:17:36 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2016-01-24 15:17:36 +0100 |
commit | d89c91e64aac6b8ba0a77eb8b95d975776969e2a (patch) | |
tree | ead8a1cac5ac5419370b7e61b414a3298f2a6863 /src/mint-lib | |
parent | 76b5350c30bb6d36ee548a7de554f388b5068e01 (diff) | |
parent | d6553966f11e24f8f86aa21c0ce3760b6e966006 (diff) |
Merge branch 'master' of git+ssh://taler.net/var/git/mint
Diffstat (limited to 'src/mint-lib')
-rw-r--r-- | src/mint-lib/Makefile.am | 4 | ||||
-rw-r--r-- | src/mint-lib/mint_api_context.c | 2 | ||||
-rw-r--r-- | src/mint-lib/mint_api_deposit_wtid.c | 386 | ||||
-rw-r--r-- | src/mint-lib/mint_api_handle.c | 2 | ||||
-rw-r--r-- | src/mint-lib/mint_api_json.c | 34 | ||||
-rw-r--r-- | src/mint-lib/mint_api_json.h | 21 | ||||
-rw-r--r-- | src/mint-lib/mint_api_refresh_link.c | 27 | ||||
-rw-r--r-- | src/mint-lib/mint_api_wire_deposits.c | 284 | ||||
-rw-r--r-- | src/mint-lib/test_mint_api.c | 406 |
9 files changed, 1138 insertions, 28 deletions
diff --git a/src/mint-lib/Makefile.am b/src/mint-lib/Makefile.am index ccea4ec58..171a42464 100644 --- a/src/mint-lib/Makefile.am +++ b/src/mint-lib/Makefile.am @@ -20,10 +20,12 @@ libtalermint_la_SOURCES = \ mint_api_handle.c mint_api_handle.h \ mint_api_admin.c \ mint_api_deposit.c \ + mint_api_deposit_wtid.c \ mint_api_refresh.c \ mint_api_refresh_link.c \ mint_api_reserve.c \ - mint_api_wire.c + mint_api_wire.c \ + mint_api_wire_deposits.c libtalermint_la_LIBADD = \ -lgnunetutil \ diff --git a/src/mint-lib/mint_api_context.c b/src/mint-lib/mint_api_context.c index 3a86efb64..2767906b5 100644 --- a/src/mint-lib/mint_api_context.c +++ b/src/mint-lib/mint_api_context.c @@ -288,7 +288,7 @@ TALER_MINT_perform (struct TALER_MINT_Context *ctx) GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle, CURLINFO_PRIVATE, - (char *) &job)); + (char **) &job)); GNUNET_assert (job->ctx == ctx); job->jcc (job->jcc_cls, cmsg->easy_handle); diff --git a/src/mint-lib/mint_api_deposit_wtid.c b/src/mint-lib/mint_api_deposit_wtid.c new file mode 100644 index 000000000..50f9c55d8 --- /dev/null +++ b/src/mint-lib/mint_api_deposit_wtid.c @@ -0,0 +1,386 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + 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 + <http://www.gnu.org/licenses/> +*/ +/** + * @file mint-lib/mint_api_deposit_wtid.c + * @brief Implementation of the /deposit/wtid request of the mint's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_mint_service.h" +#include "mint_api_common.h" +#include "mint_api_json.h" +#include "mint_api_context.h" +#include "mint_api_handle.h" +#include "taler_signatures.h" + + +/** + * @brief A Deposit Wtid Handle + */ +struct TALER_MINT_DepositWtidHandle +{ + + /** + * The connection to mint this request handle will use + */ + struct TALER_MINT_Handle *mint; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct MAC_Job *job; + + /** + * Function to call with the result. + */ + TALER_MINT_DepositWtidCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Download buffer + */ + struct MAC_DownloadBuffer db; + + /** + * Information the mint should sign in response. + * (with pre-filled fields from the request). + */ + struct TALER_ConfirmWirePS depconf; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the mint is valid. + * + * @param dwh deposit wtid handle + * @param json json reply with the signature + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_wtid_signature_ok (const struct TALER_MINT_DepositWtidHandle *dwh, + json_t *json) +{ + struct TALER_MintSignatureP mint_sig; + struct TALER_MintPublicKeyP mint_pub; + const struct TALER_MINT_Keys *key_state; + struct MAJ_Specification spec[] = { + MAJ_spec_fixed_auto ("mint_sig", &mint_sig), + MAJ_spec_fixed_auto ("mint_pub", &mint_pub), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = TALER_MINT_get_keys (dwh->mint); + if (GNUNET_OK != + TALER_MINT_test_signing_key (key_state, + &mint_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MINT_CONFIRM_WIRE, + &dwh->depconf.purpose, + &mint_sig.eddsa_signature, + &mint_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /deposit/wtid request. + * + * @param cls the `struct TALER_MINT_DepositWtidHandle` + * @param eh the curl request handle + */ +static void +handle_deposit_wtid_finished (void *cls, + CURL *eh) +{ + struct TALER_MINT_DepositWtidHandle *dwh = cls; + long response_code; + json_t *json; + const struct TALER_WireTransferIdentifierRawP *wtid = NULL; + struct GNUNET_TIME_Absolute execution_time = GNUNET_TIME_UNIT_FOREVER_ABS; + const struct TALER_Amount *coin_contribution = NULL; + const struct TALER_Amount *total_amount = NULL; + struct TALER_Amount coin_contribution_s; + struct TALER_Amount total_amount_s; + + dwh->job = NULL; + json = MAC_download_get_result (&dwh->db, + eh, + &response_code); + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + struct MAJ_Specification spec[] = { + MAJ_spec_fixed_auto ("wtid", &dwh->depconf.wtid), + MAJ_spec_absolute_time ("execution_time", &execution_time), + MAJ_spec_amount ("coin_contribution", &coin_contribution_s), + MAJ_spec_amount ("total_amount", &total_amount_s), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + wtid = &dwh->depconf.wtid; + dwh->depconf.execution_time = GNUNET_TIME_absolute_hton (execution_time); + TALER_amount_hton (&dwh->depconf.coin_contribution, + &coin_contribution_s); + coin_contribution = &coin_contribution_s; + TALER_amount_hton (&dwh->depconf.total_amount, + &total_amount_s); + total_amount = &total_amount_s; + if (GNUNET_OK != + verify_deposit_wtid_signature_ok (dwh, + json)) + { + GNUNET_break_op (0); + response_code = 0; + } + } + break; + case MHD_HTTP_ACCEPTED: + { + /* Transaction known, but not executed yet */ + struct MAJ_Specification spec[] = { + MAJ_spec_absolute_time ("execution_time", &execution_time), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the mint is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, mint says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Mint does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + response_code); + GNUNET_break (0); + response_code = 0; + break; + } + dwh->cb (dwh->cb_cls, + response_code, + json, + wtid, + execution_time, + coin_contribution, + total_amount); + json_decref (json); + TALER_MINT_deposit_wtid_cancel (dwh); +} + + +/** + * Obtain wire transfer details about an existing deposit operation. + * + * @param mint the mint to query + * @param merchant_priv the merchant's private key + * @param h_wire hash of merchant's wire transfer details + * @param h_contract hash of the contract + * @param coin_pub public key of the coin + * @param transaction_id transaction identifier + * @param cb function to call with the result + * @param cb_cls closure for @a cb + * @return handle to abort request + */ +struct TALER_MINT_DepositWtidHandle * +TALER_MINT_deposit_wtid (struct TALER_MINT_Handle *mint, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t transaction_id, + TALER_MINT_DepositWtidCallback cb, + void *cb_cls) +{ + struct TALER_DepositTrackPS dtp; + struct TALER_MerchantSignatureP merchant_sig; + struct TALER_MINT_DepositWtidHandle *dwh; + struct TALER_MINT_Context *ctx; + json_t *deposit_wtid_obj; + CURL *eh; + + if (GNUNET_YES != + MAH_handle_is_ready (mint)) + { + GNUNET_break (0); + return NULL; + } + dtp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_DEPOSIT_WTID); + dtp.purpose.size = htonl (sizeof (dtp)); + dtp.h_contract = *h_contract; + dtp.h_wire = *h_wire; + dtp.transaction_id = GNUNET_htonll (transaction_id); + GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, + &dtp.merchant.eddsa_pub); + + dtp.coin_pub = *coin_pub; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, + &dtp.purpose, + &merchant_sig.eddsa_sig)); + deposit_wtid_obj = json_pack ("{s:o, s:o," /* H_wire, H_contract */ + " s:o, s:I," /* coin_pub, transaction_id */ + " s:o, s:o}", /* merchant_pub, merchant_sig */ + "H_wire", TALER_json_from_data (h_wire, + sizeof (struct GNUNET_HashCode)), + "H_contract", TALER_json_from_data (h_contract, + sizeof (struct GNUNET_HashCode)), + "coin_pub", TALER_json_from_data (coin_pub, + sizeof (*coin_pub)), + "transaction_id", (json_int_t) transaction_id, + "merchant_pub", TALER_json_from_data (&dtp.merchant, + sizeof (struct TALER_MerchantPublicKeyP)), + "merchant_sig", TALER_json_from_data (&merchant_sig, + sizeof (merchant_sig))); + + dwh = GNUNET_new (struct TALER_MINT_DepositWtidHandle); + dwh->mint = mint; + dwh->cb = cb; + dwh->cb_cls = cb_cls; + dwh->url = MAH_path_to_url (mint, "/deposit/wtid"); + dwh->depconf.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); + dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_MINT_CONFIRM_WIRE); + dwh->depconf.h_wire = *h_wire; + dwh->depconf.h_contract = *h_contract; + dwh->depconf.coin_pub = *coin_pub; + dwh->depconf.transaction_id = GNUNET_htonll (transaction_id); + + eh = curl_easy_init (); + GNUNET_assert (NULL != (dwh->json_enc = + json_dumps (deposit_wtid_obj, + JSON_COMPACT))); + json_decref (deposit_wtid_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + dwh->url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + dwh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (dwh->json_enc))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEFUNCTION, + &MAC_download_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEDATA, + &dwh->db)); + ctx = MAH_handle_to_context (mint); + dwh->job = MAC_job_add (ctx, + eh, + GNUNET_YES, + &handle_deposit_wtid_finished, + dwh); + return dwh; +} + + +/** + * Cancel deposit wtid request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param dwh the wire deposits request handle + */ +void +TALER_MINT_deposit_wtid_cancel (struct TALER_MINT_DepositWtidHandle *dwh) +{ + if (NULL != dwh->job) + { + MAC_job_cancel (dwh->job); + dwh->job = NULL; + } + GNUNET_free_non_null (dwh->db.buf); + GNUNET_free (dwh->url); + GNUNET_free (dwh->json_enc); + GNUNET_free (dwh); +} + + +/* end of mint_api_deposit_wtid.c */ diff --git a/src/mint-lib/mint_api_handle.c b/src/mint-lib/mint_api_handle.c index 077e42c24..c8898d5c3 100644 --- a/src/mint-lib/mint_api_handle.c +++ b/src/mint-lib/mint_api_handle.c @@ -757,7 +757,7 @@ TALER_MINT_connect (struct TALER_MINT_Context *ctx, GNUNET_assert (CURLE_OK == curl_easy_setopt (c, CURLOPT_VERBOSE, - 1)); + 0)); GNUNET_assert (CURLE_OK == curl_easy_setopt (c, CURLOPT_STDERR, diff --git a/src/mint-lib/mint_api_json.c b/src/mint-lib/mint_api_json.c index a728a5495..7de33e5eb 100644 --- a/src/mint-lib/mint_api_json.c +++ b/src/mint-lib/mint_api_json.c @@ -232,6 +232,20 @@ parse_json (json_t *root, } break; + case MAJ_CMD_UINT64: + { + json_int_t val; + + if (! json_is_integer (pos)) + { + GNUNET_break_op (0); + return i; + } + val = json_integer_value (pos); + *spec[i].details.u64 = (uint64_t) val; + } + break; + case MAJ_CMD_JSON_OBJECT: { if (! (json_is_object (pos) || json_is_array (pos)) ) @@ -429,6 +443,26 @@ MAJ_spec_uint16 (const char *name, /** + * 64-bit integer. + * + * @param name name of the JSON field + * @param[out] u64 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint64 (const char *name, + uint64_t *u64) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_UINT64, + .field = name, + .details.u64 = u64 + }; + return ret; +} + + +/** * JSON object. * * @param name name of the JSON field diff --git a/src/mint-lib/mint_api_json.h b/src/mint-lib/mint_api_json.h index 68809059e..6bc3a5572 100644 --- a/src/mint-lib/mint_api_json.h +++ b/src/mint-lib/mint_api_json.h @@ -79,6 +79,11 @@ enum MAJ_Command MAJ_CMD_UINT16, /** + * Parse `uint64_t` integer at the current position. + */ + MAJ_CMD_UINT64, + + /** * Parse JSON object at the current position. */ MAJ_CMD_JSON_OBJECT, @@ -192,6 +197,11 @@ struct MAJ_Specification uint16_t *u16; /** + * Where to store 64-bit integer. + */ + uint64_t *u64; + + /** * Where to store a JSON object. */ json_t **obj; @@ -283,6 +293,17 @@ MAJ_spec_uint16 (const char *name, /** + * 64-bit integer. + * + * @param name name of the JSON field + * @param[out] u64 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint64 (const char *name, + uint64_t *u64); + + +/** * JSON object. * * @param name name of the JSON field diff --git a/src/mint-lib/mint_api_refresh_link.c b/src/mint-lib/mint_api_refresh_link.c index 9ae55b2ae..dcd2326ca 100644 --- a/src/mint-lib/mint_api_refresh_link.c +++ b/src/mint-lib/mint_api_refresh_link.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015 GNUnet e.V. + Copyright (C) 2015, 2016 GNUnet e.V. 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 @@ -185,6 +185,17 @@ parse_refresh_link_ok (struct TALER_MINT_RefreshLinkHandle *rlh, return GNUNET_SYSERR; } num_coins = 0; + /* Theoretically, a coin may have been melted repeatedly + into different sessions; so the response is an array + which contains information by melting session. That + array contains another array. However, our API returns + a single 1d array, so we flatten the 2d array that is + returned into a single array. Note that usually a coin + is melted at most once, and so we'll only run this + loop once for 'session=0' in most cases. + + num_coins tracks the size of the 1d array we return, + whilst 'i' and 'session' track the 2d array. */ for (session=0;session<json_array_size (json); session++) { json_t *jsona; @@ -212,13 +223,17 @@ parse_refresh_link_ok (struct TALER_MINT_RefreshLinkHandle *rlh, num_coins += json_array_size (jsona); MAJ_parse_free (spec); } + /* Now that we know how big the 1d array is, allocate + and fill it. */ { - unsigned int off_coin; + unsigned int off_coin; /* index into 1d array */ unsigned int i; struct TALER_CoinSpendPrivateKeyP coin_privs[num_coins]; struct TALER_DenominationSignature sigs[num_coins]; struct TALER_DenominationPublicKey pubs[num_coins]; + memset (sigs, 0, sizeof (sigs)); + memset (pubs, 0, sizeof (pubs)); off_coin = 0; for (session=0;session<json_array_size (json); session++) { @@ -265,6 +280,7 @@ parse_refresh_link_ok (struct TALER_MINT_RefreshLinkHandle *rlh, } } /* check if we really got all, then invoke callback */ + off_coin += i; if (i != json_array_size (jsona)) { GNUNET_break_op (0); @@ -272,7 +288,6 @@ parse_refresh_link_ok (struct TALER_MINT_RefreshLinkHandle *rlh, MAJ_parse_free (spec); break; } - off_coin += json_array_size (jsona); MAJ_parse_free (spec); } /* end of for (session) */ @@ -295,9 +310,13 @@ parse_refresh_link_ok (struct TALER_MINT_RefreshLinkHandle *rlh, } /* clean up */ - for (i=0;i<num_coins;i++) + for (i=0;i<off_coin;i++) + { if (NULL != sigs[i].rsa_signature) GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); + if (NULL != pubs[i].rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (pubs[i].rsa_public_key); + } } return ret; } diff --git a/src/mint-lib/mint_api_wire_deposits.c b/src/mint-lib/mint_api_wire_deposits.c new file mode 100644 index 000000000..f71c5b696 --- /dev/null +++ b/src/mint-lib/mint_api_wire_deposits.c @@ -0,0 +1,284 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + 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 + <http://www.gnu.org/licenses/> +*/ +/** + * @file mint-lib/mint_api_wire_deposits.c + * @brief Implementation of the /wire/deposits request of the mint's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_mint_service.h" +#include "mint_api_common.h" +#include "mint_api_json.h" +#include "mint_api_context.h" +#include "mint_api_handle.h" +#include "taler_signatures.h" + + +/** + * @brief A /wire/deposits Handle + */ +struct TALER_MINT_WireDepositsHandle +{ + + /** + * The connection to mint this request handle will use + */ + struct TALER_MINT_Handle *mint; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct MAC_Job *job; + + /** + * Function to call with the result. + */ + TALER_MINT_WireDepositsCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Download buffer + */ + struct MAC_DownloadBuffer db; + +}; + + +/** + * Function called when we're done processing the + * HTTP /wire/deposits request. + * + * @param cls the `struct TALER_MINT_WireDepositsHandle` + * @param eh the curl request handle + */ +static void +handle_wire_deposits_finished (void *cls, + CURL *eh) +{ + struct TALER_MINT_WireDepositsHandle *wdh = cls; + long response_code; + json_t *json; + + wdh->job = NULL; + json = MAC_download_get_result (&wdh->db, + eh, + &response_code); + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + json_t *details_j; + struct GNUNET_HashCode h_wire; + struct TALER_Amount total_amount; + struct TALER_MerchantPublicKeyP merchant_pub; + unsigned int num_details; + struct MAJ_Specification spec[] = { + MAJ_spec_fixed_auto ("H_wire", &h_wire), + MAJ_spec_fixed_auto ("merchant_pub", &merchant_pub), + MAJ_spec_amount ("total_amount", &total_amount), + MAJ_spec_json ("details", &details_j), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + num_details = json_array_size (details_j); + { + struct TALER_WireDepositDetails details[num_details]; + unsigned int i; + + for (i=0;i<num_details;i++) + { + struct TALER_WireDepositDetails *detail = &details[i]; + struct json_t *detail_j = json_array_get (details_j, i); + struct MAJ_Specification spec_detail[] = { + MAJ_spec_fixed_auto ("H_contract", &detail->h_contract), + MAJ_spec_amount ("deposit_value", &detail->coin_value), + MAJ_spec_amount ("deposit_fee", &detail->coin_fee), + MAJ_spec_uint64 ("transaction_id", &detail->transaction_id), + MAJ_spec_fixed_auto ("coin_pub", &detail->coin_pub), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (detail_j, + spec_detail)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + } + if (0 == response_code) + break; + wdh->cb (wdh->cb_cls, + response_code, + json, + &h_wire, + &total_amount, + num_details, + details); + json_decref (json); + TALER_MINT_wire_deposits_cancel (wdh); + return; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the mint is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, mint says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Mint does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + response_code); + GNUNET_break (0); + response_code = 0; + break; + } + wdh->cb (wdh->cb_cls, + response_code, + json, + NULL, NULL, 0, NULL); + json_decref (json); + TALER_MINT_wire_deposits_cancel (wdh); +} + + +/** + * Query the mint about which transactions were combined + * to create a wire transfer. + * + * @param mint mint to query + * @param wtid raw wire transfer identifier to get information about + * @param cb callback to call + * @param cb_cls closure for @a cb + * @return handle to cancel operation + */ +struct TALER_MINT_WireDepositsHandle * +TALER_MINT_wire_deposits (struct TALER_MINT_Handle *mint, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_MINT_WireDepositsCallback cb, + void *cb_cls) +{ + struct TALER_MINT_WireDepositsHandle *wdh; + struct TALER_MINT_Context *ctx; + char *buf; + char *path; + CURL *eh; + + if (GNUNET_YES != + MAH_handle_is_ready (mint)) + { + GNUNET_break (0); + return NULL; + } + + wdh = GNUNET_new (struct TALER_MINT_WireDepositsHandle); + wdh->mint = mint; + wdh->cb = cb; + wdh->cb_cls = cb_cls; + + buf = GNUNET_STRINGS_data_to_string_alloc (wtid, + sizeof (struct TALER_WireTransferIdentifierRawP)); + GNUNET_asprintf (&path, + "/wire/deposits?wtid=%s", + buf); + wdh->url = MAH_path_to_url (wdh->mint, + path); + GNUNET_free (buf); + GNUNET_free (path); + + eh = curl_easy_init (); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + wdh->url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEFUNCTION, + &MAC_download_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEDATA, + &wdh->db)); + ctx = MAH_handle_to_context (mint); + wdh->job = MAC_job_add (ctx, + eh, + GNUNET_YES, + &handle_wire_deposits_finished, + wdh); + return wdh; +} + + +/** + * Cancel wire deposits request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param wdh the wire deposits request handle + */ +void +TALER_MINT_wire_deposits_cancel (struct TALER_MINT_WireDepositsHandle *wdh) +{ + if (NULL != wdh->job) + { + MAC_job_cancel (wdh->job); + wdh->job = NULL; + } + GNUNET_free_non_null (wdh->db.buf); + GNUNET_free (wdh->url); + GNUNET_free (wdh); +} + + +/* end of mint_api_wire_deposits.c */ diff --git a/src/mint-lib/test_mint_api.c b/src/mint-lib/test_mint_api.c index 0e3b2bed8..e41d01805 100644 --- a/src/mint-lib/test_mint_api.c +++ b/src/mint-lib/test_mint_api.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. + Copyright (C) 2014, 2015, 2016 GNUnet e.V. 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 @@ -111,7 +111,17 @@ enum OpCode /** * Verify the mint's /wire-method. */ - OC_WIRE + OC_WIRE, + + /** + * Verify mint's /wire/deposits method. + */ + OC_WIRE_DEPOSITS, + + /** + * Verify mint's /deposit/wtid method. + */ + OC_DEPOSIT_WTID }; @@ -470,6 +480,60 @@ struct Command } wire; + /** + * Information for the /wire/deposits's command. + */ + struct { + + /** + * Handle to the wire deposits request. + */ + struct TALER_MINT_WireDepositsHandle *wdh; + + /** + * Reference to a /deposit/wtid command. If set, we use the + * WTID from that command. + */ + const char *wtid_ref; + + /** + * WTID to use (used if @e wtid_ref is NULL). + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /* TODO: may want to add list of deposits we expected + to see aggregated here in the future. */ + + } wire_deposits; + + /** + * Information for the /deposit/wtid command. + */ + struct { + + /** + * Handle to the deposit wtid request. + */ + struct TALER_MINT_DepositWtidHandle *dwh; + + /** + * Which /deposit operation should we obtain WTID data for? + */ + const char *deposit_ref; + + /** + * What is the expected total amount? Only used if + * @e expected_response_code was #MHD_HTTP_OK. + */ + struct TALER_Amount total_amount_expected; + + /** + * Wire transfer identifier, set if #MHD_HTTP_OK was the response code. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + } deposit_wtid; + } details; }; @@ -1220,6 +1284,158 @@ wire_cb (void *cls, /** + * Function called with detailed wire transfer data, including all + * of the coin transactions that were combined into the wire transfer. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on mint protocol violation + * @param json original json reply (may include signatures, those have then been + * validated already) + * @param wtid extracted wire transfer identifier, or NULL if the mint could + * not provide any (set only if @a http_status is #MHD_HTTP_OK) + * @param total_amount total amount of the wire transfer, or NULL if the mint could + * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + * @param details_length length of the @a details array + * @param details array with details about the combined transactions + */ +static void +wire_deposits_cb (void *cls, + unsigned int http_status, + json_t *json, + const struct GNUNET_HashCode *h_wire, + const struct TALER_Amount *total_amount, + unsigned int details_length, + const struct TALER_WireDepositDetails *details) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + const struct Command *ref; + + cmd->details.wire_deposits.wdh = NULL; + ref = find_command (is, + cmd->details.wire_deposits.wtid_ref); + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + json_dumpf (json, stderr, 0); + fail (is); + return; + } + switch (http_status) + { + case MHD_HTTP_OK: + if (0 != TALER_amount_cmp (total_amount, + &ref->details.deposit_wtid.total_amount_expected)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Total amount missmatch to command %s\n", + http_status, + cmd->label); + json_dumpf (json, stderr, 0); + fail (is); + return; + } + if (NULL != ref->details.deposit_wtid.deposit_ref) + { + const struct Command *dep; + struct GNUNET_HashCode hw; + + dep = find_command (is, + ref->details.deposit_wtid.deposit_ref); + GNUNET_CRYPTO_hash (dep->details.deposit.wire_details, + strlen (dep->details.deposit.wire_details), + &hw); + if (0 != memcmp (&hw, + h_wire, + sizeof (struct GNUNET_HashCode))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire hash missmatch to command %s\n", + cmd->label); + json_dumpf (json, stderr, 0); + fail (is); + return; + } + } + break; + default: + break; + } + + /* move to next command */ + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Function called with detailed wire transfer data. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on mint protocol violation + * @param json original json reply (may include signatures, those have then been + * validated already) + * @param wtid wire transfer identifier used by the mint, NULL if mint did not + * yet execute the transaction + * @param execution_time actual or planned execution time for the wire transfer + * @param coin_contribution contribution to the @a total_amount of the deposited coin (may be NULL) + * @param total_amount total amount of the wire transfer, or NULL if the mint could + * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + */ +static void +deposit_wtid_cb (void *cls, + unsigned int http_status, + json_t *json, + const struct TALER_WireTransferIdentifierRawP *wtid, + struct GNUNET_TIME_Absolute execution_time, + const struct TALER_Amount *coin_contribution, + const struct TALER_Amount *total_amount) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + + cmd->details.deposit_wtid.dwh = NULL; + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + json_dumpf (json, stderr, 0); + fail (is); + return; + } + switch (http_status) + { + case MHD_HTTP_OK: + cmd->details.deposit_wtid.wtid = *wtid; + if (0 != TALER_amount_cmp (total_amount, + &cmd->details.deposit_wtid.total_amount_expected)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Total amount missmatch to command %s\n", + cmd->label); + json_dumpf (json, stderr, 0); + fail (is); + return; + } + break; + default: + break; + } + + /* move to next command */ + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** * Run the main interpreter loop that performs mint operations. * * @param cls contains the `struct InterpreterState` @@ -1401,7 +1617,9 @@ interpreter_run (void *cls, struct GNUNET_TIME_Absolute refund_deadline; struct GNUNET_TIME_Absolute wire_deadline; struct GNUNET_TIME_Absolute timestamp; + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; struct TALER_MerchantPublicKeyP merchant_pub; + json_t *contract; json_t *wire; GNUNET_assert (NULL != @@ -1444,37 +1662,51 @@ interpreter_run (void *cls, fail (is); return; } - GNUNET_CRYPTO_hash (cmd->details.deposit.contract, - strlen (cmd->details.deposit.contract), - &h_contract); + contract = json_loads (cmd->details.deposit.contract, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == contract) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse contract details `%s' at %u/%s\n", + cmd->details.deposit.contract, + is->ip, + cmd->label); + fail (is); + return; + } + TALER_hash_json (contract, + &h_contract); wire = json_loads (cmd->details.deposit.wire_details, JSON_REJECT_DUPLICATES, NULL); if (NULL == wire) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to parse wire details `%s' at %u\n", + "Failed to parse wire details `%s' at %u/%s\n", cmd->details.deposit.wire_details, - is->ip); + is->ip, + cmd->label); fail (is); return; } GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, &coin_pub.eddsa_pub); + priv = GNUNET_CRYPTO_eddsa_key_create (); + cmd->details.deposit.merchant_priv.eddsa_priv = *priv; + GNUNET_free (priv); if (0 != cmd->details.deposit.refund_deadline.rel_value_us) { - struct GNUNET_CRYPTO_EddsaPrivateKey *priv; - - priv = GNUNET_CRYPTO_eddsa_key_create (); - cmd->details.deposit.merchant_priv.eddsa_priv = *priv; - GNUNET_free (priv); refund_deadline = GNUNET_TIME_relative_to_absolute (cmd->details.deposit.refund_deadline); } else { refund_deadline = GNUNET_TIME_UNIT_ZERO_ABS; } + GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.deposit.merchant_priv.eddsa_priv, + &merchant_pub.eddsa_pub); + wire_deadline = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS); timestamp = GNUNET_TIME_absolute_get (); TALER_round_abs_time (×tamp); @@ -1686,6 +1918,85 @@ interpreter_run (void *cls, is); trigger_context_task (); return; + case OC_WIRE_DEPOSITS: + if (NULL != cmd->details.wire_deposits.wtid_ref) + { + ref = find_command (is, + cmd->details.wire_deposits.wtid_ref); + GNUNET_assert (NULL != ref); + cmd->details.wire_deposits.wtid = ref->details.deposit_wtid.wtid; + } + cmd->details.wire_deposits.wdh + = TALER_MINT_wire_deposits (mint, + &cmd->details.wire_deposits.wtid, + &wire_deposits_cb, + is); + trigger_context_task (); + return; + case OC_DEPOSIT_WTID: + { + struct GNUNET_HashCode h_wire; + struct GNUNET_HashCode h_contract; + json_t *wire; + json_t *contract; + const struct Command *coin; + struct TALER_CoinSpendPublicKeyP coin_pub; + + ref = find_command (is, + cmd->details.deposit_wtid.deposit_ref); + GNUNET_assert (NULL != ref); + coin = find_command (is, + ref->details.deposit.coin_ref); + GNUNET_assert (NULL != coin); + switch (coin->oc) + { + case OC_WITHDRAW_SIGN: + GNUNET_CRYPTO_eddsa_key_get_public (&coin->details.reserve_withdraw.coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + break; + case OC_REFRESH_REVEAL: + { + const struct FreshCoin *fc; + unsigned int idx; + + idx = ref->details.deposit.coin_idx; + GNUNET_assert (idx < coin->details.refresh_reveal.num_fresh_coins); + fc = &coin->details.refresh_reveal.fresh_coins[idx]; + + GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + } + break; + default: + GNUNET_assert (0); + } + + wire = json_loads (ref->details.deposit.wire_details, + JSON_REJECT_DUPLICATES, + NULL); + GNUNET_assert (NULL != wire); + TALER_hash_json (wire, + &h_wire); + json_decref (wire); + contract = json_loads (ref->details.deposit.contract, + JSON_REJECT_DUPLICATES, + NULL); + GNUNET_assert (NULL != contract); + TALER_hash_json (contract, + &h_contract); + json_decref (contract); + cmd->details.deposit_wtid.dwh + = TALER_MINT_deposit_wtid (mint, + &ref->details.deposit.merchant_priv, + &h_wire, + &h_contract, + &coin_pub, + ref->details.deposit.transaction_id, + &deposit_wtid_cb, + is); + trigger_context_task (); + } + return; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown instruction %d at %u (%s)\n", @@ -1695,8 +2006,6 @@ interpreter_run (void *cls, fail (is); return; } - is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, - is); } @@ -1829,10 +2138,36 @@ do_shutdown (void *cls, case OC_WIRE: if (NULL != cmd->details.wire.wh) { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); TALER_MINT_wire_cancel (cmd->details.wire.wh); cmd->details.wire.wh = NULL; } break; + case OC_WIRE_DEPOSITS: + if (NULL != cmd->details.wire_deposits.wdh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MINT_wire_deposits_cancel (cmd->details.wire_deposits.wdh); + cmd->details.wire_deposits.wdh = NULL; + } + break; + case OC_DEPOSIT_WTID: + if (NULL != cmd->details.deposit_wtid.dwh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MINT_deposit_wtid_cancel (cmd->details.deposit_wtid.dwh); + cmd->details.deposit_wtid.dwh = NULL; + } + break; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown instruction %d at %u (%s)\n", @@ -2047,7 +2382,7 @@ run (void *cls, .details.deposit.amount = "EUR:5", .details.deposit.coin_ref = "withdraw-coin-1", .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }", + .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }", .details.deposit.transaction_id = 1 }, /* Try to overdraw funds ... */ @@ -2064,7 +2399,7 @@ run (void *cls, .details.deposit.amount = "EUR:5", .details.deposit.coin_ref = "withdraw-coin-1", .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":43 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }", + .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }", .details.deposit.transaction_id = 1 }, /* Try to double-spend the 5 EUR coin at the same merchant (but different transaction ID) */ @@ -2074,7 +2409,7 @@ run (void *cls, .details.deposit.amount = "EUR:5", .details.deposit.coin_ref = "withdraw-coin-1", .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }", + .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }", .details.deposit.transaction_id = 2 }, /* Try to double-spend the 5 EUR coin at the same merchant (but different contract) */ @@ -2084,7 +2419,7 @@ run (void *cls, .details.deposit.amount = "EUR:5", .details.deposit.coin_ref = "withdraw-coin-1", .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":2 } }", + .details.deposit.contract = "{ \"items\":[{ \"name\":\"ice cream\", \"value\":2 } ] }", .details.deposit.transaction_id = 1 }, /* ***************** /refresh testing ******************** */ @@ -2109,7 +2444,7 @@ run (void *cls, .details.deposit.amount = "EUR:1", .details.deposit.coin_ref = "refresh-withdraw-coin-1", .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\"EUR:1 } }", + .details.deposit.contract = "{ \"items\" : [ { \"name\":\"ice cream\", \"value\":\"EUR:1\" } ] }", .details.deposit.transaction_id = 42421 }, /* Melt the rest of the coin's value (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */ @@ -2143,7 +2478,7 @@ run (void *cls, .details.deposit.coin_ref = "refresh-reveal-1", .details.deposit.coin_idx = 0, .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":3 } }", + .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }", .details.deposit.transaction_id = 2 }, /* Test successfully spending coins from the refresh operation: @@ -2155,7 +2490,7 @@ run (void *cls, .details.deposit.coin_ref = "refresh-reveal-1", .details.deposit.coin_idx = 4, .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":3 } }", + .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }", .details.deposit.transaction_id = 2 }, /* Test running a failing melt operation (same operation again must fail) */ @@ -2168,6 +2503,35 @@ run (void *cls, // FIXME: also test with coin that was already melted // (signature differs from coin that was deposited...) /* *************** end of /refresh testing ************** */ + + /* ************** Test tracking API ******************** */ + /* Try resolving a deposit's WTID, as we never triggered + execution of transactions, the answer should be that + the mint knows about the deposit, but has no WTID yet. */ + { .oc = OC_DEPOSIT_WTID, + .label = "deposit-wtid-found", + .expected_response_code = MHD_HTTP_ACCEPTED, + .details.deposit_wtid.deposit_ref = "deposit-simple" }, + /* Try resolving a deposit's WTID for a failed deposit. + As the deposit failed, the answer should be that + the mint does NOT know about the deposit. */ + { .oc = OC_DEPOSIT_WTID, + .label = "deposit-wtid-failing", + .expected_response_code = MHD_HTTP_NOT_FOUND, + .details.deposit_wtid.deposit_ref = "deposit-double-2" }, + /* Try resolving an undefined (all zeros) WTID; this + should fail as obviously the mint didn't use that + WTID value for any transaction. */ + { .oc = OC_WIRE_DEPOSITS, + .label = "wire-deposit-failing", + .expected_response_code = MHD_HTTP_NOT_FOUND }, + + /* TODO: trigger aggregation logic and then check the + cases where tracking succeeds! */ + + /* ************** End of tracking API testing************* */ + + #endif { .oc = OC_END } |