/*
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
*/
/**
* @file merchant_api_tip_pickup.c
* @brief Implementation of the /tip-pickup request of the merchant's HTTP API
* @author Marcello Stanisci
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include /* just for HTTP status codes */
#include
#include
#include "taler_merchant_service.h"
#include
#include
#include
/**
* 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 /tip-pickup operation we are part of.
*/
struct TALER_MERCHANT_TipPickupHandle *tp;
/**
* Offset of this entry in the array.
*/
unsigned int off;
};
/**
* Handle for a /tip-pickup operation.
*/
struct TALER_MERCHANT_TipPickupHandle
{
/**
* Function to call with the result.
*/
TALER_MERCHANT_TipPickupCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
/**
* Handle for the actual (internal) withdraw operation.
*/
struct TALER_MERCHANT_TipPickup2Handle *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 tip we are picking up.
*/
struct TALER_TipIdentifierP tip_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_TipPickupHandle *tp,
enum TALER_ErrorCode ec)
{
struct TALER_MERCHANT_PickupDetails pd = {
.hr.ec = ec
};
tp->cb (tp->cb_cls,
&pd);
TALER_MERCHANT_tip_pickup_cancel (tp);
}
/**
* Callback for a /tip-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_TipPickupHandle *`
* @param hr HTTP response details
* @param num_blind_sigs length of the @a reserve_sigs array, 0 on error
* @param blind_sigs array of blind signatures over the planchets, NULL on error
*/
static void
pickup_done_cb (void *cls,
const struct TALER_MERCHANT_HttpResponse *hr,
unsigned int num_blind_sigs,
const struct TALER_BlindedDenominationSignature *blind_sigs)
{
struct TALER_MERCHANT_TipPickupHandle *tp = cls;
struct TALER_MERCHANT_PickupDetails pd = {
.hr = *hr
};
tp->tpo2 = NULL;
if (NULL == blind_sigs)
{
tp->cb (tp->cb_cls,
&pd);
TALER_MERCHANT_tip_pickup_cancel (tp);
return;
}
{
enum GNUNET_GenericReturnValue ok = GNUNET_OK;
for (unsigned int i = 0; ipcds[i];
struct TALER_FreshCoin fc;
if (GNUNET_OK !=
TALER_planchet_to_coin (&tp->planchets[i].pk.key,
&blind_sigs[i],
&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)
{
struct TALER_MERCHANT_HttpResponse hrx = {
.reply = hr->reply,
.ec = TALER_EC_MERCHANT_TIP_PICKUP_UNBLIND_FAILURE
};
pd.hr = hrx;
tp->cb (tp->cb_cls,
&pd);
}
else
{
pd.details.success.num_sigs = num_blind_sigs;
pd.details.success.pcds = tp->pcds;
tp->cb (tp->cb_cls,
&pd);
}
}
TALER_MERCHANT_tip_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_TipPickupHandle *tp)
{
struct TALER_PlanchetDetail details[tp->num_planchets];
for (unsigned int i = 0; inum_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; jtpo2 = TALER_MERCHANT_tip_pickup2 (tp->ctx,
tp->backend_url,
&tp->tip_id,
tp->num_planchets,
details,
&pickup_done_cb,
tp);
for (unsigned int j = 0; jnum_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_TipPickupHandle`
* @param csrr response details
*/
static void
csr_cb (void *cls,
const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
{
struct PlanchetData *pd = cls;
struct TALER_MERCHANT_TipPickupHandle *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.success.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_tip_pickup_cancel (tp);
return;
}
}
}
struct TALER_MERCHANT_TipPickupHandle *
TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
struct TALER_EXCHANGE_Handle *exchange,
const char *backend_url,
const struct TALER_TipIdentifierP *tip_id,
unsigned int num_planchets,
const struct TALER_MERCHANT_PlanchetData *pds,
TALER_MERCHANT_TipPickupCallback pickup_cb,
void *pickup_cb_cls)
{
struct TALER_MERCHANT_TipPickupHandle *tp;
if (0 == num_planchets)
{
GNUNET_break (0);
return NULL;
}
tp = GNUNET_new (struct TALER_MERCHANT_TipPickupHandle);
tp->cb = pickup_cb;
tp->cb_cls = pickup_cb_cls;
tp->ctx = ctx;
tp->backend_url = GNUNET_strdup (backend_url);
tp->tip_id = *tip_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; ipk;
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 (exchange,
&pd->pk,
&pd->nonce,
&csr_cb,
pd);
if (NULL == pd->csr)
{
GNUNET_break (0);
TALER_MERCHANT_tip_pickup_cancel (tp);
return NULL;
}
tp->csr_active++;
break;
}
default:
GNUNET_break (0);
TALER_MERCHANT_tip_pickup_cancel (tp);
return NULL;
}
}
if (0 == tp->csr_active)
{
pickup_post_csr (tp);
return tp;
}
return tp;
}
void
TALER_MERCHANT_tip_pickup_cancel (struct TALER_MERCHANT_TipPickupHandle *tp)
{
for (unsigned int i = 0; inum_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_tip_pickup2_cancel (tp->tpo2);
tp->tpo2 = NULL;
}
GNUNET_free (tp->backend_url);
GNUNET_free (tp->pcds);
GNUNET_free (tp);
}
/* end of merchant_api_tip_pickup.c */