exchange_api_get-reserves-RESERVE_PUB-history.c (38966B)
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 GNUNET_assert (0 <= 364 TALER_amount_add (&fee_acc, 365 &fee_acc, 366 &dki->fees.withdraw)); 367 GNUNET_assert (0 <= 368 TALER_amount_add (&amount_acc, 369 &amount_acc, 370 &dki->value)); 371 } 372 373 if ( (GNUNET_YES != 374 TALER_amount_cmp_currency (&fee_acc, 375 &withdraw_fee)) || 376 (0 != 377 TALER_amount_cmp (&amount_acc, 378 &amount_without_fee)) ) 379 { 380 GNUNET_break_op (0); 381 GNUNET_JSON_parse_free (withdraw_spec); 382 return GNUNET_SYSERR; 383 } 384 rh->details.withdraw.fee = withdraw_fee; 385 } 386 387 /* Check check that the same withdraw transaction 388 isn't listed twice by the exchange. We use the 389 "uuid" array to remember the hashes of all 390 signatures, and compare the hashes to find 391 duplicates. */ 392 GNUNET_CRYPTO_hash (&reserve_sig, 393 sizeof (reserve_sig), 394 &uc->uuids[uc->uuid_off]); 395 for (unsigned int i = 0; i<uc->uuid_off; i++) 396 { 397 if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off], 398 &uc->uuids[i])) 399 { 400 GNUNET_break_op (0); 401 GNUNET_JSON_parse_free (withdraw_spec); 402 return GNUNET_SYSERR; 403 } 404 } 405 uc->uuid_off++; 406 407 if (0 > 408 TALER_amount_add (uc->total_out, 409 uc->total_out, 410 &rh->amount)) 411 { 412 /* overflow in history already!? inconceivable! Bad exchange! */ 413 GNUNET_break_op (0); 414 GNUNET_JSON_parse_free (withdraw_spec); 415 return GNUNET_SYSERR; 416 } 417 GNUNET_JSON_parse_free (withdraw_spec); 418 return GNUNET_OK; 419 } 420 421 422 /** 423 * Parse "recoup" reserve history entry. 424 * 425 * @param[in,out] rh entry to parse 426 * @param uc our context 427 * @param transaction the transaction to parse 428 * @return #GNUNET_OK on success 429 */ 430 static enum GNUNET_GenericReturnValue 431 parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 432 struct HistoryParseContext *uc, 433 const json_t *transaction) 434 { 435 const struct TALER_EXCHANGE_Keys *key_state; 436 struct GNUNET_JSON_Specification recoup_spec[] = { 437 GNUNET_JSON_spec_fixed_auto ("coin_pub", 438 &rh->details.recoup_details.coin_pub), 439 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 440 &rh->details.recoup_details.exchange_sig), 441 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 442 &rh->details.recoup_details.exchange_pub), 443 GNUNET_JSON_spec_timestamp ("timestamp", 444 &rh->details.recoup_details.timestamp), 445 GNUNET_JSON_spec_end () 446 }; 447 448 rh->type = TALER_EXCHANGE_RTT_RECOUP; 449 if (GNUNET_OK != 450 GNUNET_JSON_parse (transaction, 451 recoup_spec, 452 NULL, NULL)) 453 { 454 GNUNET_break_op (0); 455 return GNUNET_SYSERR; 456 } 457 key_state = uc->keys; 458 if (GNUNET_OK != 459 TALER_EXCHANGE_test_signing_key (key_state, 460 &rh->details. 461 recoup_details.exchange_pub)) 462 { 463 GNUNET_break_op (0); 464 return GNUNET_SYSERR; 465 } 466 if (GNUNET_OK != 467 TALER_exchange_online_confirm_recoup_verify ( 468 rh->details.recoup_details.timestamp, 469 &rh->amount, 470 &rh->details.recoup_details.coin_pub, 471 uc->reserve_pub, 472 &rh->details.recoup_details.exchange_pub, 473 &rh->details.recoup_details.exchange_sig)) 474 { 475 GNUNET_break_op (0); 476 return GNUNET_SYSERR; 477 } 478 if (0 > 479 TALER_amount_add (uc->total_in, 480 uc->total_in, 481 &rh->amount)) 482 { 483 /* overflow in history already!? inconceivable! Bad exchange! */ 484 GNUNET_break_op (0); 485 return GNUNET_SYSERR; 486 } 487 return GNUNET_OK; 488 } 489 490 491 /** 492 * Parse "closing" reserve history entry. 493 * 494 * @param[in,out] rh entry to parse 495 * @param uc our context 496 * @param transaction the transaction to parse 497 * @return #GNUNET_OK on success 498 */ 499 static enum GNUNET_GenericReturnValue 500 parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 501 struct HistoryParseContext *uc, 502 const json_t *transaction) 503 { 504 const struct TALER_EXCHANGE_Keys *key_state; 505 struct TALER_FullPayto receiver_uri; 506 struct GNUNET_JSON_Specification closing_spec[] = { 507 TALER_JSON_spec_full_payto_uri ( 508 "receiver_account_details", 509 &receiver_uri), 510 GNUNET_JSON_spec_fixed_auto ("wtid", 511 &rh->details.close_details.wtid), 512 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 513 &rh->details.close_details.exchange_sig), 514 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 515 &rh->details.close_details.exchange_pub), 516 TALER_JSON_spec_amount_any ("closing_fee", 517 &rh->details.close_details.fee), 518 GNUNET_JSON_spec_timestamp ("timestamp", 519 &rh->details.close_details.timestamp), 520 GNUNET_JSON_spec_end () 521 }; 522 523 rh->type = TALER_EXCHANGE_RTT_CLOSING; 524 if (GNUNET_OK != 525 GNUNET_JSON_parse (transaction, 526 closing_spec, 527 NULL, NULL)) 528 { 529 GNUNET_break_op (0); 530 return GNUNET_SYSERR; 531 } 532 key_state = uc->keys; 533 if (GNUNET_OK != 534 TALER_EXCHANGE_test_signing_key ( 535 key_state, 536 &rh->details.close_details.exchange_pub)) 537 { 538 GNUNET_break_op (0); 539 return GNUNET_SYSERR; 540 } 541 if (GNUNET_OK != 542 TALER_exchange_online_reserve_closed_verify ( 543 rh->details.close_details.timestamp, 544 &rh->amount, 545 &rh->details.close_details.fee, 546 receiver_uri, 547 &rh->details.close_details.wtid, 548 uc->reserve_pub, 549 &rh->details.close_details.exchange_pub, 550 &rh->details.close_details.exchange_sig)) 551 { 552 GNUNET_break_op (0); 553 return GNUNET_SYSERR; 554 } 555 if (0 > 556 TALER_amount_add (uc->total_out, 557 uc->total_out, 558 &rh->amount)) 559 { 560 /* overflow in history already!? inconceivable! Bad exchange! */ 561 GNUNET_break_op (0); 562 return GNUNET_SYSERR; 563 } 564 rh->details.close_details.receiver_account_details.full_payto 565 = GNUNET_strdup (receiver_uri.full_payto); 566 return GNUNET_OK; 567 } 568 569 570 /** 571 * Parse "merge" reserve history entry. 572 * 573 * @param[in,out] rh entry to parse 574 * @param uc our context 575 * @param transaction the transaction to parse 576 * @return #GNUNET_OK on success 577 */ 578 static enum GNUNET_GenericReturnValue 579 parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 580 struct HistoryParseContext *uc, 581 const json_t *transaction) 582 { 583 uint32_t flags32; 584 struct GNUNET_JSON_Specification merge_spec[] = { 585 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 586 &rh->details.merge_details.h_contract_terms), 587 GNUNET_JSON_spec_fixed_auto ("merge_pub", 588 &rh->details.merge_details.merge_pub), 589 GNUNET_JSON_spec_fixed_auto ("purse_pub", 590 &rh->details.merge_details.purse_pub), 591 GNUNET_JSON_spec_uint32 ("min_age", 592 &rh->details.merge_details.min_age), 593 GNUNET_JSON_spec_uint32 ("flags", 594 &flags32), 595 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 596 &rh->details.merge_details.reserve_sig), 597 TALER_JSON_spec_amount_any ("purse_fee", 598 &rh->details.merge_details.purse_fee), 599 GNUNET_JSON_spec_timestamp ("merge_timestamp", 600 &rh->details.merge_details.merge_timestamp), 601 GNUNET_JSON_spec_timestamp ("purse_expiration", 602 &rh->details.merge_details.purse_expiration), 603 GNUNET_JSON_spec_bool ("merged", 604 &rh->details.merge_details.merged), 605 GNUNET_JSON_spec_end () 606 }; 607 608 rh->type = TALER_EXCHANGE_RTT_MERGE; 609 if (GNUNET_OK != 610 GNUNET_JSON_parse (transaction, 611 merge_spec, 612 NULL, NULL)) 613 { 614 GNUNET_break_op (0); 615 return GNUNET_SYSERR; 616 } 617 rh->details.merge_details.flags = 618 (enum TALER_WalletAccountMergeFlags) flags32; 619 if (GNUNET_OK != 620 TALER_wallet_account_merge_verify ( 621 rh->details.merge_details.merge_timestamp, 622 &rh->details.merge_details.purse_pub, 623 rh->details.merge_details.purse_expiration, 624 &rh->details.merge_details.h_contract_terms, 625 &rh->amount, 626 &rh->details.merge_details.purse_fee, 627 rh->details.merge_details.min_age, 628 rh->details.merge_details.flags, 629 uc->reserve_pub, 630 &rh->details.merge_details.reserve_sig)) 631 { 632 GNUNET_break_op (0); 633 return GNUNET_SYSERR; 634 } 635 if (rh->details.merge_details.merged) 636 { 637 if (0 > 638 TALER_amount_add (uc->total_in, 639 uc->total_in, 640 &rh->amount)) 641 { 642 /* overflow in history already!? inconceivable! Bad exchange! */ 643 GNUNET_break_op (0); 644 return GNUNET_SYSERR; 645 } 646 } 647 else 648 { 649 if (0 > 650 TALER_amount_add (uc->total_out, 651 uc->total_out, 652 &rh->details.merge_details.purse_fee)) 653 { 654 /* overflow in history already!? inconceivable! Bad exchange! */ 655 GNUNET_break_op (0); 656 return GNUNET_SYSERR; 657 } 658 } 659 return GNUNET_OK; 660 } 661 662 663 /** 664 * Parse "open" reserve open entry. 665 * 666 * @param[in,out] rh entry to parse 667 * @param uc our context 668 * @param transaction the transaction to parse 669 * @return #GNUNET_OK on success 670 */ 671 static enum GNUNET_GenericReturnValue 672 parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 673 struct HistoryParseContext *uc, 674 const json_t *transaction) 675 { 676 struct GNUNET_JSON_Specification open_spec[] = { 677 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 678 &rh->details.open_request.reserve_sig), 679 TALER_JSON_spec_amount_any ("open_fee", 680 &rh->details.open_request.reserve_payment), 681 GNUNET_JSON_spec_uint32 ("requested_min_purses", 682 &rh->details.open_request.purse_limit), 683 GNUNET_JSON_spec_timestamp ("request_timestamp", 684 &rh->details.open_request.request_timestamp), 685 GNUNET_JSON_spec_timestamp ("requested_expiration", 686 &rh->details.open_request.reserve_expiration), 687 GNUNET_JSON_spec_end () 688 }; 689 690 rh->type = TALER_EXCHANGE_RTT_OPEN; 691 if (GNUNET_OK != 692 GNUNET_JSON_parse (transaction, 693 open_spec, 694 NULL, NULL)) 695 { 696 GNUNET_break_op (0); 697 return GNUNET_SYSERR; 698 } 699 if (GNUNET_OK != 700 TALER_wallet_reserve_open_verify ( 701 &rh->amount, 702 rh->details.open_request.request_timestamp, 703 rh->details.open_request.reserve_expiration, 704 rh->details.open_request.purse_limit, 705 uc->reserve_pub, 706 &rh->details.open_request.reserve_sig)) 707 { 708 GNUNET_break_op (0); 709 return GNUNET_SYSERR; 710 } 711 if (0 > 712 TALER_amount_add (uc->total_out, 713 uc->total_out, 714 &rh->amount)) 715 { 716 /* overflow in history already!? inconceivable! Bad exchange! */ 717 GNUNET_break_op (0); 718 return GNUNET_SYSERR; 719 } 720 return GNUNET_OK; 721 } 722 723 724 /** 725 * Parse "close" reserve close entry. 726 * 727 * @param[in,out] rh entry to parse 728 * @param uc our context 729 * @param transaction the transaction to parse 730 * @return #GNUNET_OK on success 731 */ 732 static enum GNUNET_GenericReturnValue 733 parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 734 struct HistoryParseContext *uc, 735 const json_t *transaction) 736 { 737 struct GNUNET_JSON_Specification close_spec[] = { 738 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 739 &rh->details.close_request.reserve_sig), 740 GNUNET_JSON_spec_mark_optional ( 741 GNUNET_JSON_spec_fixed_auto ("h_payto", 742 &rh->details.close_request. 743 target_account_h_payto), 744 NULL), 745 GNUNET_JSON_spec_timestamp ("request_timestamp", 746 &rh->details.close_request.request_timestamp), 747 GNUNET_JSON_spec_end () 748 }; 749 750 rh->type = TALER_EXCHANGE_RTT_CLOSE; 751 if (GNUNET_OK != 752 GNUNET_JSON_parse (transaction, 753 close_spec, 754 NULL, NULL)) 755 { 756 GNUNET_break_op (0); 757 return GNUNET_SYSERR; 758 } 759 /* force amount to invalid */ 760 memset (&rh->amount, 761 0, 762 sizeof (rh->amount)); 763 if (GNUNET_OK != 764 TALER_wallet_reserve_close_verify ( 765 rh->details.close_request.request_timestamp, 766 &rh->details.close_request.target_account_h_payto, 767 uc->reserve_pub, 768 &rh->details.close_request.reserve_sig)) 769 { 770 GNUNET_break_op (0); 771 return GNUNET_SYSERR; 772 } 773 return GNUNET_OK; 774 } 775 776 777 static void 778 free_reserve_history ( 779 unsigned int len, 780 struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len]) 781 { 782 for (unsigned int i = 0; i<len; i++) 783 { 784 struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i]; 785 786 switch (rhi->type) 787 { 788 case TALER_EXCHANGE_RTT_CREDIT: 789 GNUNET_free (rhi->details.in_details.sender_url.full_payto); 790 break; 791 case TALER_EXCHANGE_RTT_WITHDRAWAL: 792 break; 793 case TALER_EXCHANGE_RTT_RECOUP: 794 break; 795 case TALER_EXCHANGE_RTT_CLOSING: 796 GNUNET_free ( 797 rhi->details.close_details.receiver_account_details.full_payto); 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 break; 805 } 806 } 807 GNUNET_free (rhistory); 808 } 809 810 811 /** 812 * Parse history given in JSON format and return it in binary format. 813 * 814 * @param keys exchange keys 815 * @param history JSON array with the history 816 * @param reserve_pub public key of the reserve to inspect 817 * @param currency currency we expect the balance to be in 818 * @param[out] total_in set to value of credits to reserve 819 * @param[out] total_out set to value of debits from reserve 820 * @param history_length number of entries in @a history 821 * @param[out] rhistory array of length @a history_length, set to the 822 * parsed history entries 823 * @return #GNUNET_OK if history was valid and @a rhistory and @a balance 824 * were set, 825 * #GNUNET_SYSERR if there was a protocol violation in @a history 826 */ 827 static enum GNUNET_GenericReturnValue 828 parse_reserve_history ( 829 const struct TALER_EXCHANGE_Keys *keys, 830 const json_t *history, 831 const struct TALER_ReservePublicKeyP *reserve_pub, 832 const char *currency, 833 struct TALER_Amount *total_in, 834 struct TALER_Amount *total_out, 835 unsigned int history_length, 836 struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length]) 837 { 838 const struct 839 { 840 const char *type; 841 ParseHelper helper; 842 } map[] = { 843 { "CREDIT", &parse_credit }, 844 { "WITHDRAW", &parse_withdraw }, 845 { "RECOUP", &parse_recoup }, 846 { "MERGE", &parse_merge }, 847 { "CLOSING", &parse_closing }, 848 { "OPEN", &parse_open }, 849 { "CLOSE", &parse_close }, 850 { NULL, NULL } 851 }; 852 struct GNUNET_HashCode *uuid 853 = GNUNET_new_array (history_length, 854 struct GNUNET_HashCode); 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 #if DEBUG 895 json_dumpf (transaction, 896 stderr, 897 JSON_INDENT (2)); 898 #endif 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 */