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