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