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