testing_api_cmd_age_withdraw.c (23795B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023-2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it 6 under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file testing/testing_api_cmd_age_withdraw.c 21 * @brief implements the withdraw command for age-restricted coins 22 * @author Özgür Kesim 23 */ 24 25 #include "taler/platform.h" 26 #include "taler/taler_exchange_service.h" 27 #include "taler/taler_json_lib.h" 28 #include <gnunet/gnunet_common.h> 29 #include <microhttpd.h> 30 #include <gnunet/gnunet_curl_lib.h> 31 #include "taler/taler_signatures.h" 32 #include "taler/taler_extensions.h" 33 #include "taler/taler_testing_lib.h" 34 35 /* 36 * The output state of coin 37 */ 38 struct CoinOutputState 39 { 40 41 /** 42 * The calculated details during "withdraw", for the selected coin. 43 */ 44 struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details; 45 46 /** 47 * The (wanted) value of the coin, MUST be the same as input.denom_pub.value; 48 */ 49 struct TALER_Amount amount; 50 51 }; 52 53 /** 54 * State for a "age withdraw" CMD: 55 */ 56 57 struct AgeWithdrawState 58 { 59 60 /** 61 * Interpreter state (during command) 62 */ 63 struct TALER_TESTING_Interpreter *is; 64 65 /** 66 * The age-withdraw handle 67 */ 68 struct TALER_EXCHANGE_WithdrawHandle *handle; 69 70 /** 71 * Exchange base URL. Only used as offered trait. 72 */ 73 char *exchange_url; 74 75 /** 76 * URI of the reserve we are withdrawing from. 77 */ 78 struct TALER_NormalizedPayto reserve_payto_uri; 79 80 /** 81 * Private key of the reserve we are withdrawing from. 82 */ 83 struct TALER_ReservePrivateKeyP reserve_priv; 84 85 /** 86 * Public key of the reserve we are withdrawing from. 87 */ 88 struct TALER_ReservePublicKeyP reserve_pub; 89 90 /** 91 * Which reserve should we withdraw from? 92 */ 93 const char *reserve_reference; 94 95 /** 96 * Expected HTTP response code to the request. 97 */ 98 unsigned int expected_response_code; 99 100 /** 101 * Age mask 102 */ 103 struct TALER_AgeMask mask; 104 105 /** 106 * The maximum age we commit to 107 */ 108 uint8_t max_age; 109 110 /** 111 * Number of coins to withdraw 112 */ 113 size_t num_coins; 114 115 /** 116 * The @e num_coins denomination public keys that are provided 117 * to the `TALER_EXCHANGE_withdraw_with_age_proof` API. 118 */ 119 struct TALER_EXCHANGE_DenomPublicKey *denoms_pub; 120 121 122 /** 123 * The master seed from which all the other seeds are derived from 124 */ 125 struct TALER_WithdrawMasterSeedP seed; 126 127 /** 128 * The #TALER_CNC_KAPPA seeds derived from @e seed 129 */ 130 struct TALER_KappaWithdrawMasterSeedP kappa_seed; 131 132 /** 133 * The master seed from which all the other seeds are derived from 134 */ 135 struct TALER_BlindingMasterSeedP blinding_seed; 136 137 /** 138 * The output state of @e num_coins coins, calculated during the 139 * "age-withdraw" operation. 140 */ 141 struct CoinOutputState *coin_outputs; 142 143 /** 144 * The index returned by the exchange for the "age-withdraw" operation, 145 * of the kappa coin candidates that we do not disclose and keep. 146 */ 147 uint8_t noreveal_index; 148 149 /** 150 * The hash of the commitment, needed for the reveal step. 151 */ 152 struct TALER_HashBlindedPlanchetsP planchets_h; 153 154 /** 155 * The hash of the selected blinded planchets 156 */ 157 struct TALER_HashBlindedPlanchetsP selected_h; 158 159 /** 160 * Set to the KYC requirement payto hash *if* the exchange replied with a 161 * request for KYC. 162 */ 163 struct TALER_NormalizedPaytoHashP h_payto; 164 165 /** 166 * Set to the KYC requirement row *if* the exchange replied with 167 * a request for KYC. 168 */ 169 uint64_t requirement_row; 170 171 /** 172 * Reserve history entry that corresponds to this withdraw. 173 * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL. 174 */ 175 struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history; 176 }; 177 178 /** 179 * Callback for the "age-withdraw" operation; It checks that the response 180 * code is expected and store the exchange signature in the state. 181 * 182 * @param cls Closure of type `struct AgeWithdrawState *` 183 * @param response Response details 184 */ 185 static void 186 age_withdraw_cb ( 187 void *cls, 188 const struct TALER_EXCHANGE_WithdrawResponse *response) 189 { 190 struct AgeWithdrawState *aws = cls; 191 struct TALER_TESTING_Interpreter *is = aws->is; 192 193 aws->handle = NULL; 194 if (aws->expected_response_code != response->hr.http_status) 195 { 196 TALER_TESTING_unexpected_status_with_body (is, 197 response->hr.http_status, 198 aws->expected_response_code, 199 response->hr.reply); 200 return; 201 } 202 203 switch (response->hr.http_status) 204 { 205 case MHD_HTTP_CREATED: 206 aws->noreveal_index = response->details.created.noreveal_index; 207 aws->planchets_h = response->details.created.planchets_h; 208 aws->selected_h = response->details.created.selected_h; 209 aws->reserve_history.details.withdraw.planchets_h = aws->planchets_h; 210 aws->reserve_history.details.withdraw.selected_h = aws->selected_h; 211 aws->reserve_history.details.withdraw.noreveal_index = aws->noreveal_index; 212 aws->kappa_seed = response->details.created.kappa_seed; 213 214 GNUNET_assert (aws->num_coins == response->details.created.num_coins); 215 for (size_t n = 0; n < aws->num_coins; n++) 216 { 217 aws->coin_outputs[n].details = response->details.created.coin_details[n]; 218 TALER_age_commitment_proof_deep_copy ( 219 &aws->coin_outputs[n].details.age_commitment_proof, 220 &response->details.created.coin_details[n].age_commitment_proof); 221 TALER_denom_ewv_copy ( 222 &aws->coin_outputs[n].details.blinding_values, 223 &response->details.created.coin_details[n].blinding_values); 224 } 225 break; 226 case MHD_HTTP_FORBIDDEN: 227 case MHD_HTTP_NOT_FOUND: 228 case MHD_HTTP_GONE: 229 /* nothing to check */ 230 break; 231 case MHD_HTTP_CONFLICT: 232 /* FIXME[oec]: Add this to the response-type and handle it here */ 233 break; 234 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 235 default: 236 /* Unsupported status code (by test harness) */ 237 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 238 "test command for age-withdraw not support status code %u, body:\n" 239 ">>%s<<\n", 240 response->hr.http_status, 241 json_dumps (response->hr.reply, JSON_INDENT (2))); 242 GNUNET_break (0); 243 break; 244 } 245 246 /* We are done with this command, pick the next one */ 247 TALER_TESTING_interpreter_next (is); 248 } 249 250 251 /** 252 * Run the command for age-withdraw. 253 */ 254 static void 255 age_withdraw_run ( 256 void *cls, 257 const struct TALER_TESTING_Command *cmd, 258 struct TALER_TESTING_Interpreter *is) 259 { 260 struct AgeWithdrawState *aws = cls; 261 struct TALER_EXCHANGE_Keys *keys = TALER_TESTING_get_keys (is); 262 const struct TALER_ReservePrivateKeyP *rp; 263 const struct TALER_TESTING_Command *create_reserve; 264 const struct TALER_EXCHANGE_DenomPublicKey *dpk; 265 266 aws->is = is; 267 268 /* Prepare the reserve related data */ 269 create_reserve 270 = TALER_TESTING_interpreter_lookup_command ( 271 is, 272 aws->reserve_reference); 273 274 if (NULL == create_reserve) 275 { 276 GNUNET_break (0); 277 TALER_TESTING_interpreter_fail (is); 278 return; 279 } 280 if (GNUNET_OK != 281 TALER_TESTING_get_trait_reserve_priv (create_reserve, 282 &rp)) 283 { 284 GNUNET_break (0); 285 TALER_TESTING_interpreter_fail (is); 286 return; 287 } 288 if (NULL == aws->exchange_url) 289 aws->exchange_url 290 = GNUNET_strdup (TALER_TESTING_get_exchange_url (is)); 291 aws->reserve_priv = *rp; 292 GNUNET_CRYPTO_eddsa_key_get_public (&aws->reserve_priv.eddsa_priv, 293 &aws->reserve_pub.eddsa_pub); 294 aws->reserve_payto_uri 295 = TALER_reserve_make_payto (aws->exchange_url, 296 &aws->reserve_pub); 297 298 aws->denoms_pub = GNUNET_new_array (aws->num_coins, 299 struct TALER_EXCHANGE_DenomPublicKey); 300 301 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, 302 &aws->seed, 303 sizeof(aws->seed)); 304 305 for (unsigned int i = 0; i<aws->num_coins; i++) 306 { 307 struct TALER_EXCHANGE_DenomPublicKey *denom_pub = &aws->denoms_pub[i]; 308 struct CoinOutputState *cos = &aws->coin_outputs[i]; 309 310 /* Find denomination */ 311 dpk = TALER_TESTING_find_pk (keys, 312 &cos->amount, 313 true); /* _always_ use denominations with age-striction */ 314 if (NULL == dpk) 315 { 316 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 317 "Failed to determine denomination key for amount at %s\n", 318 (NULL != cmd) ? cmd->label : "<retried command>"); 319 GNUNET_break (0); 320 TALER_TESTING_interpreter_fail (is); 321 return; 322 } 323 324 /* We copy the denomination key, as re-querying /keys 325 * would free the old one. */ 326 *denom_pub = *dpk; 327 TALER_denom_pub_copy (&denom_pub->key, 328 &dpk->key); 329 330 /* Accumulate the expected total amount and fee for the history */ 331 GNUNET_assert (0 <= 332 TALER_amount_add (&aws->reserve_history.amount, 333 &cos->amount, 334 &denom_pub->fees.withdraw)); 335 if (i == 0) 336 GNUNET_assert (GNUNET_OK == 337 TALER_amount_set_zero ( 338 denom_pub->fees.withdraw.currency, 339 &aws->reserve_history.details.withdraw.fee)); 340 341 GNUNET_assert (0 <= 342 TALER_amount_add (&aws->reserve_history.details.withdraw.fee, 343 &aws->reserve_history.details.withdraw.fee, 344 &denom_pub->fees.withdraw)); 345 346 } 347 /* Save the expected history entry */ 348 aws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL; 349 aws->reserve_history.details.withdraw.age_restricted = true; 350 aws->reserve_history.details.withdraw.max_age = aws->max_age; 351 352 353 /* Execute the age-restricted variant of withdraw protocol */ 354 aws->handle = 355 TALER_EXCHANGE_withdraw_with_age_proof ( 356 TALER_TESTING_interpreter_get_context (is), 357 keys, 358 TALER_TESTING_get_exchange_url (is), 359 rp, 360 aws->num_coins, 361 aws->denoms_pub, 362 &aws->seed, 363 aws->max_age, 364 &age_withdraw_cb, 365 aws); 366 367 if (NULL == aws->handle) 368 { 369 GNUNET_break (0); 370 TALER_TESTING_interpreter_fail (is); 371 return; 372 } 373 } 374 375 376 /** 377 * Free the state of a "age withdraw" CMD, and possibly cancel a 378 * pending operation thereof 379 * 380 * @param cls Closure of type `struct AgeWithdrawState` 381 * @param cmd The command being freed. 382 */ 383 static void 384 age_withdraw_cleanup ( 385 void *cls, 386 const struct TALER_TESTING_Command *cmd) 387 { 388 struct AgeWithdrawState *aws = cls; 389 390 if (NULL != aws->handle) 391 { 392 TALER_TESTING_command_incomplete (aws->is, 393 cmd->label); 394 TALER_EXCHANGE_withdraw_cancel (aws->handle); 395 aws->handle = NULL; 396 } 397 398 if (NULL != aws->denoms_pub) 399 { 400 for (size_t n = 0; n < aws->num_coins; n++) 401 TALER_denom_pub_free (&aws->denoms_pub[n].key); 402 403 GNUNET_free (aws->denoms_pub); 404 aws->denoms_pub = NULL; 405 } 406 407 if (NULL != aws->coin_outputs) 408 { 409 for (size_t n = 0; n < aws->num_coins; n++) 410 { 411 struct CoinOutputState *out = &aws->coin_outputs[n]; 412 TALER_age_commitment_proof_free (&out->details.age_commitment_proof); 413 TALER_denom_ewv_free (&out->details.blinding_values); 414 } 415 GNUNET_free (aws->coin_outputs); 416 aws->coin_outputs = NULL; 417 } 418 419 GNUNET_free (aws->exchange_url); 420 aws->exchange_url = NULL; 421 GNUNET_free (aws->reserve_payto_uri.normalized_payto); 422 aws->reserve_payto_uri.normalized_payto = NULL; 423 GNUNET_free (aws); 424 } 425 426 427 /** 428 * Offer internal data of a "age withdraw" CMD state to other commands. 429 * 430 * @param cls Closure of type `struct AgeWithdrawState` 431 * @param[out] ret result (could be anything) 432 * @param trait name of the trait 433 * @param idx index number of the object to offer. 434 * @return #GNUNET_OK on success 435 */ 436 static enum GNUNET_GenericReturnValue 437 age_withdraw_traits ( 438 void *cls, 439 const void **ret, 440 const char *trait, 441 unsigned int idx) 442 { 443 struct AgeWithdrawState *aws = cls; 444 struct CoinOutputState *out = &aws->coin_outputs[idx]; 445 struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *details = 446 &aws->coin_outputs[idx].details; 447 struct TALER_TESTING_Trait traits[] = { 448 /* history entry MUST be first due to response code logic below! */ 449 TALER_TESTING_make_trait_reserve_history (idx, 450 &aws->reserve_history), 451 TALER_TESTING_make_trait_denom_pub (idx, 452 &aws->denoms_pub[idx]), 453 TALER_TESTING_make_trait_reserve_priv (&aws->reserve_priv), 454 TALER_TESTING_make_trait_reserve_pub (&aws->reserve_pub), 455 TALER_TESTING_make_trait_withdraw_commitment (&aws->planchets_h), 456 TALER_TESTING_make_trait_amounts (idx, 457 &out->amount), 458 /* FIXME[oec]: add legal requirement to response and handle it here, as well 459 TALER_TESTING_make_trait_legi_requirement_row (&aws->requirement_row), 460 TALER_TESTING_make_trait_h_payto (&aws->h_payto), 461 */ 462 TALER_TESTING_make_trait_normalized_payto_uri (&aws->reserve_payto_uri), 463 TALER_TESTING_make_trait_exchange_url (aws->exchange_url), 464 TALER_TESTING_make_trait_coin_priv (idx, 465 &details->coin_priv), 466 TALER_TESTING_make_trait_withdraw_seed (&aws->seed), 467 /* FIXME[oec]: needed!? 468 TALER_TESTING_make_trait_planchet_secrets (idx, 469 &aws->secrets[k][idx]), 470 */ 471 TALER_TESTING_make_trait_blinding_key (idx, 472 &details->blinding_key), 473 TALER_TESTING_make_trait_exchange_blinding_values (idx, 474 &details->blinding_values 475 ), 476 TALER_TESTING_make_trait_age_commitment_proof ( 477 idx, 478 &details->age_commitment_proof), 479 TALER_TESTING_make_trait_h_age_commitment ( 480 idx, 481 &details->h_age_commitment), 482 TALER_TESTING_trait_end () 483 }; 484 485 if (idx >= aws->num_coins) 486 return GNUNET_NO; 487 488 return TALER_TESTING_get_trait ((aws->expected_response_code == MHD_HTTP_OK) 489 ? &traits[0] /* we have reserve history */ 490 : &traits[1], /* skip reserve history */ 491 ret, 492 trait, 493 idx); 494 } 495 496 497 struct TALER_TESTING_Command 498 TALER_TESTING_cmd_withdraw_with_age_proof (const char *label, 499 const char *reserve_reference, 500 uint8_t max_age, 501 unsigned int 502 expected_response_code, 503 const char *amount, 504 ...) 505 { 506 struct AgeWithdrawState *aws; 507 unsigned int cnt; 508 va_list ap; 509 510 aws = GNUNET_new (struct AgeWithdrawState); 511 aws->reserve_reference = reserve_reference; 512 aws->expected_response_code = expected_response_code; 513 aws->mask = TALER_extensions_get_age_restriction_mask (); 514 aws->max_age = TALER_get_lowest_age (&aws->mask, 515 max_age); 516 cnt = 1; 517 va_start (ap, amount); 518 while (NULL != (va_arg (ap, const char *))) 519 cnt++; 520 aws->num_coins = cnt; 521 aws->coin_outputs = GNUNET_new_array (cnt, 522 struct CoinOutputState); 523 va_end (ap); 524 va_start (ap, amount); 525 526 for (unsigned int i = 0; i<aws->num_coins; i++) 527 { 528 struct CoinOutputState *out = &aws->coin_outputs[i]; 529 if (GNUNET_OK != 530 TALER_string_to_amount (amount, 531 &out->amount)) 532 { 533 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 534 "Failed to parse amount `%s' at %s\n", 535 amount, 536 label); 537 GNUNET_assert (0); 538 } 539 /* move on to next vararg! */ 540 amount = va_arg (ap, const char *); 541 } 542 543 GNUNET_assert (NULL == amount); 544 va_end (ap); 545 546 { 547 struct TALER_TESTING_Command cmd = { 548 .cls = aws, 549 .label = label, 550 .run = &age_withdraw_run, 551 .cleanup = &age_withdraw_cleanup, 552 .traits = &age_withdraw_traits, 553 }; 554 555 return cmd; 556 } 557 } 558 559 560 /** 561 * The state for the age-withdraw-reveal operation 562 */ 563 struct AgeRevealWithdrawState 564 { 565 /** 566 * The reference to the CMD resembling the previous call to age-withdraw 567 */ 568 const char *age_withdraw_reference; 569 570 /** 571 * The state to the previous age-withdraw command 572 */ 573 const struct AgeWithdrawState *aws; 574 575 /** 576 * The expected response code from the call to the 577 * age-withdraw-reveal operation 578 */ 579 unsigned int expected_response_code; 580 581 /** 582 * Interpreter state (during command) 583 */ 584 struct TALER_TESTING_Interpreter *is; 585 586 /** 587 * The handle to the reveal-operation 588 */ 589 struct TALER_EXCHANGE_RevealWithdrawHandle *handle; 590 591 592 /** 593 * Number of coins, extracted form the age withdraw command 594 */ 595 size_t num_coins; 596 597 /** 598 * The signatures of the @e num_coins coins returned 599 */ 600 struct TALER_DenominationSignature *denom_sigs; 601 602 }; 603 604 605 /** 606 * Callback for the reveal response 607 * 608 * @param cls Closure of type `struct AgeRevealWithdrawState` 609 * @param response The response 610 */ 611 static void 612 age_reveal_withdraw_cb ( 613 void *cls, 614 const struct TALER_EXCHANGE_RevealWithdrawResponse *response) 615 { 616 struct AgeRevealWithdrawState *awrs = cls; 617 struct TALER_TESTING_Interpreter *is = awrs->is; 618 619 awrs->handle = NULL; 620 if (awrs->expected_response_code != response->hr.http_status) 621 { 622 TALER_TESTING_unexpected_status_with_body (is, 623 response->hr.http_status, 624 awrs->expected_response_code, 625 response->hr.reply); 626 return; 627 } 628 switch (response->hr.http_status) 629 { 630 case MHD_HTTP_OK: 631 { 632 const struct AgeWithdrawState *aws = awrs->aws; 633 GNUNET_assert (awrs->num_coins == response->details.ok.num_sigs); 634 awrs->denom_sigs = GNUNET_new_array (awrs->num_coins, 635 struct TALER_DenominationSignature); 636 for (size_t n = 0; n < awrs->num_coins; n++) 637 { 638 GNUNET_assert (GNUNET_OK == 639 TALER_denom_sig_unblind ( 640 &awrs->denom_sigs[n], 641 &response->details.ok.blinded_denom_sigs[n], 642 &aws->coin_outputs[n].details.blinding_key, 643 &aws->coin_outputs[n].details.h_coin_pub, 644 &aws->coin_outputs[n].details.blinding_values, 645 &aws->denoms_pub[n].key)); 646 TALER_denom_sig_free (&awrs->denom_sigs[n]); 647 } 648 649 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 650 "age-withdraw reveal success!\n"); 651 GNUNET_free (awrs->denom_sigs); 652 } 653 break; 654 case MHD_HTTP_NOT_FOUND: 655 case MHD_HTTP_FORBIDDEN: 656 /* nothing to check */ 657 break; 658 /* FIXME[oec]: handle more cases !? */ 659 default: 660 /* Unsupported status code (by test harness) */ 661 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 662 "Age withdraw reveal test command does not support status code %u\n", 663 response->hr.http_status); 664 GNUNET_break (0); 665 break; 666 } 667 668 /* We are done with this command, pick the next one */ 669 TALER_TESTING_interpreter_next (is); 670 } 671 672 673 /** 674 * Run the command for age-withdraw-reveal 675 */ 676 static void 677 age_reveal_withdraw_run ( 678 void *cls, 679 const struct TALER_TESTING_Command *cmd, 680 struct TALER_TESTING_Interpreter *is) 681 { 682 struct AgeRevealWithdrawState *awrs = cls; 683 const struct TALER_TESTING_Command *age_withdraw_cmd; 684 const struct AgeWithdrawState *aws; 685 686 (void) cmd; 687 awrs->is = is; 688 689 /* 690 * Get the command and state for the previous call to "age witdraw" 691 */ 692 age_withdraw_cmd = 693 TALER_TESTING_interpreter_lookup_command (is, 694 awrs->age_withdraw_reference); 695 if (NULL == age_withdraw_cmd) 696 { 697 GNUNET_break (0); 698 TALER_TESTING_interpreter_fail (is); 699 return; 700 } 701 GNUNET_assert (age_withdraw_cmd->run == age_withdraw_run); 702 aws = age_withdraw_cmd->cls; 703 awrs->aws = aws; 704 awrs->num_coins = aws->num_coins; 705 706 { 707 struct TALER_RevealWithdrawMasterSeedsP revealed_seeds; 708 size_t j = 0; 709 for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) 710 { 711 if (aws->noreveal_index == k) 712 continue; 713 714 revealed_seeds.tuple[j] = aws->kappa_seed.tuple[k]; 715 j++; 716 } 717 718 awrs->handle = 719 TALER_EXCHANGE_reveal_withdraw ( 720 TALER_TESTING_interpreter_get_context (is), 721 TALER_TESTING_get_exchange_url (is), 722 aws->num_coins, 723 &aws->planchets_h, 724 &revealed_seeds, 725 age_reveal_withdraw_cb, 726 awrs); 727 } 728 } 729 730 731 /** 732 * Free the state of a "age-withdraw-reveal" CMD, and possibly 733 * cancel a pending operation thereof 734 * 735 * @param cls Closure of type `struct AgeRevealWithdrawState` 736 * @param cmd The command being freed. 737 */ 738 static void 739 age_reveal_withdraw_cleanup ( 740 void *cls, 741 const struct TALER_TESTING_Command *cmd) 742 { 743 struct AgeRevealWithdrawState *awrs = cls; 744 745 if (NULL != awrs->handle) 746 { 747 TALER_TESTING_command_incomplete (awrs->is, 748 cmd->label); 749 TALER_EXCHANGE_reveal_withdraw_cancel (awrs->handle); 750 awrs->handle = NULL; 751 } 752 GNUNET_free (awrs->denom_sigs); 753 awrs->denom_sigs = NULL; 754 GNUNET_free (awrs); 755 } 756 757 758 /** 759 * Offer internal data of a "age withdraw reveal" CMD state to other commands. 760 * 761 * @param cls Closure of they `struct AgeRevealWithdrawState` 762 * @param[out] ret result (could be anything) 763 * @param trait name of the trait 764 * @param idx index number of the object to offer. 765 * @return #GNUNET_OK on success 766 */ 767 static enum GNUNET_GenericReturnValue 768 age_reveal_withdraw_traits ( 769 void *cls, 770 const void **ret, 771 const char *trait, 772 unsigned int idx) 773 { 774 struct AgeRevealWithdrawState *awrs = cls; 775 struct TALER_TESTING_Trait traits[] = { 776 TALER_TESTING_make_trait_denom_sig (idx, 777 &awrs->denom_sigs[idx]), 778 /* FIXME: shall we provide the traits from the previous 779 * call to "age withdraw" as well? */ 780 TALER_TESTING_trait_end () 781 }; 782 783 if (idx >= awrs->num_coins) 784 return GNUNET_NO; 785 786 return TALER_TESTING_get_trait (traits, 787 ret, 788 trait, 789 idx); 790 } 791 792 793 struct TALER_TESTING_Command 794 TALER_TESTING_cmd_withdraw_reveal_age_proof ( 795 const char *label, 796 const char *age_withdraw_reference, 797 unsigned int expected_response_code) 798 { 799 struct AgeRevealWithdrawState *awrs = 800 GNUNET_new (struct AgeRevealWithdrawState); 801 802 awrs->age_withdraw_reference = age_withdraw_reference; 803 awrs->expected_response_code = expected_response_code; 804 { 805 struct TALER_TESTING_Command cmd = { 806 .cls = awrs, 807 .label = label, 808 .run = age_reveal_withdraw_run, 809 .cleanup = age_reveal_withdraw_cleanup, 810 .traits = age_reveal_withdraw_traits, 811 }; 812 813 return cmd; 814 } 815 } 816 817 818 /* end of testing_api_cmd_age_withdraw.c */