summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_track-transfer.c
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2016-06-09 19:36:24 +0200
committerChristian Grothoff <christian@grothoff.org>2016-06-09 19:36:24 +0200
commit7afcc8275c0e8e19ceeb178bca21c7988966cba5 (patch)
treeb8fb349ae21cd455b82a4bf354cc1cefa5d68e26 /src/backend/taler-merchant-httpd_track-transfer.c
parentcb930318e14b314288d762d690b69ff86e7ee466 (diff)
downloadmerchant-7afcc8275c0e8e19ceeb178bca21c7988966cba5.tar.gz
merchant-7afcc8275c0e8e19ceeb178bca21c7988966cba5.tar.bz2
merchant-7afcc8275c0e8e19ceeb178bca21c7988966cba5.zip
skeleton for /track/transfer and /track/transaction, renaming to match latest exchange API
Diffstat (limited to 'src/backend/taler-merchant-httpd_track-transfer.c')
-rw-r--r--src/backend/taler-merchant-httpd_track-transfer.c521
1 files changed, 521 insertions, 0 deletions
diff --git a/src/backend/taler-merchant-httpd_track-transfer.c b/src/backend/taler-merchant-httpd_track-transfer.c
new file mode 100644
index 00000000..034cc5ae
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_track-transfer.c
@@ -0,0 +1,521 @@
+/*
+ This file is part of TALER
+ (C) 2014, 2015, 2016 INRIA
+
+ 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, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/taler-merchant-httpd_track-transfer.c
+ * @brief implement API for tracking transfers and wire transfers
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.h"
+#include "taler-merchant-httpd_mhd.h"
+#include "taler-merchant-httpd_parsing.h"
+#include "taler-merchant-httpd_auditors.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_responses.h"
+#include "taler-merchant-httpd_track-transfer.h"
+
+
+/**
+ * How long to wait before giving up processing with the exchange?
+ */
+#define TRACK_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30))
+
+
+/**
+ * Context used for handing /track/transfer requests.
+ */
+struct TransferTrackContext
+{
+
+ /**
+ * This MUST be first!
+ */
+ struct TM_HandlerContext hc;
+
+ /**
+ * Handle to the exchange.
+ */
+ struct TALER_EXCHANGE_Handle *eh;
+
+ /**
+ * Handle for the /wire/transfers request.
+ */
+ struct TALER_EXCHANGE_TrackTransferHandle *wdh;
+
+ /**
+ * HTTP connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Response to return upon resume.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Handle for operation to lookup /keys (and auditors) from
+ * the exchange used for this transaction; NULL if no operation is
+ * pending.
+ */
+ struct TMH_EXCHANGES_FindOperation *fo;
+
+ /**
+ * Task run on timeout.
+ */
+ struct GNUNET_SCHEDULER_Task *timeout_task;
+
+ /**
+ * URI of the exchange.
+ */
+ char *uri;
+
+ /**
+ * Pointer to the detail that we are currently
+ * checking in #check_transfer().
+ */
+ const struct TALER_TrackTransferDetails *current_detail;
+
+ /**
+ * Argument for the /wire/transfers request.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * Response code to return.
+ */
+ unsigned int response_code;
+
+ /**
+ * #GNUNET_NO if we did not find a matching coin.
+ * #GNUNET_SYSERR if we found a matching coin, but the amounts do not match.
+ * #GNUNET_OK if we did find a matching coin.
+ */
+ int check_transfer_result;
+};
+
+
+/**
+ * Free the @a rctx.
+ *
+ * @param rctx data to free
+ */
+static void
+free_transfer_track_context (struct TransferTrackContext *rctx)
+{
+ if (NULL != rctx->fo)
+ {
+ TMH_EXCHANGES_find_exchange_cancel (rctx->fo);
+ rctx->fo = NULL;
+ }
+ if (NULL != rctx->timeout_task)
+ {
+ GNUNET_SCHEDULER_cancel (rctx->timeout_task);
+ rctx->timeout_task = NULL;
+ }
+ if (NULL != rctx->wdh)
+ {
+ TALER_EXCHANGE_track_transfer_cancel (rctx->wdh);
+ rctx->wdh = NULL;
+ }
+ if (NULL != rctx->uri)
+ {
+ GNUNET_free (rctx->uri);
+ rctx->uri = NULL;
+ }
+ GNUNET_free (rctx);
+}
+
+
+/**
+ * Resume the given /track/transfer operation and send the given response.
+ * Stores the response in the @a rctx and signals MHD to resume
+ * the connection. Also ensures MHD runs immediately.
+ *
+ * @param rctx transfer tracking context
+ * @param response_code response code to use
+ * @param response response data to send back
+ */
+static void
+resume_track_transfer_with_response (struct TransferTrackContext *rctx,
+ unsigned int response_code,
+ struct MHD_Response *response)
+{
+ rctx->response_code = response_code;
+ rctx->response = response;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Resuming /track/transaction handling as exchange interaction is done (%u)\n",
+ response_code);
+ if (NULL != rctx->timeout_task)
+ {
+ GNUNET_SCHEDULER_cancel (rctx->timeout_task);
+ rctx->timeout_task = NULL;
+ }
+ MHD_resume_connection (rctx->connection);
+ TMH_trigger_daemon (); /* we resumed, kick MHD */
+}
+
+
+/**
+ * Custom cleanup routine for a `struct TransferTrackContext`.
+ *
+ * @param hc the `struct TransferTrackContext` to clean up.
+ */
+static void
+track_transfer_cleanup (struct TM_HandlerContext *hc)
+{
+ struct TransferTrackContext *rctx = (struct TransferTrackContext *) hc;
+
+ free_transfer_track_context (rctx);
+}
+
+
+/**
+ * Function called with information about a coin that was transfered.
+ * Verify that it matches the information claimed by the exchange.
+ * Update the `check_transfer_result` field accordingly.
+ *
+ * @param cls closure with our `struct TransferTrackContext *`
+ * @param transaction_id of the contract
+ * @param coin_pub public key of the coin
+ * @param amount_with_fee amount the exchange will transfer for this coin
+ * @param transfer_fee fee the exchange will charge for this coin
+ * @param exchange_proof proof from exchange that coin was accepted
+ */
+static void
+check_transfer (void *cls,
+ uint64_t transaction_id,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *transfer_fee,
+ const json_t *exchange_proof)
+{
+ struct TransferTrackContext *rctx = cls;
+ const struct TALER_TrackTransferDetails *wdd = rctx->current_detail;
+
+ if (0 != memcmp (&wdd->coin_pub,
+ coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP)))
+ return; /* not the coin we're looking for */
+ if ( (0 != TALER_amount_cmp (amount_with_fee,
+ &wdd->coin_value)) ||
+ (0 != TALER_amount_cmp (transfer_fee,
+ &wdd->coin_fee)) )
+ {
+ /* Disagreement between the exchange and us about how much this
+ coin is worth! */
+ GNUNET_break_op (0);
+ rctx->check_transfer_result = GNUNET_SYSERR;
+ return;
+ }
+ rctx->check_transfer_result = GNUNET_OK;
+}
+
+
+/**
+ * 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 exchange protocol violation
+ * @param exchange_pub public key of the exchange used to sign @a json
+ * @param json original json reply (may include signatures, those have then been
+ * validated already)
+ * @param wtid extracted wire transfer identifier, or NULL if the exchange 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 exchange 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_transfer_cb (void *cls,
+ unsigned int http_status,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const json_t *json,
+ const struct GNUNET_HashCode *h_wire,
+ const struct TALER_Amount *total_amount,
+ unsigned int details_length,
+ const struct TALER_TrackTransferDetails *details)
+{
+ struct TransferTrackContext *rctx = cls;
+ unsigned int i;
+ int ret;
+
+ rctx->wdh = NULL;
+ if (MHD_HTTP_OK != http_status)
+ {
+ resume_track_transfer_with_response
+ (rctx,
+ MHD_HTTP_FAILED_DEPENDENCY,
+ TMH_RESPONSE_make_json_pack ("{s:I, s:O}",
+ "exchange_status", (json_int_t) http_status,
+ "details", json));
+ return;
+ }
+
+ if (GNUNET_OK !=
+ db->store_transfer_to_proof (db->cls,
+ rctx->uri,
+ &rctx->wtid,
+ exchange_pub,
+ json))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to persist wire transfer proof in DB\n");
+ }
+
+ for (i=0;i<details_length;i++)
+ {
+ rctx->current_detail = &details[i];
+ rctx->check_transfer_result = GNUNET_NO;
+ ret = db->find_payments_by_id (rctx,
+ details[i].transaction_id,
+ &check_transfer,
+ rctx);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to verify existing payment data in DB\n");
+ }
+ if ( (GNUNET_NO == ret) ||
+ (GNUNET_NO == rctx->check_transfer_result) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to find payment data in DB\n");
+ }
+ if (GNUNET_SYSERR == rctx->check_transfer_result)
+ {
+ /* #check_transfer() failed, do something! */
+ GNUNET_break (0);
+ /* FIXME: generate nicer custom response */
+ resume_track_transfer_with_response
+ (rctx,
+ MHD_HTTP_FAILED_DEPENDENCY,
+ TMH_RESPONSE_make_json_pack ("{s:I, s:O}",
+ "index", (json_int_t) i,
+ "details", json));
+ return;
+ }
+ ret = db->store_coin_to_transfer (db->cls,
+ details[i].transaction_id,
+ &details[i].coin_pub,
+ &rctx->wtid);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to persist coin to wire transfer mapping in DB\n");
+ }
+ }
+ /* FIXME: might want a more custom response here... */
+ resume_track_transfer_with_response
+ (rctx,
+ MHD_HTTP_OK,
+ TMH_RESPONSE_make_json_pack ("{s:I, s:O}",
+ "exchange_status", (json_int_t) http_status,
+ "details", json));
+}
+
+
+/**
+ * Function called with the result of our exchange lookup.
+ *
+ * @param cls the `struct TransferTrackContext`
+ * @param eh NULL if exchange was not found to be acceptable
+ * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
+ */
+static void
+process_track_transfer_with_exchange (void *cls,
+ struct TALER_EXCHANGE_Handle *eh,
+ int exchange_trusted)
+{
+ struct TransferTrackContext *rctx = cls;
+
+ rctx->fo = NULL;
+ rctx->eh = eh;
+ rctx->wdh = TALER_EXCHANGE_track_transfer (eh,
+ &rctx->wtid,
+ &wire_transfer_cb,
+ rctx);
+}
+
+
+/**
+ * Handle a timeout for the processing of the track transfer request.
+ *
+ * @param cls closure
+ */
+static void
+handle_track_transfer_timeout (void *cls)
+{
+ struct TransferTrackContext *rctx = cls;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Resuming /track/transfer with error after timeout\n");
+ rctx->timeout_task = NULL;
+
+ if (NULL != rctx->fo)
+ {
+ TMH_EXCHANGES_find_exchange_cancel (rctx->fo);
+ rctx->fo = NULL;
+ }
+ resume_track_transfer_with_response (rctx,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ TMH_RESPONSE_make_internal_error ("exchange not reachable"));
+}
+
+
+/**
+ * Function called with information about a wire transfer identifier.
+ * Generate a response based on the given @a proof.
+ *
+ * @param cls closure
+ * @param proof proof from exchange about what the wire transfer was for
+ */
+static void
+proof_cb (void *cls,
+ const json_t *proof)
+{
+ struct TransferTrackContext *rctx = cls;
+
+ rctx->response_code = MHD_HTTP_OK;
+ /* FIXME: might want a more custom response here... */
+ rctx->response = TMH_RESPONSE_make_json_pack ("{s:I, s:O}",
+ "exchange_status", (json_int_t) MHD_HTTP_OK,
+ "details", proof);
+}
+
+
+/**
+ * Manages a /track/transfer call, thus it calls the /track/wtid
+ * offered by the exchange in order to return the set of transfers
+ * (of coins) associated with a given wire transfer.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+MH_handler_track_transfer (struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ struct TransferTrackContext *rctx;
+ const char *str;
+ const char *uri;
+ int ret;
+
+ if (NULL == *connection_cls)
+ {
+ rctx = GNUNET_new (struct TransferTrackContext);
+ rctx->hc.cc = &track_transfer_cleanup;
+ rctx->connection = connection;
+ *connection_cls = rctx;
+ }
+ else
+ {
+ /* not first call, recover state */
+ rctx = *connection_cls;
+ }
+
+ if (0 != rctx->response_code)
+ {
+ /* We are *done* processing the request, just queue the response (!) */
+ if (UINT_MAX == rctx->response_code)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard error */
+ }
+ ret = MHD_queue_response (connection,
+ rctx->response_code,
+ rctx->response);
+ if (NULL != rctx->response)
+ {
+ MHD_destroy_response (rctx->response);
+ rctx->response = NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Queueing response (%u) for /track/transfer (%s).\n",
+ (unsigned int) rctx->response_code,
+ ret ? "OK" : "FAILED");
+ return ret;
+ }
+
+ uri = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "exchange");
+ if (NULL == uri)
+ return TMH_RESPONSE_reply_bad_request (connection,
+ "exchange argument missing");
+ rctx->uri = GNUNET_strdup (uri);
+
+ str = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "wtid");
+ if (NULL == str)
+ return TMH_RESPONSE_reply_bad_request (connection,
+ "wtid argument missing");
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (str,
+ strlen (str),
+ &rctx->wtid,
+ sizeof (rctx->wtid)))
+ {
+ return TMH_RESPONSE_reply_bad_request (connection,
+ "wtid argument malformed");
+ }
+
+ /* Check if reply is already in database! */
+ ret = db->find_proof_by_wtid (db->cls,
+ rctx->uri,
+ &rctx->wtid,
+ &proof_cb,
+ rctx);
+ if (0 != rctx->response_code)
+ {
+ ret = MHD_queue_response (connection,
+ rctx->response_code,
+ rctx->response);
+ if (NULL != rctx->response)
+ {
+ MHD_destroy_response (rctx->response);
+ rctx->response = NULL;
+ }
+ return ret;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Suspending /track/transfer handling while working with the exchange\n");
+ MHD_suspend_connection (connection);
+ rctx->fo = TMH_EXCHANGES_find_exchange (uri,
+ &process_track_transfer_with_exchange,
+ rctx);
+ rctx->timeout_task
+ = GNUNET_SCHEDULER_add_delayed (TRACK_TIMEOUT,
+ &handle_track_transfer_timeout,
+ rctx);
+ return MHD_NO;
+}
+
+/* end of taler-merchant-httpd_track-transfer.c */