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