diff options
Diffstat (limited to 'src/lib/merchant_api_reward_pickup.c')
-rw-r--r-- | src/lib/merchant_api_reward_pickup.c | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/src/lib/merchant_api_reward_pickup.c b/src/lib/merchant_api_reward_pickup.c new file mode 100644 index 00000000..1d884d48 --- /dev/null +++ b/src/lib/merchant_api_reward_pickup.c @@ -0,0 +1,440 @@ +/* + This file is part of TALER + Copyright (C) 2014-2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING.LGPL. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file merchant_api_reward_pickup.c + * @brief Implementation of the /reward-pickup request of the merchant's HTTP API + * @author Marcello Stanisci + * @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 <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> +#include <taler/taler_curl_lib.h> + + +/** + * Data we keep per planchet. + */ +struct PlanchetData +{ + /** + * Secrets of the planchet. + */ + struct TALER_PlanchetMasterSecretP ps; + + /** + * Denomination key we are withdrawing. + */ + struct TALER_EXCHANGE_DenomPublicKey pk; + + /** + * Hash of the public key of the coin we are signing. + */ + struct TALER_CoinPubHashP c_hash; + + /** + * Nonce used for @e csr request, if any. + */ + struct TALER_CsNonce nonce; + + /** + * Handle for a /csr request we may optionally need + * to trigger. + */ + struct TALER_EXCHANGE_CsRWithdrawHandle *csr; + + /** + * Handle for the /reward-pickup operation we are part of. + */ + struct TALER_MERCHANT_RewardPickupHandle *tp; + + /** + * Offset of this entry in the array. + */ + unsigned int off; +}; + + +/** + * Handle for a /reward-pickup operation. + */ +struct TALER_MERCHANT_RewardPickupHandle +{ + + /** + * Function to call with the result. + */ + TALER_MERCHANT_RewardPickupCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Handle for the actual (internal) withdraw operation. + */ + struct TALER_MERCHANT_RewardPickup2Handle *tpo2; + + /** + * Array of length @e num_planchets. + */ + struct PlanchetData *planchets; + + /** + * Array of length @e num_planchets. + */ + struct TALER_EXCHANGE_PrivateCoinDetails *pcds; + + /** + * Context for making HTTP requests. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * URL of the merchant backend. + */ + char *backend_url; + + /** + * ID of the reward we are picking up. + */ + struct TALER_RewardIdentifierP reward_id; + + /** + * Number of planchets/coins used for this operation. + */ + unsigned int num_planchets; + + /** + * Number of remaining active /csr-withdraw requests. + */ + unsigned int csr_active; +}; + + +/** + * Fail the pickup operation @a tp, returning @a ec. + * Also cancels @a tp. + * + * @param[in] tp operation to fail + * @param ec reason for the failure + */ +static void +fail_pickup (struct TALER_MERCHANT_RewardPickupHandle *tp, + enum TALER_ErrorCode ec) +{ + struct TALER_MERCHANT_PickupDetails pd = { + .hr.ec = ec + }; + + tp->cb (tp->cb_cls, + &pd); + TALER_MERCHANT_reward_pickup_cancel (tp); +} + + +/** + * Callback for a /reward-pickup request. Returns the result of the operation. + * Note that the client MUST still do the unblinding of the @a blind_sigs. + * + * @param cls closure, a `struct TALER_MERCHANT_RewardPickupHandle *` + * @param tpr response details + */ +static void +pickup_done_cb (void *cls, + const struct TALER_MERCHANT_RewardPickup2Response *tpr) +{ + struct TALER_MERCHANT_RewardPickupHandle *tp = cls; + struct TALER_MERCHANT_PickupDetails pd = { + .hr = tpr->hr + }; + + tp->tpo2 = NULL; + if (MHD_HTTP_OK != tpr->hr.http_status) + { + tp->cb (tp->cb_cls, + &pd); + TALER_MERCHANT_reward_pickup_cancel (tp); + return; + } + { + enum GNUNET_GenericReturnValue ok = GNUNET_OK; + + for (unsigned int i = 0; i<tpr->details.ok.num_blind_sigs; i++) + { + const struct TALER_BlindedDenominationSignature *blind_sig + = &tpr->details.ok.blind_sigs[i]; + struct TALER_EXCHANGE_PrivateCoinDetails *pcd + = &tp->pcds[i]; + struct TALER_FreshCoin fc; + + if (GNUNET_OK != + TALER_planchet_to_coin (&tp->planchets[i].pk.key, + blind_sig, + &pcd->bks, + &pcd->coin_priv, + NULL, + &tp->planchets[i].c_hash, + &pcd->exchange_vals, + &fc)) + { + ok = GNUNET_SYSERR; + break; + } + pcd->sig = fc.sig; + } + if (GNUNET_OK != ok) + { + pd.hr.ec = TALER_EC_MERCHANT_REWARD_PICKUP_UNBLIND_FAILURE; + } + else + { + pd.details.ok.num_sigs = tpr->details.ok.num_blind_sigs; + pd.details.ok.pcds = tp->pcds; + } + tp->cb (tp->cb_cls, + &pd); + } + TALER_MERCHANT_reward_pickup_cancel (tp); +} + + +/** + * We have obtained all of the exchange inputs. Continue the pickup. + * + * @param[in,out] tp operation to continue + */ +static void +pickup_post_csr (struct TALER_MERCHANT_RewardPickupHandle *tp) +{ + struct TALER_PlanchetDetail details[tp->num_planchets]; + + for (unsigned int i = 0; i<tp->num_planchets; i++) + { + const struct PlanchetData *pd = &tp->planchets[i]; + struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i]; + + TALER_planchet_setup_coin_priv (&pd->ps, + &pcd->exchange_vals, + &pcd->coin_priv); + TALER_planchet_blinding_secret_create (&pd->ps, + &pcd->exchange_vals, + &pcd->bks); + if (TALER_DENOMINATION_CS == pcd->exchange_vals.cipher) + { + details[i].blinded_planchet.details.cs_blinded_planchet.nonce + = pd->nonce; + } + if (GNUNET_OK != + TALER_planchet_prepare (&pd->pk.key, + &pcd->exchange_vals, + &pcd->bks, + &pcd->coin_priv, + NULL, + &tp->planchets[i].c_hash, + &details[i])) + { + GNUNET_break (0); + for (unsigned int j = 0; j<i; j++) + TALER_planchet_detail_free (&details[j]); + fail_pickup (tp, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE); + return; + } + } + tp->tpo2 = TALER_MERCHANT_reward_pickup2 (tp->ctx, + tp->backend_url, + &tp->reward_id, + tp->num_planchets, + details, + &pickup_done_cb, + tp); + for (unsigned int j = 0; j<tp->num_planchets; j++) + TALER_planchet_detail_free (&details[j]); + if (NULL == tp->tpo2) + { + GNUNET_break (0); + fail_pickup (tp, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE); + return; + } +} + + +/** + * Callbacks of this type are used to serve the result of submitting a + * CS R request to a exchange. + * + * @param cls a `struct TALER_MERCHANT_RewardPickupHandle` + * @param csrr response details + */ +static void +csr_cb (void *cls, + const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr) +{ + struct PlanchetData *pd = cls; + struct TALER_MERCHANT_RewardPickupHandle *tp = pd->tp; + + pd->csr = NULL; + tp->csr_active--; + switch (csrr->hr.http_status) + { + case MHD_HTTP_OK: + { + struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[pd->off]; + + pcd->exchange_vals = csrr->details.ok.alg_values; + } + if (0 != tp->csr_active) + return; + pickup_post_csr (tp); + return; + default: + { + struct TALER_MERCHANT_PickupDetails pd = { + .hr.hint = "/csr-withdraw failed", + .hr.exchange_http_status = csrr->hr.http_status + }; + + tp->cb (tp->cb_cls, + &pd); + TALER_MERCHANT_reward_pickup_cancel (tp); + return; + } + } +} + + +struct TALER_MERCHANT_RewardPickupHandle * +TALER_MERCHANT_reward_pickup ( + struct GNUNET_CURL_Context *ctx, + const char *exchange_url, + const char *backend_url, + const struct TALER_RewardIdentifierP *reward_id, + unsigned int num_planchets, + const struct TALER_MERCHANT_PlanchetData pds[static num_planchets], + TALER_MERCHANT_RewardPickupCallback pickup_cb, + void *pickup_cb_cls) +{ + struct TALER_MERCHANT_RewardPickupHandle *tp; + + if (0 == num_planchets) + { + GNUNET_break (0); + return NULL; + } + tp = GNUNET_new (struct TALER_MERCHANT_RewardPickupHandle); + tp->cb = pickup_cb; + tp->cb_cls = pickup_cb_cls; + tp->ctx = ctx; + tp->backend_url = GNUNET_strdup (backend_url); + tp->reward_id = *reward_id; + tp->num_planchets = num_planchets; + tp->planchets = GNUNET_new_array (num_planchets, + struct PlanchetData); + tp->pcds = GNUNET_new_array (num_planchets, + struct TALER_EXCHANGE_PrivateCoinDetails); + for (unsigned int i = 0; i<num_planchets; i++) + { + const struct TALER_MERCHANT_PlanchetData *mpd = &pds[i]; + const struct TALER_EXCHANGE_DenomPublicKey *pk = mpd->pk; + struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i]; + struct PlanchetData *pd = &tp->planchets[i]; + + pd->off = i; + pd->tp = tp; + tp->planchets[i].ps = mpd->ps; + tp->planchets[i].pk = *pds[i].pk; + TALER_denom_pub_deep_copy (&tp->planchets[i].pk.key, + &pds[i].pk->key); + switch (pk->key.cipher) + { + case TALER_DENOMINATION_RSA: + pcd->exchange_vals.cipher = TALER_DENOMINATION_RSA; + break; + case TALER_DENOMINATION_CS: + { + TALER_cs_withdraw_nonce_derive (&pd->ps, + &pd->nonce); + pd->csr = TALER_EXCHANGE_csr_withdraw (ctx, + exchange_url, + &pd->pk, + &pd->nonce, + &csr_cb, + pd); + if (NULL == pd->csr) + { + GNUNET_break (0); + TALER_MERCHANT_reward_pickup_cancel (tp); + return NULL; + } + tp->csr_active++; + break; + } + default: + GNUNET_break (0); + TALER_MERCHANT_reward_pickup_cancel (tp); + return NULL; + } + } + if (0 == tp->csr_active) + { + pickup_post_csr (tp); + return tp; + } + return tp; +} + + +void +TALER_MERCHANT_reward_pickup_cancel (struct TALER_MERCHANT_RewardPickupHandle *tp) +{ + for (unsigned int i = 0; i<tp->num_planchets; i++) + { + struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i]; + struct PlanchetData *pd = &tp->planchets[i]; + + TALER_denom_sig_free (&pcd->sig); + TALER_denom_pub_free (&tp->planchets[i].pk.key); + if (NULL != pd->csr) + { + TALER_EXCHANGE_csr_withdraw_cancel (pd->csr); + pd->csr = NULL; + } + } + GNUNET_array_grow (tp->planchets, + tp->num_planchets, + 0); + if (NULL != tp->tpo2) + { + TALER_MERCHANT_reward_pickup2_cancel (tp->tpo2); + tp->tpo2 = NULL; + } + GNUNET_free (tp->backend_url); + GNUNET_free (tp->pcds); + GNUNET_free (tp); +} + + +/* end of merchant_api_reward_pickup.c */ |