From 132359a4440e07177df4afe596be4b16270a47d8 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 23 Apr 2022 12:34:48 +0200 Subject: add purses-get to build --- src/exchange/taler-exchange-httpd_purses_get.c | 371 ++++++++++++++++++++++--- 1 file changed, 327 insertions(+), 44 deletions(-) (limited to 'src/exchange/taler-exchange-httpd_purses_get.c') diff --git a/src/exchange/taler-exchange-httpd_purses_get.c b/src/exchange/taler-exchange-httpd_purses_get.c index 799eeccbf..dd904d790 100644 --- a/src/exchange/taler-exchange-httpd_purses_get.c +++ b/src/exchange/taler-exchange-httpd_purses_get.c @@ -15,7 +15,7 @@ */ /** * @file taler-exchange-httpd_purses_get.c - * @brief Handle GET /purses/$PID requests + * @brief Handle GET /purses/$PID/$TARGET requests * @author Christian Grothoff */ #include "platform.h" @@ -23,67 +23,350 @@ #include #include #include "taler_mhd_lib.h" -#include "taler-exchange-httpd_purses.h" +#include "taler_dbevents.h" +#include "taler-exchange-httpd_keys.h" +#include "taler-exchange-httpd_purses_get.h" #include "taler-exchange-httpd_mhd.h" #include "taler-exchange-httpd_responses.h" -// FIXME: add long-polling support! -MHD_RESULT -TEH_handler_pursess_get (struct TEH_RequestContext *rc, - const char *const args[1]) +/** + * Information about an ongoing /purses GET operation. + */ +struct GetContext { + /** + * Kept in a DLL. + */ + struct GetContext *next; + + /** + * Kept in a DLL. + */ + struct GetContext *prev; + + /** + * Connection we are handling. + */ + struct MHD_Connection *connection; + + /** + * Subscription for the database event we are + * waiting for. + */ + struct GNUNET_DB_EventHandler *eh; + + /** + * Public key of our purse. + */ struct TALER_PurseContractPublicKeyP purse_pub; - enum GNUNET_DB_QueryStatus qs; - MHD_RESULT res; - struct GNUNET_TIME_Timestamp merge_timestamp = {0}; - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[0], - strlen (args[0]), - &purse_pub, - sizeof (purse_pub))) + /** + * When does this purse expire? + */ + struct GNUNET_TIME_Timestamp purse_expiration; + + /** + * When was this purse merged? + */ + struct GNUNET_TIME_Timestamp merge_timestamp; + + /** + * When was the full amount deposited into this purse? + */ + struct GNUNET_TIME_Timestamp deposit_timestamp; + + /** + * How much is the purse (supposed) to be worth? + */ + struct TALER_Amount amount; + + /** + * How much was deposited into the purse so far? + */ + struct TALER_Amount deposited; + + /** + * Hash over the contract of the purse. + */ + struct TALER_PrivateContractHashP h_contract; + + /** + * When will this request time out? + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * true to wait for merge, false to wait for deposit. + */ + bool wait_for_merge; + + /** + * True if we are still suspended. + */ + bool suspended; +}; + + +/** + * Head of DLL of suspended GET requests. + */ +static struct GetContext *gc_head; + +/** + * Tail of DLL of suspended GET requests. + */ +static struct GetContext *gc_tail; + + +void +TEH_purses_get_cleanup () +{ + struct GetContext *gc; + + while (NULL != (gc = gc_head)) { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_PURSES_INVALID_PURSE_PUB, - args[0]); + GNUNET_CONTAINER_DLL_remove (gc_head, + gc_tail, + gc); + if (gc->suspended) + { + gc->suspended = false; + MHD_resume_connection (gc->connection); + } + } +} + + +/** + * Function called once a connection is done to + * clean up the `struct GetContext` state. + * + * @param rc context to clean up for + */ +static void +gc_cleanup (struct TEH_RequestContext *rc) +{ + struct GetContext *gc = rc->rh_ctx; + + GNUNET_assert (! gc->suspended); + if (NULL != gc->eh) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Cancelling DB event listening\n"); + TEH_plugin->event_listen_cancel (TEH_plugin->cls, + gc->eh); + gc->eh = NULL; } + GNUNET_free (gc); +} + + +/** + * Function called on events received from Postgres. + * Wakes up long pollers. + * + * @param cls the `struct TEH_RequestContext *` + * @param extra additional event data provided + * @param extra_size number of bytes in @a extra + */ +static void +db_event_cb (void *cls, + const void *extra, + size_t extra_size) +{ + struct TEH_RequestContext *rc = cls; + struct GetContext *gc = rc->rh_ctx; + struct GNUNET_AsyncScopeSave old_scope; + + (void) extra; + (void) extra_size; + if (NULL == gc) + return; /* event triggered while main transaction + was still running */ + if (! gc->suspended) + return; /* might get multiple wake-up events */ + gc->suspended = false; + GNUNET_async_scope_enter (&rc->async_scope_id, + &old_scope); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Resuming from long-polling on purse\n"); + TEH_check_invariants (); + GNUNET_CONTAINER_DLL_remove (gc_head, + gc_tail, + gc); + MHD_resume_connection (gc->connection); + TALER_MHD_daemon_trigger (); + TEH_check_invariants (); + GNUNET_async_scope_restore (&old_scope); +} + + +MHD_RESULT +TEH_handler_purses_get (struct TEH_RequestContext *rc, + const char *const args[2]) +{ + struct GetContext *gc = rc->rh_ctx; + MHD_RESULT res; + + if (NULL == gc) + { + gc = GNUNET_new (struct GetContext); + rc->rh_ctx = gc; + rc->rh_cleaner = &gc_cleanup; + gc->connection = rc->connection; + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &gc->purse_pub, + sizeof (gc->purse_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED, + args[0]); + } + if (0 == strcmp (args[1], + "merge")) + gc->wait_for_merge = true; + else if (0 == strcmp (args[1], + "deposit")) + gc->wait_for_merge = false; + else + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_PURSES_INVALID_WAIT_TARGET, + args[1]); + } + + { + const char *long_poll_timeout_ms; + + long_poll_timeout_ms + = MHD_lookup_connection_value (rc->connection, + MHD_GET_ARGUMENT_KIND, + "timeout_ms"); + if (NULL != long_poll_timeout_ms) + { + unsigned int timeout_ms; + char dummy; + struct GNUNET_TIME_Relative timeout; + + if (1 != sscanf (long_poll_timeout_ms, + "%u%c", + &timeout_ms, + &dummy)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "timeout_ms (must be non-negative number)"); + } + timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + timeout_ms); + gc->timeout = GNUNET_TIME_relative_to_absolute (timeout); + } + } + + if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) && + (NULL == gc->eh) ) + { + struct TALER_PurseEventP rep = { + .header.size = htons (sizeof (rep)), + .header.type = htons ( + gc->wait_for_merge + ? TALER_DBEVENT_EXCHANGE_PURSE_MERGED + : TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED), + .purse_pub = gc->purse_pub + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting DB event listening\n"); + gc->eh = TEH_plugin->event_listen ( + TEH_plugin->cls, + GNUNET_TIME_absolute_get_remaining (gc->timeout), + &rep.header, + &db_event_cb, + rc); + } + } /* end first-time initialization */ + #if FIXME - qs = TEH_plugin->select_purse (TEH_plugin->cls, - &purse_pub, - &merge_timestamp, - ...); - switch (qs) { - case GNUNET_DB_STATUS_HARD_ERROR: - GNUNET_break (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "select_purses"); - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "select_purses"); - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_plugin->select_purse (TEH_plugin->cls, + &gc->purse_pub, + &gc->purse_expiration, + &gc->amount, + &gc->deposited, + &gc->h_contract, + &gc->merge_timestamp, + &gc->deposit_timestamp); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "select_purse"); + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "select_purse"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_PURSE_UNKNOWN, + NULL); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; /* handled below */ + } + } + if (GNUNET_TIME_absolute_is_past (gc->purse_expiration)) + { return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_PURSESS_UNKNOWN, - NULL); - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - break; /* handled below */ + MHD_HTTP_GONE, + TALER_EC_EXCHANGE_PURSE_EXPIRED, + GNUNET_TIME_timestamp2s ( + gc->purse_expiration)); } #endif + + // FIXME: compare amount to deposited amount; + // if below, set 'deposit_timestamp' to zero! + + if (GNUNET_TIME_absolute_is_future (gc->timeout) && + ( ((gc->wait_for_merge) && + GNUNET_TIME_absolute_is_zero (gc->merge_timestamp.abs_time)) || + ((! gc->wait_for_merge) && + GNUNET_TIME_absolute_is_zero (gc->deposit_timestamp.abs_time)) )) + { + gc->suspended = true; + GNUNET_CONTAINER_DLL_insert (gc_head, + gc_tail, + gc); + MHD_suspend_connection (gc->connection); + return MHD_YES; + } + + // FIXME: add exchange signature!? + // FIXME: return amount? res = TALER_MHD_REPLY_JSON_PACK ( rc->connection, MHD_HTTP_OK, GNUNET_JSON_pack_timestamp ("merge_timestamp", - &merge_timestamp)); - GNUNET_free (epurses); + gc->merge_timestamp), + GNUNET_JSON_pack_timestamp ("deposit_timestamp", + gc->deposit_timestamp) + ); return res; } -- cgit v1.2.3