/*
This file is part of TALER
(C) 2014-2021 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero 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
*/
/**
* @file taler-merchant-httpd_post-orders-ID-abort.c
* @brief handling of POST /orders/$ID/abort requests
* @author Marcello Stanisci
* @author Christian Grothoff
* @author Florian Dold
*/
#include "platform.h"
#include
#include
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_post-orders-ID-abort.h"
/**
* How long to wait before giving up processing with the exchange?
*/
#define ABORT_GENERIC_TIMEOUT (GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_SECONDS, \
30))
/**
* How often do we retry the (complex!) database transaction?
*/
#define MAX_RETRIES 5
/**
* Information we keep for an individual call to the /abort handler.
*/
struct AbortContext;
/**
* Information kept during a /abort request for each coin.
*/
struct RefundDetails
{
/**
* Public key of the coin.
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
* Signature from the exchange confirming the refund.
* Set if we were successful (status 200).
*/
struct TALER_ExchangeSignatureP exchange_sig;
/**
* Public key used for @e exchange_sig.
* Set if we were successful (status 200).
*/
struct TALER_ExchangePublicKeyP exchange_pub;
/**
* Reference to the main AbortContext
*/
struct AbortContext *ac;
/**
* Handle to the refund operation we are performing for
* this coin, NULL after the operation is done.
*/
struct TALER_EXCHANGE_RefundHandle *rh;
/**
* URL of the exchange that issued this coin.
*/
char *exchange_url;
/**
* Body of the response from the exchange. Note that the body returned MUST
* be freed (if non-NULL).
*/
json_t *exchange_reply;
/**
* Amount this coin contributes to the total purchase price.
* This amount includes the deposit fee.
*/
struct TALER_Amount amount_with_fee;
/**
* Offset of this coin into the `rd` array of all coins in the
* @e ac.
*/
unsigned int index;
/**
* HTTP status returned by the exchange (if any).
*/
unsigned int http_status;
/**
* Did we try to process this refund yet?
*/
bool processed;
};
/**
* Information we keep for an individual call to the /abort handler.
*/
struct AbortContext
{
/**
* Hashed contract terms (according to client).
*/
struct TALER_PrivateContractHashP h_contract_terms;
/**
* Context for our operation.
*/
struct TMH_HandlerContext *hc;
/**
* Stored in a DLL.
*/
struct AbortContext *next;
/**
* Stored in a DLL.
*/
struct AbortContext *prev;
/**
* Array with @e coins_cnt coins we are despositing.
*/
struct RefundDetails *rd;
/**
* MHD connection to return to
*/
struct MHD_Connection *connection;
/**
* Task called when the (suspended) processing for
* the /abort request times out.
* Happens when we don't get a response from the exchange.
*/
struct GNUNET_SCHEDULER_Task *timeout_task;
/**
* Response to return, NULL if we don't have one yet.
*/
struct MHD_Response *response;
/**
* Handle to the exchange that we are doing the abortment with.
* (initially NULL while @e fo is trying to find a exchange).
*/
struct TALER_EXCHANGE_Handle *mh;
/**
* Handle for operation to lookup /keys (and auditors) from
* the exchange used for this transaction; NULL if no operation is
* pending.
*/
struct TMH_EXCHANGES_KeysOperation *fo;
/**
* URL of the exchange used for the last @e fo.
*/
const char *current_exchange;
/**
* Number of coins this abort is for. Length of the @e rd array.
*/
size_t coins_cnt;
/**
* How often have we retried the 'main' transaction?
*/
unsigned int retry_counter;
/**
* Number of transactions still pending. Initially set to
* @e coins_cnt, decremented on each transaction that
* successfully finished.
*/
size_t pending;
/**
* Number of transactions still pending for the currently selected
* exchange. Initially set to the number of coins started at the
* exchange, decremented on each transaction that successfully
* finished. Once it hits zero, we pick the next exchange.
*/
size_t pending_at_ce;
/**
* HTTP status code to use for the reply, i.e 200 for "OK".
* Special value UINT_MAX is used to indicate hard errors
* (no reply, return #MHD_NO).
*/
unsigned int response_code;
/**
* #GNUNET_NO if the @e connection was not suspended,
* #GNUNET_YES if the @e connection was suspended,
* #GNUNET_SYSERR if @e connection was resumed to as
* part of #MH_force_ac_resume during shutdown.
*/
int suspended;
};
/**
* Head of active abort context DLL.
*/
static struct AbortContext *ac_head;
/**
* Tail of active abort context DLL.
*/
static struct AbortContext *ac_tail;
/**
* Abort all pending /deposit operations.
*
* @param ac abort context to abort
*/
static void
abort_refunds (struct AbortContext *ac)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Aborting pending /deposit operations\n");
for (size_t i = 0; icoins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
if (NULL != rdi->rh)
{
TALER_EXCHANGE_refund_cancel (rdi->rh);
rdi->rh = NULL;
}
}
}
void
TMH_force_ac_resume ()
{
for (struct AbortContext *ac = ac_head;
NULL != ac;
ac = ac->next)
{
abort_refunds (ac);
if (NULL != ac->timeout_task)
{
GNUNET_SCHEDULER_cancel (ac->timeout_task);
ac->timeout_task = NULL;
}
if (GNUNET_YES == ac->suspended)
{
ac->suspended = GNUNET_SYSERR;
MHD_resume_connection (ac->connection);
}
}
}
/**
* Resume the given abort context and send the given response.
* Stores the response in the @a ac and signals MHD to resume
* the connection. Also ensures MHD runs immediately.
*
* @param ac abortment context
* @param response_code response code to use
* @param response response data to send back
*/
static void
resume_abort_with_response (struct AbortContext *ac,
unsigned int response_code,
struct MHD_Response *response)
{
abort_refunds (ac);
ac->response_code = response_code;
ac->response = response;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming /abort handling as exchange interaction is done (%u)\n",
response_code);
if (NULL != ac->timeout_task)
{
GNUNET_SCHEDULER_cancel (ac->timeout_task);
ac->timeout_task = NULL;
}
GNUNET_assert (GNUNET_YES == ac->suspended);
ac->suspended = GNUNET_NO;
MHD_resume_connection (ac->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
/**
* Resume abortment processing with an error.
*
* @param ac operation to resume
* @param http_status http status code to return
* @param ec taler error code to return
* @param msg human readable error message
*/
static void
resume_abort_with_error (struct AbortContext *ac,
unsigned int http_status,
enum TALER_ErrorCode ec,
const char *msg)
{
resume_abort_with_response (ac,
http_status,
TALER_MHD_make_error (ec,
msg));
}
/**
* Generate a response that indicates abortment success.
*
* @param ac abortment context
*/
static void
generate_success_response (struct AbortContext *ac)
{
json_t *refunds;
unsigned int hc = MHD_HTTP_OK;
refunds = json_array ();
if (NULL == refunds)
{
GNUNET_break (0);
resume_abort_with_error (ac,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
"could not create JSON array");
return;
}
for (size_t i = 0; icoins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
json_t *detail;
if ( ( (MHD_HTTP_BAD_REQUEST <= rdi->http_status) &&
(MHD_HTTP_NOT_FOUND != rdi->http_status) &&
(MHD_HTTP_GONE != rdi->http_status) ) ||
(0 == rdi->http_status) ||
(NULL == rdi->exchange_reply) )
hc = MHD_HTTP_BAD_GATEWAY;
if (MHD_HTTP_OK != rdi->http_status)
detail = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("type",
"failure"),
GNUNET_JSON_pack_uint64 ("exchange_status",
rdi->http_status),
GNUNET_JSON_pack_uint64 ("exchange_code",
(NULL != rdi->exchange_reply)
?
TALER_JSON_get_error_code (
rdi->exchange_reply)
:
TALER_EC_GENERIC_INVALID_RESPONSE),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("exchange_reply",
rdi->exchange_reply)));
else
detail = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("type",
"success"),
GNUNET_JSON_pack_uint64 ("exchange_status",
rdi->http_status),
GNUNET_JSON_pack_data_auto ("exchange_sig",
&rdi->exchange_sig),
GNUNET_JSON_pack_data_auto ("exchange_pub",
&rdi->exchange_pub));
GNUNET_assert (0 ==
json_array_append_new (refunds,
detail));
}
/* Resume and send back the response. */
resume_abort_with_response (
ac,
hc,
TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_array_steal ("refunds",
refunds)));
}
/**
* Custom cleanup routine for a `struct AbortContext`.
*
* @param cls the `struct AbortContext` to clean up.
*/
static void
abort_context_cleanup (void *cls)
{
struct AbortContext *ac = cls;
if (NULL != ac->timeout_task)
{
GNUNET_SCHEDULER_cancel (ac->timeout_task);
ac->timeout_task = NULL;
}
abort_refunds (ac);
for (size_t i = 0; icoins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
if (NULL != rdi->exchange_reply)
{
json_decref (rdi->exchange_reply);
rdi->exchange_reply = NULL;
}
GNUNET_free (rdi->exchange_url);
}
GNUNET_free (ac->rd);
if (NULL != ac->fo)
{
TMH_EXCHANGES_keys4exchange_cancel (ac->fo);
ac->fo = NULL;
}
if (NULL != ac->response)
{
MHD_destroy_response (ac->response);
ac->response = NULL;
}
GNUNET_CONTAINER_DLL_remove (ac_head,
ac_tail,
ac);
GNUNET_free (ac);
}
/**
* Find the exchange we need to talk to for the next
* pending deposit permission.
*
* @param ac abortment context we are processing
*/
static void
find_next_exchange (struct AbortContext *ac);
/**
* Function called with the result from the exchange (to be
* passed back to the wallet).
*
* @param cls closure
* @param rr response data
*/
static void
refund_cb (void *cls,
const struct TALER_EXCHANGE_RefundResponse *rr)
{
struct RefundDetails *rd = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
struct AbortContext *ac = rd->ac;
rd->rh = NULL;
rd->http_status = hr->http_status;
rd->exchange_reply = json_incref ((json_t*) hr->reply);
if (MHD_HTTP_OK == hr->http_status)
{
rd->exchange_pub = rr->details.ok.exchange_pub;
rd->exchange_sig = rr->details.ok.exchange_sig;
}
ac->pending_at_ce--;
if (0 == ac->pending_at_ce)
find_next_exchange (ac);
}
/**
* Function called with the result of our exchange lookup.
*
* @param cls the `struct AbortContext`
* @param keys keys of the exchange
* @param exchange representation of the exchange
*/
static void
process_abort_with_exchange (void *cls,
struct TALER_EXCHANGE_Keys *keys,
struct TMH_Exchange *exchange)
{
struct AbortContext *ac = cls;
(void) exchange;
ac->fo = NULL;
GNUNET_assert (GNUNET_YES == ac->suspended);
if (NULL == keys)
{
resume_abort_with_response (
ac,
MHD_HTTP_GATEWAY_TIMEOUT,
TALER_MHD_make_error (
TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
NULL));
return;
}
/* Initiate refund operation for all coins of
the current exchange (!) */
GNUNET_assert (0 == ac->pending_at_ce);
for (size_t i = 0; icoins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
if (rdi->processed)
continue;
GNUNET_assert (NULL == rdi->rh);
if (0 != strcmp (rdi->exchange_url,
ac->current_exchange))
continue;
rdi->processed = true;
ac->pending--;
rdi->rh = TALER_EXCHANGE_refund (
TMH_curl_ctx,
ac->current_exchange,
keys,
&rdi->amount_with_fee,
&ac->h_contract_terms,
&rdi->coin_pub,
0, /* rtransaction_id */
&ac->hc->instance->merchant_priv,
&refund_cb,
rdi);
if (NULL == rdi->rh)
{
GNUNET_break_op (0);
resume_abort_with_error (ac,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_EXCHANGE_REFUND_FAILED,
"Failed to start refund with exchange");
return;
}
ac->pending_at_ce++;
}
}
/**
* Begin of the DB transaction. If required (from
* soft/serialization errors), the transaction can be
* restarted here.
*
* @param ac abortment context to transact
*/
static void
begin_transaction (struct AbortContext *ac);
/**
* Find the exchange we need to talk to for the next
* pending deposit permission.
*
* @param ac abortment context we are processing
*/
static void
find_next_exchange (struct AbortContext *ac)
{
for (size_t i = 0; icoins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
if (! rdi->processed)
{
ac->current_exchange = rdi->exchange_url;
ac->fo = TMH_EXCHANGES_keys4exchange (ac->current_exchange,
false,
&process_abort_with_exchange,
ac);
if (NULL == ac->fo)
{
/* strange, should have happened on pay! */
GNUNET_break (0);
resume_abort_with_error (ac,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED,
ac->current_exchange);
return;
}
return;
}
}
ac->current_exchange = NULL;
GNUNET_assert (0 == ac->pending);
/* We are done with all the HTTP requests, go back and try
the 'big' database transaction! (It should work now!) */
begin_transaction (ac);
}
/**
* Function called with information about a coin that was deposited.
*
* @param cls closure
* @param exchange_url exchange where @a coin_pub was deposited
* @param coin_pub public key of the coin
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param refund_fee fee the exchange will charge for refunding this coin
*/
static void
refund_coins (void *cls,
const char *exchange_url,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
const struct TALER_Amount *refund_fee)
{
struct AbortContext *ac = cls;
struct GNUNET_TIME_Timestamp now;
(void) amount_with_fee;
(void) deposit_fee;
(void) refund_fee;
now = GNUNET_TIME_timestamp_get ();
for (size_t i = 0; icoins_cnt; i++)
{
struct RefundDetails *rdi = &ac->rd[i];
enum GNUNET_DB_QueryStatus qs;
if ( (0 !=
GNUNET_memcmp (coin_pub,
&rdi->coin_pub)) ||
(0 !=
strcmp (exchange_url,
rdi->exchange_url)) )
continue; /* not in request */
/* Store refund in DB */
qs = TMH_db->refund_coin (TMH_db->cls,
ac->hc->instance->settings.id,
&ac->h_contract_terms,
now,
coin_pub,
/* justification */
"incomplete abortment aborted");
if (0 > qs)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
begin_transaction (ac);
return;
}
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
resume_abort_with_error (ac,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"refund_coin");
return;
}
} /* for all coins */
}
/**
* Begin of the DB transaction. If required (from soft/serialization errors),
* the transaction can be restarted here.
*
* @param ac abortment context to transact
*/
static void
begin_transaction (struct AbortContext *ac)
{
enum GNUNET_DB_QueryStatus qs;
/* Avoid re-trying transactions on soft errors forever! */
if (ac->retry_counter++ > MAX_RETRIES)
{
GNUNET_break (0);
resume_abort_with_error (ac,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
NULL);
return;
}
GNUNET_assert (GNUNET_YES == ac->suspended);
/* First, try to see if we have all we need already done */
TMH_db->preflight (TMH_db->cls);
if (GNUNET_OK !=
TMH_db->start (TMH_db->cls,
"run abort"))
{
GNUNET_break (0);
resume_abort_with_error (ac,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_START_FAILED,
NULL);
return;
}
/* check payment was indeed incomplete
(now that we are in the transaction scope!) */
{
struct TALER_PrivateContractHashP h_contract_terms;
bool paid;
qs = TMH_db->lookup_order_status (TMH_db->cls,
ac->hc->instance->settings.id,
ac->hc->infix,
&h_contract_terms,
&paid);
switch (qs)
{
case GNUNET_DB_STATUS_SOFT_ERROR:
case GNUNET_DB_STATUS_HARD_ERROR:
/* Always report on hard error to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
begin_transaction (ac);
return;
}
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
resume_abort_with_error (ac,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"order status");
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
TMH_db->rollback (TMH_db->cls);
resume_abort_with_error (ac,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND,
"Could not find contract");
return;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
if (paid)
{
/* Payment is complete, refuse to abort. */
TMH_db->rollback (TMH_db->cls);
resume_abort_with_error (ac,
MHD_HTTP_PRECONDITION_FAILED,
TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE,
"Payment was complete, refusing to abort");
return;
}
}
if (0 !=
GNUNET_memcmp (&ac->h_contract_terms,
&h_contract_terms))
{
GNUNET_break_op (0);
resume_abort_with_error (ac,
MHD_HTTP_FORBIDDEN,
TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_HASH_MISSMATCH,
"Provided hash does not match order on file");
return;
}
}
/* Mark all deposits we have in our database for the order as refunded. */
qs = TMH_db->lookup_deposits (TMH_db->cls,
ac->hc->instance->settings.id,
&ac->h_contract_terms,
&refund_coins,
ac);
if (0 > qs)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
begin_transaction (ac);
return;
}
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
resume_abort_with_error (ac,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"deposits");
return;
}
qs = TMH_db->commit (TMH_db->cls);
if (0 > qs)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
begin_transaction (ac);
return;
}
resume_abort_with_error (ac,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
NULL);
return;
}
/* At this point, the refund got correctly committed
into the database. Tell exchange about abort/refund. */
if (ac->pending > 0)
{
find_next_exchange (ac);
return;
}
generate_success_response (ac);
}
/**
* Try to parse the abort request into the given abort context.
* Schedules an error response in the connection on failure.
*
* @param connection HTTP connection we are receiving abortment on
* @param hc context we use to handle the abortment
* @param ac state of the /abort call
* @return #GNUNET_OK on success,
* #GNUNET_NO on failure (response was queued with MHD)
* #GNUNET_SYSERR on hard error (MHD connection must be dropped)
*/
static enum GNUNET_GenericReturnValue
parse_abort (struct MHD_Connection *connection,
struct TMH_HandlerContext *hc,
struct AbortContext *ac)
{
const json_t *coins;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_array_const ("coins",
&coins),
GNUNET_JSON_spec_fixed_auto ("h_contract",
&ac->h_contract_terms),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (connection,
hc->request_body,
spec);
if (GNUNET_YES != res)
{
GNUNET_break_op (0);
return res;
}
ac->coins_cnt = json_array_size (coins);
if (0 == ac->coins_cnt)
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_COINS_ARRAY_EMPTY,
"coins");
}
/* note: 1 coin = 1 deposit confirmation expected */
ac->pending = ac->coins_cnt;
ac->rd = GNUNET_new_array (ac->coins_cnt,
struct RefundDetails);
/* This loop populates the array 'rd' in 'ac' */
{
unsigned int coins_index;
json_t *coin;
json_array_foreach (coins, coins_index, coin)
{
struct RefundDetails *rd = &ac->rd[coins_index];
const char *exchange_url;
struct GNUNET_JSON_Specification ispec[] = {
TALER_JSON_spec_amount ("contribution",
TMH_currency,
&rd->amount_with_fee),
TALER_JSON_spec_web_url ("exchange_url",
&exchange_url),
GNUNET_JSON_spec_fixed_auto ("coin_pub",
&rd->coin_pub),
GNUNET_JSON_spec_end ()
};
res = TALER_MHD_parse_json_data (connection,
coin,
ispec);
if (GNUNET_YES != res)
{
GNUNET_break_op (0);
return res;
}
rd->exchange_url = GNUNET_strdup (exchange_url);
rd->index = coins_index;
rd->ac = ac;
}
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Handling /abort for order `%s' with contract hash `%s'\n",
ac->hc->infix,
GNUNET_h2s (&ac->h_contract_terms.hash));
return GNUNET_OK;
}
/**
* Handle a timeout for the processing of the abort request.
*
* @param cls our `struct AbortContext`
*/
static void
handle_abort_timeout (void *cls)
{
struct AbortContext *ac = cls;
ac->timeout_task = NULL;
GNUNET_assert (GNUNET_YES == ac->suspended);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming abort with error after timeout\n");
if (NULL != ac->fo)
{
TMH_EXCHANGES_keys4exchange_cancel (ac->fo);
ac->fo = NULL;
}
resume_abort_with_error (ac,
MHD_HTTP_GATEWAY_TIMEOUT,
TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
NULL);
}
MHD_RESULT
TMH_post_orders_ID_abort (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
struct AbortContext *ac = hc->ctx;
if (NULL == ac)
{
ac = GNUNET_new (struct AbortContext);
GNUNET_CONTAINER_DLL_insert (ac_head,
ac_tail,
ac);
ac->connection = connection;
ac->hc = hc;
hc->ctx = ac;
hc->cc = &abort_context_cleanup;
}
if (GNUNET_SYSERR == ac->suspended)
return MHD_NO; /* during shutdown, we don't generate any more replies */
if (0 != ac->response_code)
{
MHD_RESULT res;
/* We are *done* processing the request,
just queue the response (!) */
if (UINT_MAX == ac->response_code)
{
GNUNET_break (0);
return MHD_NO; /* hard error */
}
res = MHD_queue_response (connection,
ac->response_code,
ac->response);
MHD_destroy_response (ac->response);
ac->response = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Queueing response (%u) for /abort (%s).\n",
(unsigned int) ac->response_code,
res ? "OK" : "FAILED");
return res;
}
{
enum GNUNET_GenericReturnValue ret;
ret = parse_abort (connection,
hc,
ac);
if (GNUNET_OK != ret)
return (GNUNET_NO == ret)
? MHD_YES
: MHD_NO;
}
/* Abort not finished, suspend while we interact with the exchange */
GNUNET_assert (GNUNET_NO == ac->suspended);
MHD_suspend_connection (connection);
ac->suspended = GNUNET_YES;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Suspending abort handling while working with the exchange\n");
ac->timeout_task = GNUNET_SCHEDULER_add_delayed (ABORT_GENERIC_TIMEOUT,
&handle_abort_timeout,
ac);
begin_transaction (ac);
return MHD_YES;
}
/* end of taler-merchant-httpd_post-orders-ID-abort.c */