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