taler-exchange-httpd_reserves_get.c (7808B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-httpd_reserves_get.c 18 * @brief Handle /reserves/$RESERVE_PUB GET requests 19 * @author Florian Dold 20 * @author Benedikt Mueller 21 * @author Christian Grothoff 22 */ 23 #include "taler/platform.h" 24 #include <gnunet/gnunet_util_lib.h> 25 #include <jansson.h> 26 #include "taler/taler_mhd_lib.h" 27 #include "taler/taler_json_lib.h" 28 #include "taler/taler_dbevents.h" 29 #include "taler-exchange-httpd_keys.h" 30 #include "taler-exchange-httpd_reserves_get.h" 31 #include "taler-exchange-httpd_responses.h" 32 33 34 /** 35 * Reserve GET request that is long-polling. 36 */ 37 struct ReservePoller 38 { 39 /** 40 * Kept in a DLL. 41 */ 42 struct ReservePoller *next; 43 44 /** 45 * Kept in a DLL. 46 */ 47 struct ReservePoller *prev; 48 49 /** 50 * Connection we are handling. 51 */ 52 struct MHD_Connection *connection; 53 54 /** 55 * Our request context. 56 */ 57 struct TEH_RequestContext *rc; 58 59 /** 60 * Subscription for the database event we are waiting for. 61 */ 62 struct GNUNET_DB_EventHandler *eh; 63 64 /** 65 * When will this request time out? 66 */ 67 struct GNUNET_TIME_Absolute timeout; 68 69 /** 70 * Public key of the reserve the inquiry is about. 71 */ 72 struct TALER_ReservePublicKeyP reserve_pub; 73 74 /** 75 * Balance of the reserve, set in the callback. 76 */ 77 struct TALER_Amount balance; 78 79 /** 80 * Last origin account of the reserve, NULL if only 81 * P2P payments were made. 82 */ 83 struct TALER_FullPayto origin_account; 84 85 /** 86 * True if we are still suspended. 87 */ 88 bool suspended; 89 90 }; 91 92 93 /** 94 * Head of list of requests in long polling. 95 */ 96 static struct ReservePoller *rp_head; 97 98 /** 99 * Tail of list of requests in long polling. 100 */ 101 static struct ReservePoller *rp_tail; 102 103 104 void 105 TEH_reserves_get_cleanup () 106 { 107 for (struct ReservePoller *rp = rp_head; 108 NULL != rp; 109 rp = rp->next) 110 { 111 if (rp->suspended) 112 { 113 rp->suspended = false; 114 MHD_resume_connection (rp->connection); 115 } 116 } 117 } 118 119 120 /** 121 * Function called once a connection is done to 122 * clean up the `struct ReservePoller` state. 123 * 124 * @param rc context to clean up for 125 */ 126 static void 127 rp_cleanup (struct TEH_RequestContext *rc) 128 { 129 struct ReservePoller *rp = rc->rh_ctx; 130 131 GNUNET_assert (! rp->suspended); 132 if (NULL != rp->eh) 133 { 134 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 135 "Cancelling DB event listening on cleanup (odd unless during shutdown)\n"); 136 TEH_plugin->event_listen_cancel (TEH_plugin->cls, 137 rp->eh); 138 rp->eh = NULL; 139 } 140 GNUNET_CONTAINER_DLL_remove (rp_head, 141 rp_tail, 142 rp); 143 GNUNET_free (rp->origin_account.full_payto); 144 GNUNET_free (rp); 145 } 146 147 148 /** 149 * Function called on events received from Postgres. 150 * Wakes up long pollers. 151 * 152 * @param cls the `struct TEH_RequestContext *` 153 * @param extra additional event data provided 154 * @param extra_size number of bytes in @a extra 155 */ 156 static void 157 db_event_cb (void *cls, 158 const void *extra, 159 size_t extra_size) 160 { 161 struct ReservePoller *rp = cls; 162 struct GNUNET_AsyncScopeSave old_scope; 163 164 (void) extra; 165 (void) extra_size; 166 if (! rp->suspended) 167 return; /* might get multiple wake-up events */ 168 GNUNET_async_scope_enter (&rp->rc->async_scope_id, 169 &old_scope); 170 TEH_check_invariants (); 171 rp->suspended = false; 172 MHD_resume_connection (rp->connection); 173 TALER_MHD_daemon_trigger (); 174 TEH_check_invariants (); 175 GNUNET_async_scope_restore (&old_scope); 176 } 177 178 179 MHD_RESULT 180 TEH_handler_reserves_get ( 181 struct TEH_RequestContext *rc, 182 const struct TALER_ReservePublicKeyP *reserve_pub) 183 { 184 struct ReservePoller *rp = rc->rh_ctx; 185 186 if (NULL == rp) 187 { 188 rp = GNUNET_new (struct ReservePoller); 189 rp->connection = rc->connection; 190 rp->rc = rc; 191 rc->rh_ctx = rp; 192 rc->rh_cleaner = &rp_cleanup; 193 GNUNET_CONTAINER_DLL_insert (rp_head, 194 rp_tail, 195 rp); 196 rp->reserve_pub = *reserve_pub; 197 TALER_MHD_parse_request_timeout (rc->connection, 198 &rp->timeout); 199 } 200 201 if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) && 202 (NULL == rp->eh) ) 203 { 204 struct TALER_ReserveEventP rep = { 205 .header.size = htons (sizeof (rep)), 206 .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING), 207 .reserve_pub = rp->reserve_pub 208 }; 209 210 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 211 "Starting DB event listening until %s\n", 212 GNUNET_TIME_absolute2s (rp->timeout)); 213 rp->eh = TEH_plugin->event_listen ( 214 TEH_plugin->cls, 215 GNUNET_TIME_absolute_get_remaining (rp->timeout), 216 &rep.header, 217 &db_event_cb, 218 rp); 219 } 220 { 221 enum GNUNET_DB_QueryStatus qs; 222 223 GNUNET_free (rp->origin_account.full_payto); 224 qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls, 225 &rp->reserve_pub, 226 &rp->balance, 227 &rp->origin_account); 228 switch (qs) 229 { 230 case GNUNET_DB_STATUS_SOFT_ERROR: 231 GNUNET_break (0); /* single-shot query should never have soft-errors */ 232 return TALER_MHD_reply_with_error (rc->connection, 233 MHD_HTTP_INTERNAL_SERVER_ERROR, 234 TALER_EC_GENERIC_DB_SOFT_FAILURE, 235 "get_reserve_balance"); 236 case GNUNET_DB_STATUS_HARD_ERROR: 237 GNUNET_break (0); 238 return TALER_MHD_reply_with_error (rc->connection, 239 MHD_HTTP_INTERNAL_SERVER_ERROR, 240 TALER_EC_GENERIC_DB_FETCH_FAILED, 241 "get_reserve_balance"); 242 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 243 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 244 "Got reserve balance of %s\n", 245 TALER_amount2s (&rp->balance)); 246 return TALER_MHD_REPLY_JSON_PACK ( 247 rc->connection, 248 MHD_HTTP_OK, 249 GNUNET_JSON_pack_allow_null ( 250 GNUNET_JSON_pack_string ( 251 "last_origin", 252 rp->origin_account.full_payto)), 253 TALER_JSON_pack_amount ("balance", 254 &rp->balance)); 255 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 256 if (! GNUNET_TIME_absolute_is_future (rp->timeout)) 257 { 258 return TALER_MHD_reply_with_error (rc->connection, 259 MHD_HTTP_NOT_FOUND, 260 TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, 261 NULL); 262 } 263 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 264 "Long-polling on reserve for %s\n", 265 GNUNET_STRINGS_relative_time_to_string ( 266 GNUNET_TIME_absolute_get_remaining (rp->timeout), 267 true)); 268 rp->suspended = true; 269 MHD_suspend_connection (rc->connection); 270 return MHD_YES; 271 } 272 } 273 GNUNET_break (0); 274 return MHD_NO; 275 } 276 277 278 /* end of taler-exchange-httpd_reserves_get.c */