exchange_api_get-reserves-RESERVE_PUB-history.c (38885B)
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 break; 799 case TALER_EXCHANGE_RTT_MERGE: 800 break; 801 case TALER_EXCHANGE_RTT_OPEN: 802 break; 803 case TALER_EXCHANGE_RTT_CLOSE: 804 GNUNET_free (rhi->details.close_details 805 .receiver_account_details.full_payto); 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[history_length]; 855 struct HistoryParseContext uc = { 856 .keys = keys, 857 .reserve_pub = reserve_pub, 858 .uuids = uuid, 859 .total_in = total_in, 860 .total_out = total_out 861 }; 862 863 GNUNET_assert (GNUNET_OK == 864 TALER_amount_set_zero (currency, 865 total_in)); 866 GNUNET_assert (GNUNET_OK == 867 TALER_amount_set_zero (currency, 868 total_out)); 869 for (unsigned int off = 0; off<history_length; off++) 870 { 871 struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off]; 872 json_t *transaction; 873 const char *type; 874 struct GNUNET_JSON_Specification hist_spec[] = { 875 GNUNET_JSON_spec_string ("type", 876 &type), 877 TALER_JSON_spec_amount_any ("amount", 878 &rh->amount), 879 GNUNET_JSON_spec_uint64 ("history_offset", 880 &rh->history_offset), 881 /* 'wire' and 'signature' are optional depending on 'type'! */ 882 GNUNET_JSON_spec_end () 883 }; 884 bool found = false; 885 886 transaction = json_array_get (history, 887 off); 888 if (GNUNET_OK != 889 GNUNET_JSON_parse (transaction, 890 hist_spec, 891 NULL, NULL)) 892 { 893 GNUNET_break_op (0); 894 json_dumpf (transaction, 895 stderr, 896 JSON_INDENT (2)); 897 return GNUNET_SYSERR; 898 } 899 if (GNUNET_YES != 900 TALER_amount_cmp_currency (&rh->amount, 901 total_in)) 902 { 903 GNUNET_break_op (0); 904 return GNUNET_SYSERR; 905 } 906 for (unsigned int i = 0; NULL != map[i].type; i++) 907 { 908 if (0 == strcasecmp (map[i].type, 909 type)) 910 { 911 found = true; 912 if (GNUNET_OK != 913 map[i].helper (rh, 914 &uc, 915 transaction)) 916 { 917 GNUNET_break_op (0); 918 return GNUNET_SYSERR; 919 } 920 break; 921 } 922 } 923 if (! found) 924 { 925 /* unexpected 'type', protocol incompatibility, complain! */ 926 GNUNET_break_op (0); 927 return GNUNET_SYSERR; 928 } 929 } 930 return GNUNET_OK; 931 } 932 933 934 /** 935 * Handle HTTP header received by curl. 936 * 937 * @param buffer one line of HTTP header data 938 * @param size size of an item 939 * @param nitems number of items passed 940 * @param userdata our `struct TALER_EXCHANGE_GetReservesHistoryHandle *` 941 * @return `size * nitems` 942 */ 943 static size_t 944 handle_header (char *buffer, 945 size_t size, 946 size_t nitems, 947 void *userdata) 948 { 949 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = userdata; 950 size_t total = size * nitems; 951 char *ndup; 952 const char *hdr_type; 953 char *hdr_val; 954 char *sp; 955 956 ndup = GNUNET_strndup (buffer, 957 total); 958 hdr_type = strtok_r (ndup, 959 ":", 960 &sp); 961 if (NULL == hdr_type) 962 { 963 GNUNET_free (ndup); 964 return total; 965 } 966 hdr_val = strtok_r (NULL, 967 "\n\r", 968 &sp); 969 if (NULL == hdr_val) 970 { 971 GNUNET_free (ndup); 972 return total; 973 } 974 if (' ' == *hdr_val) 975 hdr_val++; 976 if (0 == strcasecmp (hdr_type, 977 MHD_HTTP_HEADER_ETAG)) 978 { 979 unsigned long long tval; 980 char dummy; 981 982 if (1 != 983 sscanf (hdr_val, 984 "\"%llu\"%c", 985 &tval, 986 &dummy)) 987 { 988 GNUNET_break_op (0); 989 GNUNET_free (ndup); 990 return 0; 991 } 992 grhh->etag = (uint64_t) tval; 993 } 994 GNUNET_free (ndup); 995 return total; 996 } 997 998 999 /** 1000 * We received an #MHD_HTTP_OK status code. Handle the JSON response. 1001 * 1002 * @param grhh handle of the request 1003 * @param j JSON response 1004 * @return #GNUNET_OK on success 1005 */ 1006 static enum GNUNET_GenericReturnValue 1007 handle_reserves_history_ok ( 1008 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh, 1009 const json_t *j) 1010 { 1011 const json_t *history; 1012 unsigned int len; 1013 struct TALER_EXCHANGE_GetReservesHistoryResponse rs = { 1014 .hr.reply = j, 1015 .hr.http_status = MHD_HTTP_OK, 1016 .details.ok.etag = grhh->etag 1017 }; 1018 struct GNUNET_JSON_Specification spec[] = { 1019 TALER_JSON_spec_amount_any ("balance", 1020 &rs.details.ok.balance), 1021 GNUNET_JSON_spec_array_const ("history", 1022 &history), 1023 GNUNET_JSON_spec_end () 1024 }; 1025 1026 if (GNUNET_OK != 1027 GNUNET_JSON_parse (j, 1028 spec, 1029 NULL, 1030 NULL)) 1031 { 1032 GNUNET_break_op (0); 1033 return GNUNET_SYSERR; 1034 } 1035 len = json_array_size (history); 1036 { 1037 struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory; 1038 1039 rhistory = GNUNET_new_array (len, 1040 struct TALER_EXCHANGE_ReserveHistoryEntry); 1041 if (GNUNET_OK != 1042 parse_reserve_history (grhh->keys, 1043 history, 1044 &grhh->reserve_pub, 1045 rs.details.ok.balance.currency, 1046 &rs.details.ok.total_in, 1047 &rs.details.ok.total_out, 1048 len, 1049 rhistory)) 1050 { 1051 GNUNET_break_op (0); 1052 free_reserve_history (len, 1053 rhistory); 1054 GNUNET_JSON_parse_free (spec); 1055 return GNUNET_SYSERR; 1056 } 1057 if (NULL != grhh->cb) 1058 { 1059 rs.details.ok.history = rhistory; 1060 rs.details.ok.history_len = len; 1061 grhh->cb (grhh->cb_cls, 1062 &rs); 1063 grhh->cb = NULL; 1064 } 1065 free_reserve_history (len, 1066 rhistory); 1067 } 1068 return GNUNET_OK; 1069 } 1070 1071 1072 /** 1073 * Function called when we're done processing the 1074 * HTTP GET /reserves/$RESERVE_PUB/history request. 1075 * 1076 * @param cls the `struct TALER_EXCHANGE_GetReservesHistoryHandle` 1077 * @param response_code HTTP response code, 0 on error 1078 * @param response parsed JSON result, NULL on error 1079 */ 1080 static void 1081 handle_reserves_history_finished (void *cls, 1082 long response_code, 1083 const void *response) 1084 { 1085 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = cls; 1086 const json_t *j = response; 1087 struct TALER_EXCHANGE_GetReservesHistoryResponse rs = { 1088 .hr.reply = j, 1089 .hr.http_status = (unsigned int) response_code 1090 }; 1091 1092 grhh->job = NULL; 1093 switch (response_code) 1094 { 1095 case 0: 1096 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 1097 break; 1098 case MHD_HTTP_OK: 1099 if (GNUNET_OK != 1100 handle_reserves_history_ok (grhh, 1101 j)) 1102 { 1103 rs.hr.http_status = 0; 1104 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 1105 } 1106 break; 1107 case MHD_HTTP_BAD_REQUEST: 1108 /* This should never happen, either us or the exchange is buggy 1109 (or API version conflict); just pass JSON reply to the application */ 1110 GNUNET_break (0); 1111 rs.hr.ec = TALER_JSON_get_error_code (j); 1112 rs.hr.hint = TALER_JSON_get_error_hint (j); 1113 break; 1114 case MHD_HTTP_FORBIDDEN: 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_NOT_FOUND: 1122 /* Nothing really to verify, this should never 1123 happen, we should pass the JSON reply to the application */ 1124 rs.hr.ec = TALER_JSON_get_error_code (j); 1125 rs.hr.hint = TALER_JSON_get_error_hint (j); 1126 break; 1127 case MHD_HTTP_INTERNAL_SERVER_ERROR: 1128 /* Server had an internal issue; we should retry, but this API 1129 leaves this to the application */ 1130 rs.hr.ec = TALER_JSON_get_error_code (j); 1131 rs.hr.hint = TALER_JSON_get_error_hint (j); 1132 break; 1133 default: 1134 /* unexpected response code */ 1135 GNUNET_break_op (0); 1136 rs.hr.ec = TALER_JSON_get_error_code (j); 1137 rs.hr.hint = TALER_JSON_get_error_hint (j); 1138 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1139 "Unexpected response code %u/%d for reserves history\n", 1140 (unsigned int) response_code, 1141 (int) rs.hr.ec); 1142 break; 1143 } 1144 if (NULL != grhh->cb) 1145 { 1146 grhh->cb (grhh->cb_cls, 1147 &rs); 1148 grhh->cb = NULL; 1149 } 1150 TALER_EXCHANGE_get_reserves_history_cancel (grhh); 1151 } 1152 1153 1154 struct TALER_EXCHANGE_GetReservesHistoryHandle * 1155 TALER_EXCHANGE_get_reserves_history_create ( 1156 struct GNUNET_CURL_Context *ctx, 1157 const char *url, 1158 struct TALER_EXCHANGE_Keys *keys, 1159 const struct TALER_ReservePrivateKeyP *reserve_priv) 1160 { 1161 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh; 1162 1163 grhh = GNUNET_new (struct TALER_EXCHANGE_GetReservesHistoryHandle); 1164 grhh->ctx = ctx; 1165 grhh->base_url = GNUNET_strdup (url); 1166 grhh->keys = TALER_EXCHANGE_keys_incref (keys); 1167 grhh->reserve_priv = *reserve_priv; 1168 GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, 1169 &grhh->reserve_pub.eddsa_pub); 1170 return grhh; 1171 } 1172 1173 1174 enum GNUNET_GenericReturnValue 1175 TALER_EXCHANGE_get_reserves_history_set_options_ ( 1176 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh, 1177 unsigned int num_options, 1178 const struct TALER_EXCHANGE_GetReservesHistoryOptionValue *options) 1179 { 1180 for (unsigned int i = 0; i < num_options; i++) 1181 { 1182 switch (options[i].option) 1183 { 1184 case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_END: 1185 return GNUNET_OK; 1186 case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_START_OFF: 1187 grhh->options.start_off = options[i].details.start_off; 1188 break; 1189 default: 1190 GNUNET_break (0); 1191 return GNUNET_SYSERR; 1192 } 1193 } 1194 return GNUNET_OK; 1195 } 1196 1197 1198 enum TALER_ErrorCode 1199 TALER_EXCHANGE_get_reserves_history_start ( 1200 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh, 1201 TALER_EXCHANGE_GetReservesHistoryCallback cb, 1202 TALER_EXCHANGE_GET_RESERVES_HISTORY_RESULT_CLOSURE *cb_cls) 1203 { 1204 char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; 1205 struct curl_slist *job_headers; 1206 CURL *eh; 1207 1208 if (NULL != grhh->job) 1209 { 1210 GNUNET_break (0); 1211 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1212 } 1213 grhh->cb = cb; 1214 grhh->cb_cls = cb_cls; 1215 { 1216 char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; 1217 char *end; 1218 char start_off_str[32]; 1219 1220 end = GNUNET_STRINGS_data_to_string ( 1221 &grhh->reserve_pub, 1222 sizeof (grhh->reserve_pub), 1223 pub_str, 1224 sizeof (pub_str)); 1225 *end = '\0'; 1226 GNUNET_snprintf (arg_str, 1227 sizeof (arg_str), 1228 "reserves/%s/history", 1229 pub_str); 1230 GNUNET_snprintf (start_off_str, 1231 sizeof (start_off_str), 1232 "%llu", 1233 (unsigned long long) grhh->options.start_off); 1234 grhh->url = TALER_url_join (grhh->base_url, 1235 arg_str, 1236 "start", 1237 (0 != grhh->options.start_off) 1238 ? start_off_str 1239 : NULL, 1240 NULL); 1241 } 1242 if (NULL == grhh->url) 1243 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 1244 eh = TALER_EXCHANGE_curl_easy_get_ (grhh->url); 1245 if (NULL == eh) 1246 { 1247 GNUNET_free (grhh->url); 1248 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 1249 } 1250 GNUNET_assert (CURLE_OK == 1251 curl_easy_setopt (eh, 1252 CURLOPT_HEADERFUNCTION, 1253 &handle_header)); 1254 GNUNET_assert (CURLE_OK == 1255 curl_easy_setopt (eh, 1256 CURLOPT_HEADERDATA, 1257 grhh)); 1258 { 1259 struct TALER_ReserveSignatureP reserve_sig; 1260 char *sig_hdr; 1261 char *hdr; 1262 1263 TALER_wallet_reserve_history_sign (grhh->options.start_off, 1264 &grhh->reserve_priv, 1265 &reserve_sig); 1266 sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( 1267 &reserve_sig, 1268 sizeof (reserve_sig)); 1269 GNUNET_asprintf (&hdr, 1270 "%s: %s", 1271 TALER_RESERVE_HISTORY_SIGNATURE_HEADER, 1272 sig_hdr); 1273 GNUNET_free (sig_hdr); 1274 job_headers = curl_slist_append (NULL, 1275 hdr); 1276 GNUNET_free (hdr); 1277 if (NULL == job_headers) 1278 { 1279 GNUNET_break (0); 1280 curl_easy_cleanup (eh); 1281 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1282 } 1283 } 1284 grhh->job = GNUNET_CURL_job_add2 (grhh->ctx, 1285 eh, 1286 job_headers, 1287 &handle_reserves_history_finished, 1288 grhh); 1289 curl_slist_free_all (job_headers); 1290 if (NULL == grhh->job) 1291 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1292 return TALER_EC_NONE; 1293 } 1294 1295 1296 void 1297 TALER_EXCHANGE_get_reserves_history_cancel ( 1298 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh) 1299 { 1300 if (NULL != grhh->job) 1301 { 1302 GNUNET_CURL_job_cancel (grhh->job); 1303 grhh->job = NULL; 1304 } 1305 GNUNET_free (grhh->url); 1306 GNUNET_free (grhh->base_url); 1307 TALER_EXCHANGE_keys_decref (grhh->keys); 1308 GNUNET_free (grhh); 1309 } 1310 1311 1312 /* end of exchange_api_get-reserves-RESERVE_PUB-history.c */