diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_purses_get.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_purses_get.c | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/src/exchange/taler-exchange-httpd_purses_get.c b/src/exchange/taler-exchange-httpd_purses_get.c new file mode 100644 index 000000000..22328fe09 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_purses_get.c @@ -0,0 +1,433 @@ +/* + This file is part of TALER + Copyright (C) 2022 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_purses_get.c + * @brief Handle GET /purses/$PID/$TARGET requests + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include "taler_mhd_lib.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" + + +/** + * 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; + + /** + * Subscription for refund event we are + * waiting for. + */ + struct GNUNET_DB_EventHandler *ehr; + + /** + * Public key of our purse. + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * When does this purse expire? + */ + struct GNUNET_TIME_Timestamp purse_expiration; + + /** + * When was this purse merged? + */ + struct GNUNET_TIME_Timestamp merge_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_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; + } + if (NULL != gc->ehr) + { + TEH_plugin->event_listen_cancel (TEH_plugin->cls, + gc->ehr); + gc->ehr = 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; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Waking up on %p - %p - %s\n", + rc, + gc, + gc->suspended ? "suspended" : "active"); + 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; + bool purse_deleted; + bool purse_refunded; + 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]); + } + + TALER_MHD_parse_request_timeout (rc->connection, + &gc->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 on purse %s\n", + TALER_B2S (&gc->purse_pub)); + gc->eh = TEH_plugin->event_listen ( + TEH_plugin->cls, + GNUNET_TIME_absolute_get_remaining (gc->timeout), + &rep.header, + &db_event_cb, + rc); + if (NULL == gc->eh) + { + GNUNET_break (0); + gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS; + } + else + { + struct GNUNET_DB_EventHeaderP repr = { + .size = htons (sizeof (repr)), + .type = htons (TALER_DBEVENT_EXCHANGE_PURSE_REFUNDED), + }; + + gc->ehr = TEH_plugin->event_listen ( + TEH_plugin->cls, + GNUNET_TIME_absolute_get_remaining (gc->timeout), + &repr, + &db_event_cb, + rc); + } + } + } /* end first-time initialization */ + + { + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Timestamp create_timestamp; + + qs = TEH_plugin->select_purse (TEH_plugin->cls, + &gc->purse_pub, + &create_timestamp, + &gc->purse_expiration, + &gc->amount, + &gc->deposited, + &gc->h_contract, + &gc->merge_timestamp, + &purse_deleted, + &purse_refunded); + 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_GENERIC_PURSE_UNKNOWN, + NULL); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; /* handled below */ + } + } + if (purse_refunded || + purse_deleted) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Purse refunded or deleted\n"); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_GONE, + purse_deleted + ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED + : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, + GNUNET_TIME_timestamp2s ( + gc->purse_expiration)); + } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Deposited amount is %s\n", + TALER_amount2s (&gc->deposited)); + if (GNUNET_TIME_absolute_is_future (gc->timeout) && + ( ((gc->wait_for_merge) && + GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time)) || + ((! gc->wait_for_merge) && + (0 < + TALER_amount_cmp (&gc->amount, + &gc->deposited))) ) ) + { + gc->suspended = true; + GNUNET_CONTAINER_DLL_insert (gc_head, + gc_tail, + gc); + MHD_suspend_connection (gc->connection); + return MHD_YES; + } + + { + struct GNUNET_TIME_Timestamp dt = GNUNET_TIME_timestamp_get (); + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + enum TALER_ErrorCode ec; + + if (GNUNET_TIME_timestamp_cmp (dt, + >, + gc->purse_expiration)) + dt = gc->purse_expiration; + if (0 < + TALER_amount_cmp (&gc->amount, + &gc->deposited)) + { + /* amount > deposited: not yet fully paid */ + dt = GNUNET_TIME_UNIT_ZERO_TS; + } + if (TALER_EC_NONE != + (ec = TALER_exchange_online_purse_status_sign ( + &TEH_keys_exchange_sign_, + gc->merge_timestamp, + dt, + &gc->deposited, + &exchange_pub, + &exchange_sig))) + { + res = TALER_MHD_reply_with_ec (rc->connection, + ec, + NULL); + } + else + { + /* Make sure merge_timestamp is omitted if not yet merged */ + if (GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time)) + gc->merge_timestamp = GNUNET_TIME_UNIT_ZERO_TS; + res = TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + MHD_HTTP_OK, + TALER_JSON_pack_amount ("balance", + &gc->deposited), + GNUNET_JSON_pack_data_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_pack_data_auto ("exchange_pub", + &exchange_pub), + GNUNET_JSON_pack_timestamp ("purse_expiration", + gc->purse_expiration), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_timestamp ("merge_timestamp", + gc->merge_timestamp)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_timestamp ("deposit_timestamp", + dt)) + ); + } + } + return res; +} + + +/* end of taler-exchange-httpd_purses_get.c */ |