exchange_api_reserves_history.c (37132B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file lib/exchange_api_reserves_history.c 19 * @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <jansson.h> 24 #include <microhttpd.h> /* just for HTTP history codes */ 25 #include <gnunet/gnunet_util_lib.h> 26 #include <gnunet/gnunet_json_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include "taler/taler_exchange_service.h" 29 #include "taler/taler_json_lib.h" 30 #include "exchange_api_handle.h" 31 #include "taler/taler_signatures.h" 32 #include "exchange_api_curl_defaults.h" 33 34 35 /** 36 * @brief A /reserves/$RID/history Handle 37 */ 38 struct TALER_EXCHANGE_ReservesHistoryHandle 39 { 40 41 /** 42 * The keys of the exchange this request handle will use 43 */ 44 struct TALER_EXCHANGE_Keys *keys; 45 46 /** 47 * The url for this request. 48 */ 49 char *url; 50 51 /** 52 * Handle for the request. 53 */ 54 struct GNUNET_CURL_Job *job; 55 56 /** 57 * Context for #TEH_curl_easy_post(). Keeps the data that must 58 * persist for Curl to make the upload. 59 */ 60 struct TALER_CURL_PostContext post_ctx; 61 62 /** 63 * Function to call with the result. 64 */ 65 TALER_EXCHANGE_ReservesHistoryCallback cb; 66 67 /** 68 * Public key of the reserve we are querying. 69 */ 70 struct TALER_ReservePublicKeyP reserve_pub; 71 72 /** 73 * Closure for @a cb. 74 */ 75 void *cb_cls; 76 77 /** 78 * Where to store the etag (if any). 79 */ 80 uint64_t etag; 81 82 }; 83 84 85 /** 86 * Context for history entry helpers. 87 */ 88 struct HistoryParseContext 89 { 90 91 /** 92 * Keys of the exchange we use. 93 */ 94 const struct TALER_EXCHANGE_Keys *keys; 95 96 /** 97 * Our reserve public key. 98 */ 99 const struct TALER_ReservePublicKeyP *reserve_pub; 100 101 /** 102 * Array of UUIDs. 103 */ 104 struct GNUNET_HashCode *uuids; 105 106 /** 107 * Where to sum up total inbound amounts. 108 */ 109 struct TALER_Amount *total_in; 110 111 /** 112 * Where to sum up total outbound amounts. 113 */ 114 struct TALER_Amount *total_out; 115 116 /** 117 * Number of entries already used in @e uuids. 118 */ 119 unsigned int uuid_off; 120 }; 121 122 123 /** 124 * Type of a function called to parse a reserve history 125 * entry @a rh. 126 * 127 * @param[in,out] rh where to write the result 128 * @param[in,out] uc UUID context for duplicate detection 129 * @param transaction the transaction to parse 130 * @return #GNUNET_OK on success 131 */ 132 typedef enum GNUNET_GenericReturnValue 133 (*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 134 struct HistoryParseContext *uc, 135 const json_t *transaction); 136 137 138 /** 139 * Parse "credit" reserve history entry. 140 * 141 * @param[in,out] rh entry to parse 142 * @param uc our context 143 * @param transaction the transaction to parse 144 * @return #GNUNET_OK on success 145 */ 146 static enum GNUNET_GenericReturnValue 147 parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 148 struct HistoryParseContext *uc, 149 const json_t *transaction) 150 { 151 struct TALER_FullPayto wire_uri; 152 uint64_t wire_reference; 153 struct GNUNET_TIME_Timestamp timestamp; 154 struct GNUNET_JSON_Specification withdraw_spec[] = { 155 GNUNET_JSON_spec_uint64 ("wire_reference", 156 &wire_reference), 157 GNUNET_JSON_spec_timestamp ("timestamp", 158 ×tamp), 159 TALER_JSON_spec_full_payto_uri ("sender_account_url", 160 &wire_uri), 161 GNUNET_JSON_spec_end () 162 }; 163 164 rh->type = TALER_EXCHANGE_RTT_CREDIT; 165 if (0 > 166 TALER_amount_add (uc->total_in, 167 uc->total_in, 168 &rh->amount)) 169 { 170 /* overflow in history already!? inconceivable! Bad exchange! */ 171 GNUNET_break_op (0); 172 return GNUNET_SYSERR; 173 } 174 if (GNUNET_OK != 175 GNUNET_JSON_parse (transaction, 176 withdraw_spec, 177 NULL, NULL)) 178 { 179 GNUNET_break_op (0); 180 return GNUNET_SYSERR; 181 } 182 rh->details.in_details.sender_url.full_payto 183 = GNUNET_strdup (wire_uri.full_payto); 184 rh->details.in_details.wire_reference = wire_reference; 185 rh->details.in_details.timestamp = timestamp; 186 return GNUNET_OK; 187 } 188 189 190 /** 191 * Parse "withdraw" reserve history entry. 192 * 193 * @param[in,out] rh entry to parse 194 * @param uc our context 195 * @param transaction the transaction to parse 196 * @return #GNUNET_OK on success 197 */ 198 static enum GNUNET_GenericReturnValue 199 parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 200 struct HistoryParseContext *uc, 201 const json_t *transaction) 202 { 203 uint16_t num_coins; 204 struct TALER_Amount withdraw_fee; 205 struct TALER_Amount withdraw_amount; 206 struct TALER_Amount amount_without_fee; 207 uint8_t max_age = 0; 208 uint8_t noreveal_index = 0; 209 struct TALER_HashBlindedPlanchetsP planchets_h; 210 struct TALER_HashBlindedPlanchetsP selected_h; 211 struct TALER_ReserveSignatureP reserve_sig; 212 struct TALER_BlindingMasterSeedP blinding_seed; 213 struct TALER_DenominationHashP *denom_pub_hashes; 214 size_t num_denom_pub_hashes; 215 bool no_max_age; 216 bool no_noreveal_index; 217 bool no_blinding_seed; 218 bool no_selected_h; 219 struct GNUNET_JSON_Specification withdraw_spec[] = { 220 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 221 &reserve_sig), 222 GNUNET_JSON_spec_uint16 ("num_coins", 223 &num_coins), 224 GNUNET_JSON_spec_fixed_auto ("planchets_h", 225 &planchets_h), 226 TALER_JSON_spec_amount_any ("amount", 227 &withdraw_amount), 228 TALER_JSON_spec_amount_any ("withdraw_fee", 229 &withdraw_fee), 230 TALER_JSON_spec_array_of_denom_pub_h ("denom_pub_hashes", 231 &num_denom_pub_hashes, 232 &denom_pub_hashes), 233 GNUNET_JSON_spec_mark_optional ( 234 GNUNET_JSON_spec_fixed_auto ("selected_h", 235 &selected_h), 236 &no_selected_h), 237 GNUNET_JSON_spec_mark_optional ( 238 GNUNET_JSON_spec_uint8 ("max_age", 239 &max_age), 240 &no_max_age), 241 GNUNET_JSON_spec_mark_optional ( 242 GNUNET_JSON_spec_fixed_auto ("blinding_seed", 243 &blinding_seed), 244 &no_blinding_seed), 245 GNUNET_JSON_spec_mark_optional ( 246 GNUNET_JSON_spec_uint8 ("noreveal_index", 247 &noreveal_index), 248 &no_noreveal_index), 249 GNUNET_JSON_spec_end () 250 }; 251 252 rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL; 253 if (GNUNET_OK != 254 GNUNET_JSON_parse (transaction, 255 withdraw_spec, 256 NULL, NULL)) 257 { 258 GNUNET_break_op (0); 259 return GNUNET_SYSERR; 260 } 261 262 if ((no_max_age != no_noreveal_index) || 263 (no_max_age != no_selected_h)) 264 { 265 GNUNET_break_op (0); 266 GNUNET_JSON_parse_free (withdraw_spec); 267 return GNUNET_SYSERR; 268 } 269 rh->details.withdraw.age_restricted = ! no_max_age; 270 271 if (num_coins != num_denom_pub_hashes) 272 { 273 GNUNET_break_op (0); 274 GNUNET_JSON_parse_free (withdraw_spec); 275 return GNUNET_SYSERR; 276 } 277 278 /* Check that the signature is a valid withdraw request */ 279 if (0>TALER_amount_subtract ( 280 &amount_without_fee, 281 &withdraw_amount, 282 &withdraw_fee)) 283 { 284 GNUNET_break_op (0); 285 GNUNET_JSON_parse_free (withdraw_spec); 286 return GNUNET_SYSERR; 287 } 288 289 if (GNUNET_OK != 290 TALER_wallet_withdraw_verify ( 291 &amount_without_fee, 292 &withdraw_fee, 293 &planchets_h, 294 no_blinding_seed ? NULL : &blinding_seed, 295 no_max_age ? NULL : &uc->keys->age_mask, 296 no_max_age ? 0 : max_age, 297 uc->reserve_pub, 298 &reserve_sig)) 299 { 300 GNUNET_break_op (0); 301 GNUNET_JSON_parse_free (withdraw_spec); 302 return GNUNET_SYSERR; 303 } 304 305 rh->details.withdraw.num_coins = num_coins; 306 rh->details.withdraw.age_restricted = ! no_max_age; 307 rh->details.withdraw.max_age = max_age; 308 rh->details.withdraw.planchets_h = planchets_h; 309 rh->details.withdraw.selected_h = selected_h; 310 rh->details.withdraw.noreveal_index = noreveal_index; 311 rh->details.withdraw.no_blinding_seed = no_blinding_seed; 312 if (! no_blinding_seed) 313 rh->details.withdraw.blinding_seed = blinding_seed; 314 315 /* check that withdraw fee matches expectations! */ 316 { 317 const struct TALER_EXCHANGE_Keys *key_state; 318 struct TALER_Amount fee_acc; 319 struct TALER_Amount amount_acc; 320 321 GNUNET_assert (GNUNET_OK == 322 TALER_amount_set_zero (withdraw_amount.currency, 323 &fee_acc)); 324 GNUNET_assert (GNUNET_OK == 325 TALER_amount_set_zero (withdraw_amount.currency, 326 &amount_acc)); 327 328 key_state = uc->keys; 329 330 /* accumulate the withdraw fees */ 331 for (size_t i=0; i < num_coins; i++) 332 { 333 const struct TALER_EXCHANGE_DenomPublicKey *dki; 334 335 dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, 336 &denom_pub_hashes[i]); 337 if (NULL == dki) 338 { 339 GNUNET_break_op (0); 340 GNUNET_JSON_parse_free (withdraw_spec); 341 return GNUNET_SYSERR; 342 } 343 GNUNET_assert (0 <= 344 TALER_amount_add (&fee_acc, 345 &fee_acc, 346 &dki->fees.withdraw)); 347 GNUNET_assert (0 <= 348 TALER_amount_add (&amount_acc, 349 &amount_acc, 350 &dki->value)); 351 } 352 353 if ( (GNUNET_YES != 354 TALER_amount_cmp_currency (&fee_acc, 355 &withdraw_fee)) || 356 (0 != 357 TALER_amount_cmp (&amount_acc, 358 &amount_without_fee)) ) 359 { 360 GNUNET_break_op (0); 361 GNUNET_JSON_parse_free (withdraw_spec); 362 return GNUNET_SYSERR; 363 } 364 rh->details.withdraw.fee = withdraw_fee; 365 } 366 367 #pragma message "is out_authorization_sig still needed? Not set anywhere" 368 rh->details.withdraw.out_authorization_sig 369 = json_object_get (transaction, 370 "signature"); 371 /* Check check that the same withdraw transaction 372 isn't listed twice by the exchange. We use the 373 "uuid" array to remember the hashes of all 374 signatures, and compare the hashes to find 375 duplicates. */ 376 GNUNET_CRYPTO_hash (&reserve_sig, 377 sizeof (reserve_sig), 378 &uc->uuids[uc->uuid_off]); 379 for (unsigned int i = 0; i<uc->uuid_off; i++) 380 { 381 if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off], 382 &uc->uuids[i])) 383 { 384 GNUNET_break_op (0); 385 GNUNET_JSON_parse_free (withdraw_spec); 386 return GNUNET_SYSERR; 387 } 388 } 389 uc->uuid_off++; 390 391 if (0 > 392 TALER_amount_add (uc->total_out, 393 uc->total_out, 394 &rh->amount)) 395 { 396 /* overflow in history already!? inconceivable! Bad exchange! */ 397 GNUNET_break_op (0); 398 GNUNET_JSON_parse_free (withdraw_spec); 399 return GNUNET_SYSERR; 400 } 401 GNUNET_JSON_parse_free (withdraw_spec); 402 return GNUNET_OK; 403 } 404 405 406 /** 407 * Parse "recoup" reserve history entry. 408 * 409 * @param[in,out] rh entry to parse 410 * @param uc our context 411 * @param transaction the transaction to parse 412 * @return #GNUNET_OK on success 413 */ 414 static enum GNUNET_GenericReturnValue 415 parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 416 struct HistoryParseContext *uc, 417 const json_t *transaction) 418 { 419 const struct TALER_EXCHANGE_Keys *key_state; 420 struct GNUNET_JSON_Specification recoup_spec[] = { 421 GNUNET_JSON_spec_fixed_auto ("coin_pub", 422 &rh->details.recoup_details.coin_pub), 423 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 424 &rh->details.recoup_details.exchange_sig), 425 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 426 &rh->details.recoup_details.exchange_pub), 427 GNUNET_JSON_spec_timestamp ("timestamp", 428 &rh->details.recoup_details.timestamp), 429 GNUNET_JSON_spec_end () 430 }; 431 432 rh->type = TALER_EXCHANGE_RTT_RECOUP; 433 if (GNUNET_OK != 434 GNUNET_JSON_parse (transaction, 435 recoup_spec, 436 NULL, NULL)) 437 { 438 GNUNET_break_op (0); 439 return GNUNET_SYSERR; 440 } 441 key_state = uc->keys; 442 if (GNUNET_OK != 443 TALER_EXCHANGE_test_signing_key (key_state, 444 &rh->details. 445 recoup_details.exchange_pub)) 446 { 447 GNUNET_break_op (0); 448 return GNUNET_SYSERR; 449 } 450 if (GNUNET_OK != 451 TALER_exchange_online_confirm_recoup_verify ( 452 rh->details.recoup_details.timestamp, 453 &rh->amount, 454 &rh->details.recoup_details.coin_pub, 455 uc->reserve_pub, 456 &rh->details.recoup_details.exchange_pub, 457 &rh->details.recoup_details.exchange_sig)) 458 { 459 GNUNET_break_op (0); 460 return GNUNET_SYSERR; 461 } 462 if (0 > 463 TALER_amount_add (uc->total_in, 464 uc->total_in, 465 &rh->amount)) 466 { 467 /* overflow in history already!? inconceivable! Bad exchange! */ 468 GNUNET_break_op (0); 469 return GNUNET_SYSERR; 470 } 471 return GNUNET_OK; 472 } 473 474 475 /** 476 * Parse "closing" reserve history entry. 477 * 478 * @param[in,out] rh entry to parse 479 * @param uc our context 480 * @param transaction the transaction to parse 481 * @return #GNUNET_OK on success 482 */ 483 static enum GNUNET_GenericReturnValue 484 parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 485 struct HistoryParseContext *uc, 486 const json_t *transaction) 487 { 488 const struct TALER_EXCHANGE_Keys *key_state; 489 struct TALER_FullPayto receiver_uri; 490 struct GNUNET_JSON_Specification closing_spec[] = { 491 TALER_JSON_spec_full_payto_uri ( 492 "receiver_account_details", 493 &receiver_uri), 494 GNUNET_JSON_spec_fixed_auto ("wtid", 495 &rh->details.close_details.wtid), 496 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 497 &rh->details.close_details.exchange_sig), 498 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 499 &rh->details.close_details.exchange_pub), 500 TALER_JSON_spec_amount_any ("closing_fee", 501 &rh->details.close_details.fee), 502 GNUNET_JSON_spec_timestamp ("timestamp", 503 &rh->details.close_details.timestamp), 504 GNUNET_JSON_spec_end () 505 }; 506 507 rh->type = TALER_EXCHANGE_RTT_CLOSING; 508 if (GNUNET_OK != 509 GNUNET_JSON_parse (transaction, 510 closing_spec, 511 NULL, NULL)) 512 { 513 GNUNET_break_op (0); 514 return GNUNET_SYSERR; 515 } 516 key_state = uc->keys; 517 if (GNUNET_OK != 518 TALER_EXCHANGE_test_signing_key ( 519 key_state, 520 &rh->details.close_details.exchange_pub)) 521 { 522 GNUNET_break_op (0); 523 return GNUNET_SYSERR; 524 } 525 if (GNUNET_OK != 526 TALER_exchange_online_reserve_closed_verify ( 527 rh->details.close_details.timestamp, 528 &rh->amount, 529 &rh->details.close_details.fee, 530 receiver_uri, 531 &rh->details.close_details.wtid, 532 uc->reserve_pub, 533 &rh->details.close_details.exchange_pub, 534 &rh->details.close_details.exchange_sig)) 535 { 536 GNUNET_break_op (0); 537 return GNUNET_SYSERR; 538 } 539 if (0 > 540 TALER_amount_add (uc->total_out, 541 uc->total_out, 542 &rh->amount)) 543 { 544 /* overflow in history already!? inconceivable! Bad exchange! */ 545 GNUNET_break_op (0); 546 return GNUNET_SYSERR; 547 } 548 rh->details.close_details.receiver_account_details.full_payto 549 = GNUNET_strdup (receiver_uri.full_payto); 550 return GNUNET_OK; 551 } 552 553 554 /** 555 * Parse "merge" reserve history entry. 556 * 557 * @param[in,out] rh entry to parse 558 * @param uc our context 559 * @param transaction the transaction to parse 560 * @return #GNUNET_OK on success 561 */ 562 static enum GNUNET_GenericReturnValue 563 parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 564 struct HistoryParseContext *uc, 565 const json_t *transaction) 566 { 567 uint32_t flags32; 568 struct GNUNET_JSON_Specification merge_spec[] = { 569 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 570 &rh->details.merge_details.h_contract_terms), 571 GNUNET_JSON_spec_fixed_auto ("merge_pub", 572 &rh->details.merge_details.merge_pub), 573 GNUNET_JSON_spec_fixed_auto ("purse_pub", 574 &rh->details.merge_details.purse_pub), 575 GNUNET_JSON_spec_uint32 ("min_age", 576 &rh->details.merge_details.min_age), 577 GNUNET_JSON_spec_uint32 ("flags", 578 &flags32), 579 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 580 &rh->details.merge_details.reserve_sig), 581 TALER_JSON_spec_amount_any ("purse_fee", 582 &rh->details.merge_details.purse_fee), 583 GNUNET_JSON_spec_timestamp ("merge_timestamp", 584 &rh->details.merge_details.merge_timestamp), 585 GNUNET_JSON_spec_timestamp ("purse_expiration", 586 &rh->details.merge_details.purse_expiration), 587 GNUNET_JSON_spec_bool ("merged", 588 &rh->details.merge_details.merged), 589 GNUNET_JSON_spec_end () 590 }; 591 592 rh->type = TALER_EXCHANGE_RTT_MERGE; 593 if (GNUNET_OK != 594 GNUNET_JSON_parse (transaction, 595 merge_spec, 596 NULL, NULL)) 597 { 598 GNUNET_break_op (0); 599 return GNUNET_SYSERR; 600 } 601 rh->details.merge_details.flags = 602 (enum TALER_WalletAccountMergeFlags) flags32; 603 if (GNUNET_OK != 604 TALER_wallet_account_merge_verify ( 605 rh->details.merge_details.merge_timestamp, 606 &rh->details.merge_details.purse_pub, 607 rh->details.merge_details.purse_expiration, 608 &rh->details.merge_details.h_contract_terms, 609 &rh->amount, 610 &rh->details.merge_details.purse_fee, 611 rh->details.merge_details.min_age, 612 rh->details.merge_details.flags, 613 uc->reserve_pub, 614 &rh->details.merge_details.reserve_sig)) 615 { 616 GNUNET_break_op (0); 617 return GNUNET_SYSERR; 618 } 619 if (rh->details.merge_details.merged) 620 { 621 if (0 > 622 TALER_amount_add (uc->total_in, 623 uc->total_in, 624 &rh->amount)) 625 { 626 /* overflow in history already!? inconceivable! Bad exchange! */ 627 GNUNET_break_op (0); 628 return GNUNET_SYSERR; 629 } 630 } 631 else 632 { 633 if (0 > 634 TALER_amount_add (uc->total_out, 635 uc->total_out, 636 &rh->details.merge_details.purse_fee)) 637 { 638 /* overflow in history already!? inconceivable! Bad exchange! */ 639 GNUNET_break_op (0); 640 return GNUNET_SYSERR; 641 } 642 } 643 return GNUNET_OK; 644 } 645 646 647 /** 648 * Parse "open" reserve open entry. 649 * 650 * @param[in,out] rh entry to parse 651 * @param uc our context 652 * @param transaction the transaction to parse 653 * @return #GNUNET_OK on success 654 */ 655 static enum GNUNET_GenericReturnValue 656 parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 657 struct HistoryParseContext *uc, 658 const json_t *transaction) 659 { 660 struct GNUNET_JSON_Specification open_spec[] = { 661 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 662 &rh->details.open_request.reserve_sig), 663 TALER_JSON_spec_amount_any ("open_payment", 664 &rh->details.open_request.reserve_payment), 665 GNUNET_JSON_spec_uint32 ("requested_min_purses", 666 &rh->details.open_request.purse_limit), 667 GNUNET_JSON_spec_timestamp ("request_timestamp", 668 &rh->details.open_request.request_timestamp), 669 GNUNET_JSON_spec_timestamp ("requested_expiration", 670 &rh->details.open_request.reserve_expiration), 671 GNUNET_JSON_spec_end () 672 }; 673 674 rh->type = TALER_EXCHANGE_RTT_OPEN; 675 if (GNUNET_OK != 676 GNUNET_JSON_parse (transaction, 677 open_spec, 678 NULL, NULL)) 679 { 680 GNUNET_break_op (0); 681 return GNUNET_SYSERR; 682 } 683 if (GNUNET_OK != 684 TALER_wallet_reserve_open_verify ( 685 &rh->amount, 686 rh->details.open_request.request_timestamp, 687 rh->details.open_request.reserve_expiration, 688 rh->details.open_request.purse_limit, 689 uc->reserve_pub, 690 &rh->details.open_request.reserve_sig)) 691 { 692 GNUNET_break_op (0); 693 return GNUNET_SYSERR; 694 } 695 if (0 > 696 TALER_amount_add (uc->total_out, 697 uc->total_out, 698 &rh->amount)) 699 { 700 /* overflow in history already!? inconceivable! Bad exchange! */ 701 GNUNET_break_op (0); 702 return GNUNET_SYSERR; 703 } 704 return GNUNET_OK; 705 } 706 707 708 /** 709 * Parse "close" reserve close entry. 710 * 711 * @param[in,out] rh entry to parse 712 * @param uc our context 713 * @param transaction the transaction to parse 714 * @return #GNUNET_OK on success 715 */ 716 static enum GNUNET_GenericReturnValue 717 parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 718 struct HistoryParseContext *uc, 719 const json_t *transaction) 720 { 721 struct GNUNET_JSON_Specification close_spec[] = { 722 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 723 &rh->details.close_request.reserve_sig), 724 GNUNET_JSON_spec_mark_optional ( 725 GNUNET_JSON_spec_fixed_auto ("h_payto", 726 &rh->details.close_request. 727 target_account_h_payto), 728 NULL), 729 GNUNET_JSON_spec_timestamp ("request_timestamp", 730 &rh->details.close_request.request_timestamp), 731 GNUNET_JSON_spec_end () 732 }; 733 734 rh->type = TALER_EXCHANGE_RTT_CLOSE; 735 if (GNUNET_OK != 736 GNUNET_JSON_parse (transaction, 737 close_spec, 738 NULL, NULL)) 739 { 740 GNUNET_break_op (0); 741 return GNUNET_SYSERR; 742 } 743 /* force amount to invalid */ 744 memset (&rh->amount, 745 0, 746 sizeof (rh->amount)); 747 if (GNUNET_OK != 748 TALER_wallet_reserve_close_verify ( 749 rh->details.close_request.request_timestamp, 750 &rh->details.close_request.target_account_h_payto, 751 uc->reserve_pub, 752 &rh->details.close_request.reserve_sig)) 753 { 754 GNUNET_break_op (0); 755 return GNUNET_SYSERR; 756 } 757 return GNUNET_OK; 758 } 759 760 761 static void 762 free_reserve_history ( 763 unsigned int len, 764 struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len]) 765 { 766 for (unsigned int i = 0; i<len; i++) 767 { 768 struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i]; 769 770 switch (rhi->type) 771 { 772 case TALER_EXCHANGE_RTT_CREDIT: 773 GNUNET_free (rhi->details.in_details.sender_url.full_payto); 774 break; 775 case TALER_EXCHANGE_RTT_WITHDRAWAL: 776 break; 777 case TALER_EXCHANGE_RTT_RECOUP: 778 break; 779 case TALER_EXCHANGE_RTT_CLOSING: 780 break; 781 case TALER_EXCHANGE_RTT_MERGE: 782 break; 783 case TALER_EXCHANGE_RTT_OPEN: 784 break; 785 case TALER_EXCHANGE_RTT_CLOSE: 786 GNUNET_free (rhi->details.close_details 787 .receiver_account_details.full_payto); 788 break; 789 } 790 } 791 GNUNET_free (rhistory); 792 } 793 794 795 /** 796 * Parse history given in JSON format and return it in binary 797 * format. 798 * 799 * @param keys exchange keys 800 * @param history JSON array with the history 801 * @param reserve_pub public key of the reserve to inspect 802 * @param currency currency we expect the balance to be in 803 * @param[out] total_in set to value of credits to reserve 804 * @param[out] total_out set to value of debits from reserve 805 * @param history_length number of entries in @a history 806 * @param[out] rhistory array of length @a history_length, set to the 807 * parsed history entries 808 * @return #GNUNET_OK if history was valid and @a rhistory and @a balance 809 * were set, 810 * #GNUNET_SYSERR if there was a protocol violation in @a history 811 */ 812 static enum GNUNET_GenericReturnValue 813 parse_reserve_history ( 814 const struct TALER_EXCHANGE_Keys *keys, 815 const json_t *history, 816 const struct TALER_ReservePublicKeyP *reserve_pub, 817 const char *currency, 818 struct TALER_Amount *total_in, 819 struct TALER_Amount *total_out, 820 unsigned int history_length, 821 struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length]) 822 { 823 const struct 824 { 825 const char *type; 826 ParseHelper helper; 827 } map[] = { 828 { "CREDIT", &parse_credit }, 829 { "WITHDRAW", &parse_withdraw }, 830 { "RECOUP", &parse_recoup }, 831 { "MERGE", &parse_merge }, 832 { "CLOSING", &parse_closing }, 833 { "OPEN", &parse_open }, 834 { "CLOSE", &parse_close }, 835 { NULL, NULL } 836 }; 837 struct GNUNET_HashCode uuid[history_length]; 838 struct HistoryParseContext uc = { 839 .keys = keys, 840 .reserve_pub = reserve_pub, 841 .uuids = uuid, 842 .total_in = total_in, 843 .total_out = total_out 844 }; 845 846 GNUNET_assert (GNUNET_OK == 847 TALER_amount_set_zero (currency, 848 total_in)); 849 GNUNET_assert (GNUNET_OK == 850 TALER_amount_set_zero (currency, 851 total_out)); 852 for (unsigned int off = 0; off<history_length; off++) 853 { 854 struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off]; 855 json_t *transaction; 856 struct TALER_Amount amount; 857 const char *type; 858 struct GNUNET_JSON_Specification hist_spec[] = { 859 GNUNET_JSON_spec_string ("type", 860 &type), 861 TALER_JSON_spec_amount_any ("amount", 862 &amount), 863 /* 'wire' and 'signature' are optional depending on 'type'! */ 864 GNUNET_JSON_spec_end () 865 }; 866 bool found = false; 867 868 transaction = json_array_get (history, 869 off); 870 if (GNUNET_OK != 871 GNUNET_JSON_parse (transaction, 872 hist_spec, 873 NULL, NULL)) 874 { 875 GNUNET_break_op (0); 876 json_dumpf (transaction, 877 stderr, 878 JSON_INDENT (2)); 879 return GNUNET_SYSERR; 880 } 881 rh->amount = amount; 882 if (GNUNET_YES != 883 TALER_amount_cmp_currency (&amount, 884 total_in)) 885 { 886 GNUNET_break_op (0); 887 return GNUNET_SYSERR; 888 } 889 for (unsigned int i = 0; NULL != map[i].type; i++) 890 { 891 if (0 == strcasecmp (map[i].type, 892 type)) 893 { 894 found = true; 895 if (GNUNET_OK != 896 map[i].helper (rh, 897 &uc, 898 transaction)) 899 { 900 GNUNET_break_op (0); 901 return GNUNET_SYSERR; 902 } 903 break; 904 } 905 } 906 if (! found) 907 { 908 /* unexpected 'type', protocol incompatibility, complain! */ 909 GNUNET_break_op (0); 910 return GNUNET_SYSERR; 911 } 912 } 913 return GNUNET_OK; 914 } 915 916 917 /** 918 * Handle HTTP header received by curl. 919 * 920 * @param buffer one line of HTTP header data 921 * @param size size of an item 922 * @param nitems number of items passed 923 * @param userdata our `struct TALER_EXCHANGE_ReservesHistoryHandle *` 924 * @return `size * nitems` 925 */ 926 static size_t 927 handle_header (char *buffer, 928 size_t size, 929 size_t nitems, 930 void *userdata) 931 { 932 struct TALER_EXCHANGE_ReservesHistoryHandle *rhh = userdata; 933 size_t total = size * nitems; 934 char *ndup; 935 const char *hdr_type; 936 char *hdr_val; 937 char *sp; 938 939 ndup = GNUNET_strndup (buffer, 940 total); 941 hdr_type = strtok_r (ndup, 942 ":", 943 &sp); 944 if (NULL == hdr_type) 945 { 946 GNUNET_free (ndup); 947 return total; 948 } 949 hdr_val = strtok_r (NULL, 950 "\n\r", 951 &sp); 952 if (NULL == hdr_val) 953 { 954 GNUNET_free (ndup); 955 return total; 956 } 957 if (' ' == *hdr_val) 958 hdr_val++; 959 if (0 == strcasecmp (hdr_type, 960 MHD_HTTP_HEADER_ETAG)) 961 { 962 unsigned long long tval; 963 char dummy; 964 965 if (1 != 966 sscanf (hdr_val, 967 "\"%llu\"%c", 968 &tval, 969 &dummy)) 970 { 971 GNUNET_break_op (0); 972 GNUNET_free (ndup); 973 return 0; 974 } 975 rhh->etag = (uint64_t) tval; 976 } 977 GNUNET_free (ndup); 978 return total; 979 } 980 981 982 /** 983 * We received an #MHD_HTTP_OK history code. Handle the JSON 984 * response. 985 * 986 * @param rsh handle of the request 987 * @param j JSON response 988 * @return #GNUNET_OK on success 989 */ 990 static enum GNUNET_GenericReturnValue 991 handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh, 992 const json_t *j) 993 { 994 const json_t *history; 995 unsigned int len; 996 struct TALER_EXCHANGE_ReserveHistory rs = { 997 .hr.reply = j, 998 .hr.http_status = MHD_HTTP_OK, 999 .details.ok.etag = rsh->etag 1000 }; 1001 struct GNUNET_JSON_Specification spec[] = { 1002 TALER_JSON_spec_amount_any ("balance", 1003 &rs.details.ok.balance), 1004 GNUNET_JSON_spec_array_const ("history", 1005 &history), 1006 GNUNET_JSON_spec_end () 1007 }; 1008 1009 if (GNUNET_OK != 1010 GNUNET_JSON_parse (j, 1011 spec, 1012 NULL, 1013 NULL)) 1014 { 1015 GNUNET_break_op (0); 1016 return GNUNET_SYSERR; 1017 } 1018 len = json_array_size (history); 1019 { 1020 struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory; 1021 1022 rhistory = GNUNET_new_array (len, 1023 struct TALER_EXCHANGE_ReserveHistoryEntry); 1024 if (GNUNET_OK != 1025 parse_reserve_history (rsh->keys, 1026 history, 1027 &rsh->reserve_pub, 1028 rs.details.ok.balance.currency, 1029 &rs.details.ok.total_in, 1030 &rs.details.ok.total_out, 1031 len, 1032 rhistory)) 1033 { 1034 GNUNET_break_op (0); 1035 free_reserve_history (len, 1036 rhistory); 1037 GNUNET_JSON_parse_free (spec); 1038 return GNUNET_SYSERR; 1039 } 1040 if (NULL != rsh->cb) 1041 { 1042 rs.details.ok.history = rhistory; 1043 rs.details.ok.history_len = len; 1044 rsh->cb (rsh->cb_cls, 1045 &rs); 1046 rsh->cb = NULL; 1047 } 1048 free_reserve_history (len, 1049 rhistory); 1050 } 1051 return GNUNET_OK; 1052 } 1053 1054 1055 /** 1056 * Function called when we're done processing the 1057 * HTTP /reserves/$RID/history request. 1058 * 1059 * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle` 1060 * @param response_code HTTP response code, 0 on error 1061 * @param response parsed JSON result, NULL on error 1062 */ 1063 static void 1064 handle_reserves_history_finished (void *cls, 1065 long response_code, 1066 const void *response) 1067 { 1068 struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls; 1069 const json_t *j = response; 1070 struct TALER_EXCHANGE_ReserveHistory rs = { 1071 .hr.reply = j, 1072 .hr.http_status = (unsigned int) response_code 1073 }; 1074 1075 rsh->job = NULL; 1076 switch (response_code) 1077 { 1078 case 0: 1079 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 1080 break; 1081 case MHD_HTTP_OK: 1082 if (GNUNET_OK != 1083 handle_reserves_history_ok (rsh, 1084 j)) 1085 { 1086 rs.hr.http_status = 0; 1087 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 1088 } 1089 break; 1090 case MHD_HTTP_BAD_REQUEST: 1091 /* This should never happen, either us or the exchange is buggy 1092 (or API version conflict); just pass JSON reply to the application */ 1093 GNUNET_break (0); 1094 rs.hr.ec = TALER_JSON_get_error_code (j); 1095 rs.hr.hint = TALER_JSON_get_error_hint (j); 1096 break; 1097 case MHD_HTTP_FORBIDDEN: 1098 /* This should never happen, either us or the exchange is buggy 1099 (or API version conflict); just pass JSON reply to the application */ 1100 GNUNET_break (0); 1101 rs.hr.ec = TALER_JSON_get_error_code (j); 1102 rs.hr.hint = TALER_JSON_get_error_hint (j); 1103 break; 1104 case MHD_HTTP_NOT_FOUND: 1105 /* Nothing really to verify, this should never 1106 happen, we should pass the JSON reply to the application */ 1107 rs.hr.ec = TALER_JSON_get_error_code (j); 1108 rs.hr.hint = TALER_JSON_get_error_hint (j); 1109 break; 1110 case MHD_HTTP_INTERNAL_SERVER_ERROR: 1111 /* Server had an internal issue; we should retry, but this API 1112 leaves this to the application */ 1113 rs.hr.ec = TALER_JSON_get_error_code (j); 1114 rs.hr.hint = TALER_JSON_get_error_hint (j); 1115 break; 1116 default: 1117 /* unexpected response code */ 1118 GNUNET_break_op (0); 1119 rs.hr.ec = TALER_JSON_get_error_code (j); 1120 rs.hr.hint = TALER_JSON_get_error_hint (j); 1121 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1122 "Unexpected response code %u/%d for reserves history\n", 1123 (unsigned int) response_code, 1124 (int) rs.hr.ec); 1125 break; 1126 } 1127 if (NULL != rsh->cb) 1128 { 1129 rsh->cb (rsh->cb_cls, 1130 &rs); 1131 rsh->cb = NULL; 1132 } 1133 TALER_EXCHANGE_reserves_history_cancel (rsh); 1134 } 1135 1136 1137 struct TALER_EXCHANGE_ReservesHistoryHandle * 1138 TALER_EXCHANGE_reserves_history ( 1139 struct GNUNET_CURL_Context *ctx, 1140 const char *url, 1141 struct TALER_EXCHANGE_Keys *keys, 1142 const struct TALER_ReservePrivateKeyP *reserve_priv, 1143 uint64_t start_off, 1144 TALER_EXCHANGE_ReservesHistoryCallback cb, 1145 void *cb_cls) 1146 { 1147 struct TALER_EXCHANGE_ReservesHistoryHandle *rsh; 1148 CURL *eh; 1149 char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64]; 1150 struct curl_slist *job_headers; 1151 1152 rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle); 1153 rsh->cb = cb; 1154 rsh->cb_cls = cb_cls; 1155 GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, 1156 &rsh->reserve_pub.eddsa_pub); 1157 { 1158 char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; 1159 char *end; 1160 1161 end = GNUNET_STRINGS_data_to_string ( 1162 &rsh->reserve_pub, 1163 sizeof (rsh->reserve_pub), 1164 pub_str, 1165 sizeof (pub_str)); 1166 *end = '\0'; 1167 if (0 != start_off) 1168 GNUNET_snprintf (arg_str, 1169 sizeof (arg_str), 1170 "reserves/%s/history?start=%llu", 1171 pub_str, 1172 (unsigned long long) start_off); 1173 else 1174 GNUNET_snprintf (arg_str, 1175 sizeof (arg_str), 1176 "reserves/%s/history", 1177 pub_str); 1178 } 1179 rsh->url = TALER_url_join (url, 1180 arg_str, 1181 NULL); 1182 if (NULL == rsh->url) 1183 { 1184 GNUNET_free (rsh); 1185 return NULL; 1186 } 1187 eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url); 1188 if (NULL == eh) 1189 { 1190 GNUNET_break (0); 1191 GNUNET_free (rsh->url); 1192 GNUNET_free (rsh); 1193 return NULL; 1194 } 1195 GNUNET_assert (CURLE_OK == 1196 curl_easy_setopt (eh, 1197 CURLOPT_HEADERFUNCTION, 1198 &handle_header)); 1199 GNUNET_assert (CURLE_OK == 1200 curl_easy_setopt (eh, 1201 CURLOPT_HEADERDATA, 1202 rsh)); 1203 { 1204 struct TALER_ReserveSignatureP reserve_sig; 1205 char *sig_hdr; 1206 char *hdr; 1207 1208 TALER_wallet_reserve_history_sign (start_off, 1209 reserve_priv, 1210 &reserve_sig); 1211 1212 sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( 1213 &reserve_sig, 1214 sizeof (reserve_sig)); 1215 GNUNET_asprintf (&hdr, 1216 "%s: %s", 1217 TALER_RESERVE_HISTORY_SIGNATURE_HEADER, 1218 sig_hdr); 1219 GNUNET_free (sig_hdr); 1220 job_headers = curl_slist_append (NULL, 1221 hdr); 1222 GNUNET_free (hdr); 1223 if (NULL == job_headers) 1224 { 1225 GNUNET_break (0); 1226 curl_easy_cleanup (eh); 1227 return NULL; 1228 } 1229 } 1230 1231 rsh->keys = TALER_EXCHANGE_keys_incref (keys); 1232 rsh->job = GNUNET_CURL_job_add2 (ctx, 1233 eh, 1234 job_headers, 1235 &handle_reserves_history_finished, 1236 rsh); 1237 curl_slist_free_all (job_headers); 1238 return rsh; 1239 } 1240 1241 1242 void 1243 TALER_EXCHANGE_reserves_history_cancel ( 1244 struct TALER_EXCHANGE_ReservesHistoryHandle *rsh) 1245 { 1246 if (NULL != rsh->job) 1247 { 1248 GNUNET_CURL_job_cancel (rsh->job); 1249 rsh->job = NULL; 1250 } 1251 TALER_curl_easy_post_finished (&rsh->post_ctx); 1252 GNUNET_free (rsh->url); 1253 TALER_EXCHANGE_keys_decref (rsh->keys); 1254 GNUNET_free (rsh); 1255 } 1256 1257 1258 /* end of exchange_api_reserves_history.c */