summaryrefslogtreecommitdiff
path: root/src/lib/merchant_api_reward_pickup.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/merchant_api_reward_pickup.c')
-rw-r--r--src/lib/merchant_api_reward_pickup.c440
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 */