taler-exchange-httpd_get-purses-PURSE_PUB-merge.c (13417B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022 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_get-purses-PURSE_PUB-merge.c 18 * @brief Handle GET /purses/$PID/$TARGET requests 19 * @author Christian Grothoff 20 */ 21 #include "taler/platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <jansson.h> 24 #include <microhttpd.h> 25 #include "taler/taler_mhd_lib.h" 26 #include "taler/taler_dbevents.h" 27 #include "taler-exchange-httpd_get-keys.h" 28 #include "taler-exchange-httpd_get-purses-PURSE_PUB-merge.h" 29 #include "taler-exchange-httpd_mhd.h" 30 #include "taler-exchange-httpd_responses.h" 31 32 33 /** 34 * Information about an ongoing /purses GET operation. 35 */ 36 struct GetContext 37 { 38 /** 39 * Kept in a DLL. 40 */ 41 struct GetContext *next; 42 43 /** 44 * Kept in a DLL. 45 */ 46 struct GetContext *prev; 47 48 /** 49 * Connection we are handling. 50 */ 51 struct MHD_Connection *connection; 52 53 /** 54 * Subscription for the database event we are 55 * waiting for. 56 */ 57 struct GNUNET_DB_EventHandler *eh; 58 59 /** 60 * Subscription for refund event we are 61 * waiting for. 62 */ 63 struct GNUNET_DB_EventHandler *ehr; 64 65 /** 66 * Public key of our purse. 67 */ 68 struct TALER_PurseContractPublicKeyP purse_pub; 69 70 /** 71 * When does this purse expire? 72 */ 73 struct GNUNET_TIME_Timestamp purse_expiration; 74 75 /** 76 * When was this purse merged? 77 */ 78 struct GNUNET_TIME_Timestamp merge_timestamp; 79 80 /** 81 * How much is the purse (supposed) to be worth? 82 */ 83 struct TALER_Amount amount; 84 85 /** 86 * How much was deposited into the purse so far? 87 */ 88 struct TALER_Amount deposited; 89 90 /** 91 * Hash over the contract of the purse. 92 */ 93 struct TALER_PrivateContractHashP h_contract; 94 95 /** 96 * When will this request time out? 97 */ 98 struct GNUNET_TIME_Absolute timeout; 99 100 /** 101 * true to wait for merge, false to wait for deposit. 102 */ 103 bool wait_for_merge; 104 105 /** 106 * True if we are still suspended. 107 */ 108 bool suspended; 109 }; 110 111 112 /** 113 * Head of DLL of suspended GET requests. 114 */ 115 static struct GetContext *gc_head; 116 117 /** 118 * Tail of DLL of suspended GET requests. 119 */ 120 static struct GetContext *gc_tail; 121 122 123 void 124 TEH_purses_get_cleanup () 125 { 126 struct GetContext *gc; 127 128 while (NULL != (gc = gc_head)) 129 { 130 GNUNET_CONTAINER_DLL_remove (gc_head, 131 gc_tail, 132 gc); 133 if (gc->suspended) 134 { 135 gc->suspended = false; 136 MHD_resume_connection (gc->connection); 137 } 138 } 139 } 140 141 142 /** 143 * Function called once a connection is done to 144 * clean up the `struct GetContext` state. 145 * 146 * @param rc context to clean up for 147 */ 148 static void 149 gc_cleanup (struct TEH_RequestContext *rc) 150 { 151 struct GetContext *gc = rc->rh_ctx; 152 153 GNUNET_assert (! gc->suspended); 154 if (NULL != gc->eh) 155 { 156 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 157 "Cancelling DB event listening\n"); 158 TEH_plugin->event_listen_cancel (TEH_plugin->cls, 159 gc->eh); 160 gc->eh = NULL; 161 } 162 if (NULL != gc->ehr) 163 { 164 TEH_plugin->event_listen_cancel (TEH_plugin->cls, 165 gc->ehr); 166 gc->ehr = NULL; 167 } 168 GNUNET_free (gc); 169 } 170 171 172 /** 173 * Function called on events received from Postgres. 174 * Wakes up long pollers. 175 * 176 * @param cls the `struct TEH_RequestContext *` 177 * @param extra additional event data provided 178 * @param extra_size number of bytes in @a extra 179 */ 180 static void 181 db_event_cb (void *cls, 182 const void *extra, 183 size_t extra_size) 184 { 185 struct TEH_RequestContext *rc = cls; 186 struct GetContext *gc = rc->rh_ctx; 187 struct GNUNET_AsyncScopeSave old_scope; 188 189 (void) extra; 190 (void) extra_size; 191 if (NULL == gc) 192 return; /* event triggered while main transaction 193 was still running */ 194 if (! gc->suspended) 195 return; /* might get multiple wake-up events */ 196 gc->suspended = false; 197 GNUNET_async_scope_enter (&rc->async_scope_id, 198 &old_scope); 199 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 200 "Waking up on %p - %p - %s\n", 201 rc, 202 gc, 203 gc->suspended ? "suspended" : "active"); 204 TEH_check_invariants (); 205 GNUNET_CONTAINER_DLL_remove (gc_head, 206 gc_tail, 207 gc); 208 MHD_resume_connection (gc->connection); 209 TALER_MHD_daemon_trigger (); 210 TEH_check_invariants (); 211 GNUNET_async_scope_restore (&old_scope); 212 } 213 214 215 MHD_RESULT 216 TEH_handler_purses_get (struct TEH_RequestContext *rc, 217 const char *const args[2]) 218 { 219 struct GetContext *gc = rc->rh_ctx; 220 bool purse_deleted; 221 bool purse_refunded; 222 MHD_RESULT res; 223 224 if (NULL == gc) 225 { 226 gc = GNUNET_new (struct GetContext); 227 rc->rh_ctx = gc; 228 rc->rh_cleaner = &gc_cleanup; 229 gc->connection = rc->connection; 230 if (GNUNET_OK != 231 GNUNET_STRINGS_string_to_data (args[0], 232 strlen (args[0]), 233 &gc->purse_pub, 234 sizeof (gc->purse_pub))) 235 { 236 GNUNET_break_op (0); 237 return TALER_MHD_reply_with_error (rc->connection, 238 MHD_HTTP_BAD_REQUEST, 239 TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED, 240 args[0]); 241 } 242 if (0 == strcmp (args[1], 243 "merge")) 244 gc->wait_for_merge = true; 245 else if (0 == strcmp (args[1], 246 "deposit")) 247 gc->wait_for_merge = false; 248 else 249 { 250 GNUNET_break_op (0); 251 return TALER_MHD_reply_with_error (rc->connection, 252 MHD_HTTP_BAD_REQUEST, 253 TALER_EC_EXCHANGE_PURSES_INVALID_WAIT_TARGET, 254 args[1]); 255 } 256 257 TALER_MHD_parse_request_timeout (rc->connection, 258 &gc->timeout); 259 if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) && 260 (NULL == gc->eh) ) 261 { 262 struct TALER_PurseEventP rep = { 263 .header.size = htons (sizeof (rep)), 264 .header.type = htons ( 265 gc->wait_for_merge 266 ? TALER_DBEVENT_EXCHANGE_PURSE_MERGED 267 : TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED), 268 .purse_pub = gc->purse_pub 269 }; 270 271 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 272 "Starting DB event listening on purse %s (%s)\n", 273 TALER_B2S (&gc->purse_pub), 274 gc->wait_for_merge 275 ? "waiting for merge" 276 : "waiting for deposit"); 277 gc->eh = TEH_plugin->event_listen ( 278 TEH_plugin->cls, 279 GNUNET_TIME_absolute_get_remaining (gc->timeout), 280 &rep.header, 281 &db_event_cb, 282 rc); 283 if (NULL == gc->eh) 284 { 285 GNUNET_break (0); 286 gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS; 287 } 288 else 289 { 290 struct GNUNET_DB_EventHeaderP repr = { 291 .size = htons (sizeof (repr)), 292 .type = htons (TALER_DBEVENT_EXCHANGE_PURSE_REFUNDED), 293 }; 294 295 gc->ehr = TEH_plugin->event_listen ( 296 TEH_plugin->cls, 297 GNUNET_TIME_absolute_get_remaining (gc->timeout), 298 &repr, 299 &db_event_cb, 300 rc); 301 } 302 } 303 } /* end first-time initialization */ 304 305 { 306 enum GNUNET_DB_QueryStatus qs; 307 struct GNUNET_TIME_Timestamp create_timestamp; 308 309 qs = TEH_plugin->select_purse (TEH_plugin->cls, 310 &gc->purse_pub, 311 &create_timestamp, 312 &gc->purse_expiration, 313 &gc->amount, 314 &gc->deposited, 315 &gc->h_contract, 316 &gc->merge_timestamp, 317 &purse_deleted, 318 &purse_refunded); 319 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 320 "select_purse %s returned %d (%s)\n", 321 args[0], 322 (int) qs, 323 GNUNET_TIME_timestamp2s (gc->merge_timestamp)); 324 switch (qs) 325 { 326 case GNUNET_DB_STATUS_HARD_ERROR: 327 GNUNET_break (0); 328 return TALER_MHD_reply_with_error (rc->connection, 329 MHD_HTTP_INTERNAL_SERVER_ERROR, 330 TALER_EC_GENERIC_DB_FETCH_FAILED, 331 "select_purse"); 332 case GNUNET_DB_STATUS_SOFT_ERROR: 333 GNUNET_break (0); 334 return TALER_MHD_reply_with_error (rc->connection, 335 MHD_HTTP_INTERNAL_SERVER_ERROR, 336 TALER_EC_GENERIC_DB_FETCH_FAILED, 337 "select_purse"); 338 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 339 return TALER_MHD_reply_with_error (rc->connection, 340 MHD_HTTP_NOT_FOUND, 341 TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN, 342 NULL); 343 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 344 break; /* handled below */ 345 } 346 } 347 if (purse_refunded || 348 purse_deleted) 349 { 350 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 351 "Purse refunded or deleted\n"); 352 return TALER_MHD_reply_with_error (rc->connection, 353 MHD_HTTP_GONE, 354 purse_deleted 355 ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED 356 : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED 357 , 358 GNUNET_TIME_timestamp2s ( 359 gc->purse_expiration)); 360 } 361 362 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 363 "Deposited amount is %s (%d/%d/%d)\n", 364 TALER_amount2s (&gc->deposited), 365 GNUNET_TIME_absolute_is_future (gc->timeout), 366 GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time), 367 (0 < 368 TALER_amount_cmp (&gc->amount, 369 &gc->deposited))); 370 if (GNUNET_TIME_absolute_is_future (gc->timeout) && 371 ( ((gc->wait_for_merge) && 372 GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time)) || 373 ((! gc->wait_for_merge) && 374 (0 < 375 TALER_amount_cmp (&gc->amount, 376 &gc->deposited))) ) ) 377 { 378 gc->suspended = true; 379 GNUNET_CONTAINER_DLL_insert (gc_head, 380 gc_tail, 381 gc); 382 MHD_suspend_connection (gc->connection); 383 return MHD_YES; 384 } 385 386 { 387 struct GNUNET_TIME_Timestamp dt = GNUNET_TIME_timestamp_get (); 388 struct TALER_ExchangePublicKeyP exchange_pub; 389 struct TALER_ExchangeSignatureP exchange_sig; 390 enum TALER_ErrorCode ec; 391 392 if (GNUNET_TIME_timestamp_cmp (dt, 393 >, 394 gc->purse_expiration)) 395 dt = gc->purse_expiration; 396 if (0 < 397 TALER_amount_cmp (&gc->amount, 398 &gc->deposited)) 399 { 400 /* amount > deposited: not yet fully paid */ 401 dt = GNUNET_TIME_UNIT_ZERO_TS; 402 } 403 if (TALER_EC_NONE != 404 (ec = TALER_exchange_online_purse_status_sign ( 405 &TEH_keys_exchange_sign_, 406 gc->merge_timestamp, 407 dt, 408 &gc->deposited, 409 &exchange_pub, 410 &exchange_sig))) 411 { 412 res = TALER_MHD_reply_with_ec (rc->connection, 413 ec, 414 NULL); 415 } 416 else 417 { 418 /* Make sure merge_timestamp is omitted if not yet merged */ 419 if (GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time)) 420 gc->merge_timestamp = GNUNET_TIME_UNIT_ZERO_TS; 421 res = TALER_MHD_REPLY_JSON_PACK ( 422 rc->connection, 423 MHD_HTTP_OK, 424 TALER_JSON_pack_amount ("balance", 425 &gc->deposited), 426 GNUNET_JSON_pack_data_auto ("exchange_sig", 427 &exchange_sig), 428 GNUNET_JSON_pack_data_auto ("exchange_pub", 429 &exchange_pub), 430 GNUNET_JSON_pack_timestamp ("purse_expiration", 431 gc->purse_expiration), 432 GNUNET_JSON_pack_allow_null ( 433 GNUNET_JSON_pack_timestamp ("merge_timestamp", 434 gc->merge_timestamp)), 435 GNUNET_JSON_pack_allow_null ( 436 GNUNET_JSON_pack_timestamp ("deposit_timestamp", 437 dt)) 438 ); 439 } 440 } 441 return res; 442 } 443 444 445 /* end of taler-exchange-httpd_purses_get.c */