summaryrefslogtreecommitdiff
path: root/src/exchange/taler-exchange-httpd_purses_get.c
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2022-04-23 12:34:48 +0200
committerChristian Grothoff <christian@grothoff.org>2022-04-23 12:34:48 +0200
commit132359a4440e07177df4afe596be4b16270a47d8 (patch)
treeb964765909b49bddc54f0d2a06818a3da080f80f /src/exchange/taler-exchange-httpd_purses_get.c
parenta72337a5f39b4e826055974fbc5a3261759ebbec (diff)
downloadexchange-132359a4440e07177df4afe596be4b16270a47d8.tar.gz
exchange-132359a4440e07177df4afe596be4b16270a47d8.tar.bz2
exchange-132359a4440e07177df4afe596be4b16270a47d8.zip
add purses-get to build
Diffstat (limited to 'src/exchange/taler-exchange-httpd_purses_get.c')
-rw-r--r--src/exchange/taler-exchange-httpd_purses_get.c371
1 files changed, 327 insertions, 44 deletions
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 <jansson.h>
#include <microhttpd.h>
#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;
}