/* This file is part of TALER (C) 2017-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU 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_get-rewards-ID.c * @brief implementation of a GET /rewards/ID handler * @author Christian Grothoff */ #include "platform.h" #include #include #include #include #include #include "taler-merchant-httpd.h" #include "taler-merchant-httpd_mhd.h" #include "taler-merchant-httpd_exchanges.h" /** * Information we keep per /kyc request. */ struct RewardContext { /** * Stored in a DLL. */ struct RewardContext *next; /** * Stored in a DLL. */ struct RewardContext *prev; /** * Connection we are handling. */ struct MHD_Connection *connection; /** * Our handler context. */ struct TMH_HandlerContext *hc; /** * Database event we are waiting on to be resuming. */ struct GNUNET_DB_EventHandler *eh; /** * Response to return, NULL if we don't have one yet. */ struct MHD_Response *response; /** * When does this request time out? */ struct GNUNET_TIME_Absolute timeout; /** * ID of the reward being queried. */ struct TALER_RewardIdentifierP reward_id; /** * Minimum reward amount picked up we should return to the * client. */ struct TALER_Amount min_amount; /** * #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_pc_resume during shutdown. */ enum GNUNET_GenericReturnValue suspended; /** * Is the "pickups" argument set to "yes"? */ bool fpu; /** * True if @e min_amount was provided. */ bool have_min_amount; }; /** * Head of DLL. */ static struct RewardContext *tc_head; /** * Tail of DLL. */ static struct RewardContext *tc_tail; void TMH_force_reward_resume () { for (struct RewardContext *tc = tc_head; NULL != tc; tc = tc->next) { if (GNUNET_YES == tc->suspended) { tc->suspended = GNUNET_SYSERR; MHD_resume_connection (tc->connection); } } } /** * Custom cleanup routine for a `struct RewardContext`. * * @param cls the `struct RewardContext` to clean up. */ static void reward_context_cleanup (void *cls) { struct RewardContext *tc = cls; if (NULL != tc->response) { MHD_destroy_response (tc->response); tc->response = NULL; } if (NULL != tc->eh) { TMH_db->event_listen_cancel (tc->eh); tc->eh = NULL; } GNUNET_CONTAINER_DLL_remove (tc_head, tc_tail, tc); GNUNET_free (tc); } /** * We have received a trigger from the database * that we should (possibly) resume the request. * * @param cls a `struct RewardContext` to resume * @param extra usually NULL * @param extra_size number of bytes in @a extra */ static void resume_by_event (void *cls, const void *extra, size_t extra_size) { struct RewardContext *tc = cls; (void) extra; (void) extra_size; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Resuming request %p by trigger\n", tc); if (GNUNET_NO == tc->suspended) return; /* duplicate event is possible */ tc->suspended = GNUNET_NO; GNUNET_CONTAINER_DLL_remove (tc_head, tc_tail, tc); MHD_resume_connection (tc->connection); TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ } MHD_RESULT TMH_private_get_rewards_ID (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct RewardContext *tc = hc->ctx; struct TALER_Amount total_authorized; struct TALER_Amount total_picked_up; char *reason; struct GNUNET_TIME_Timestamp expiration; struct TALER_ReservePublicKeyP reserve_pub; unsigned int pickups_length = 0; struct TALER_MERCHANTDB_PickupDetails *pickups = NULL; enum GNUNET_DB_QueryStatus qs; json_t *pickups_json = NULL; (void) rh; if (NULL == tc) { tc = GNUNET_new (struct RewardContext); hc->ctx = tc; hc->cc = &reward_context_cleanup; GNUNET_CONTAINER_DLL_insert (tc_head, tc_tail, tc); tc->connection = connection; tc->hc = hc; GNUNET_assert (NULL != hc->infix); if (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (hc->infix, &tc->reward_id.hash)) { /* reward_id has wrong encoding */ GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, hc->infix); } { const char *pstr; pstr = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "pickups"); tc->fpu = (NULL != pstr) ? 0 == strcasecmp (pstr, "yes") : false; } { const char *min_amount; min_amount = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "min_amount"); if (NULL != min_amount) { if (GNUNET_OK != TALER_string_to_amount (min_amount, &tc->min_amount)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "min_amount"); } tc->have_min_amount = true; } } TALER_MHD_parse_request_timeout (connection, &tc->timeout); if (! GNUNET_TIME_absolute_is_future (tc->timeout)) { struct TMH_RewardPickupEventP reward_eh = { .header.size = htons (sizeof (reward_eh)), .header.type = htons (TALER_DBEVENT_MERCHANT_REWARD_PICKUP), .reward_id = tc->reward_id }; GNUNET_CRYPTO_hash (hc->instance->settings.id, strlen (hc->instance->settings.id), &reward_eh.h_instance); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Subscribing to payment triggers for %p\n", tc); tc->eh = TMH_db->event_listen ( TMH_db->cls, &reward_eh.header, GNUNET_TIME_absolute_get_remaining (tc->timeout), &resume_by_event, tc); } } GNUNET_assert (GNUNET_YES != tc->suspended); TMH_db->preflight (TMH_db->cls); qs = TMH_db->lookup_reward_details (TMH_db->cls, hc->instance->settings.id, &tc->reward_id, tc->fpu, &total_authorized, &total_picked_up, &reason, &expiration, &reserve_pub, &pickups_length, &pickups); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { unsigned int response_code; enum TALER_ErrorCode ec; switch (qs) { case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: ec = TALER_EC_MERCHANT_GENERIC_REWARD_ID_UNKNOWN; response_code = MHD_HTTP_NOT_FOUND; break; case GNUNET_DB_STATUS_SOFT_ERROR: ec = TALER_EC_GENERIC_DB_SOFT_FAILURE; response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; break; case GNUNET_DB_STATUS_HARD_ERROR: ec = TALER_EC_GENERIC_DB_COMMIT_FAILED; response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; break; default: GNUNET_break (0); ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; break; } return TALER_MHD_reply_with_error (connection, response_code, ec, NULL); } /* do not allow timeout above reward expiration */ tc->timeout = GNUNET_TIME_absolute_min (tc->timeout, expiration.abs_time); if ( (NULL != tc->eh) && (GNUNET_TIME_absolute_is_future (tc->timeout)) && (tc->have_min_amount) && (GNUNET_YES == TALER_amount_cmp_currency (&tc->min_amount, &total_picked_up)) && (1 == TALER_amount_cmp (&tc->min_amount, &total_picked_up)) ) { MHD_suspend_connection (connection); tc->suspended = GNUNET_YES; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Suspending REWARD request handling as pickup is below threshold requested by client\n"); GNUNET_array_grow (pickups, pickups_length, 0); GNUNET_free (reason); return MHD_YES; } if (tc->fpu) { pickups_json = json_array (); GNUNET_assert (NULL != pickups_json); for (unsigned int i = 0; i