testing_api_cmd_age_withdraw.c (24279B)
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 (&aws->seed, 298 sizeof(aws->seed)); 299 300 for (unsigned int i = 0; i<aws->num_coins; i++) 301 { 302 struct TALER_EXCHANGE_DenomPublicKey *denom_pub = &aws->denoms_pub[i]; 303 struct CoinOutputState *cos = &aws->coin_outputs[i]; 304 305 /* Find denomination */ 306 dpk = TALER_TESTING_find_pk (keys, 307 &cos->amount, 308 true); /* _always_ use denominations with age-striction */ 309 if (NULL == dpk) 310 { 311 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 312 "Failed to determine denomination key for amount at %s\n", 313 (NULL != cmd) ? cmd->label : "<retried command>"); 314 GNUNET_break (0); 315 TALER_TESTING_interpreter_fail (is); 316 return; 317 } 318 319 /* We copy the denomination key, as re-querying /keys 320 * would free the old one. */ 321 *denom_pub = *dpk; 322 TALER_denom_pub_copy (&denom_pub->key, 323 &dpk->key); 324 325 /* Accumulate the expected total amount and fee for the history */ 326 GNUNET_assert (0 <= 327 TALER_amount_add (&aws->reserve_history.amount, 328 &cos->amount, 329 &denom_pub->fees.withdraw)); 330 if (i == 0) 331 GNUNET_assert (GNUNET_OK == 332 TALER_amount_set_zero ( 333 denom_pub->fees.withdraw.currency, 334 &aws->reserve_history.details.withdraw.fee)); 335 336 GNUNET_assert (0 <= 337 TALER_amount_add (&aws->reserve_history.details.withdraw.fee, 338 &aws->reserve_history.details.withdraw.fee, 339 &denom_pub->fees.withdraw)); 340 341 } 342 /* Save the expected history entry */ 343 aws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL; 344 aws->reserve_history.details.withdraw.age_restricted = true; 345 aws->reserve_history.details.withdraw.max_age = aws->max_age; 346 347 348 /* Execute the age-restricted variant of withdraw protocol */ 349 aws->handle = 350 TALER_EXCHANGE_post_withdraw_create ( 351 TALER_TESTING_interpreter_get_context (is), 352 TALER_TESTING_get_exchange_url (is), 353 keys, 354 rp, 355 aws->num_coins, 356 aws->denoms_pub, 357 &aws->seed, 358 aws->max_age); 359 if (NULL == aws->handle) 360 { 361 GNUNET_break (0); 362 TALER_TESTING_interpreter_fail (is); 363 return; 364 } 365 GNUNET_assert (GNUNET_OK == 366 TALER_EXCHANGE_post_withdraw_set_options ( 367 aws->handle, 368 TALER_EXCHANGE_post_withdraw_option_with_age_proof ( 369 aws->max_age))); 370 GNUNET_assert (TALER_EC_NONE == 371 TALER_EXCHANGE_post_withdraw_start (aws->handle, 372 &age_withdraw_cb, 373 aws)); 374 } 375 376 377 /** 378 * Free the state of a "age withdraw" CMD, and possibly cancel a 379 * pending operation thereof 380 * 381 * @param cls Closure of type `struct AgeWithdrawState` 382 * @param cmd The command being freed. 383 */ 384 static void 385 age_withdraw_cleanup ( 386 void *cls, 387 const struct TALER_TESTING_Command *cmd) 388 { 389 struct AgeWithdrawState *aws = cls; 390 391 if (NULL != aws->handle) 392 { 393 TALER_TESTING_command_incomplete (aws->is, 394 cmd->label); 395 TALER_EXCHANGE_post_withdraw_cancel (aws->handle); 396 aws->handle = NULL; 397 } 398 399 if (NULL != aws->denoms_pub) 400 { 401 for (size_t n = 0; n < aws->num_coins; n++) 402 TALER_denom_pub_free (&aws->denoms_pub[n].key); 403 404 GNUNET_free (aws->denoms_pub); 405 aws->denoms_pub = NULL; 406 } 407 408 if (NULL != aws->coin_outputs) 409 { 410 for (size_t n = 0; n < aws->num_coins; n++) 411 { 412 struct CoinOutputState *out = &aws->coin_outputs[n]; 413 TALER_age_commitment_proof_free (&out->details.age_commitment_proof); 414 TALER_denom_ewv_free (&out->details.blinding_values); 415 } 416 GNUNET_free (aws->coin_outputs); 417 aws->coin_outputs = NULL; 418 } 419 420 GNUNET_free (aws->exchange_url); 421 aws->exchange_url = NULL; 422 GNUNET_free (aws->reserve_payto_uri.normalized_payto); 423 aws->reserve_payto_uri.normalized_payto = NULL; 424 GNUNET_free (aws); 425 } 426 427 428 /** 429 * Offer internal data of a "age withdraw" CMD state to other commands. 430 * 431 * @param cls Closure of type `struct AgeWithdrawState` 432 * @param[out] ret result (could be anything) 433 * @param trait name of the trait 434 * @param idx index number of the object to offer. 435 * @return #GNUNET_OK on success 436 */ 437 static enum GNUNET_GenericReturnValue 438 age_withdraw_traits ( 439 void *cls, 440 const void **ret, 441 const char *trait, 442 unsigned int idx) 443 { 444 struct AgeWithdrawState *aws = cls; 445 struct CoinOutputState *out = &aws->coin_outputs[idx]; 446 struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *details = 447 &aws->coin_outputs[idx].details; 448 struct TALER_TESTING_Trait traits[] = { 449 /* history entry MUST be first due to response code logic below! */ 450 TALER_TESTING_make_trait_reserve_history (idx, 451 &aws->reserve_history), 452 TALER_TESTING_make_trait_denom_pub (idx, 453 &aws->denoms_pub[idx]), 454 TALER_TESTING_make_trait_reserve_priv (&aws->reserve_priv), 455 TALER_TESTING_make_trait_reserve_pub (&aws->reserve_pub), 456 TALER_TESTING_make_trait_withdraw_commitment (&aws->planchets_h), 457 TALER_TESTING_make_trait_amounts (idx, 458 &out->amount), 459 /* FIXME[oec]: add legal requirement to response and handle it here, as well 460 TALER_TESTING_make_trait_legi_requirement_row (&aws->requirement_row), 461 TALER_TESTING_make_trait_h_payto (&aws->h_payto), 462 */ 463 TALER_TESTING_make_trait_normalized_payto_uri (&aws->reserve_payto_uri), 464 TALER_TESTING_make_trait_exchange_url (aws->exchange_url), 465 TALER_TESTING_make_trait_coin_priv (idx, 466 &details->coin_priv), 467 TALER_TESTING_make_trait_withdraw_seed (&aws->seed), 468 /* FIXME[oec]: needed!? 469 TALER_TESTING_make_trait_planchet_secrets (idx, 470 &aws->secrets[k][idx]), 471 */ 472 TALER_TESTING_make_trait_blinding_key (idx, 473 &details->blinding_key), 474 TALER_TESTING_make_trait_exchange_blinding_values (idx, 475 &details->blinding_values 476 ), 477 TALER_TESTING_make_trait_age_commitment_proof ( 478 idx, 479 &details->age_commitment_proof), 480 TALER_TESTING_make_trait_h_age_commitment ( 481 idx, 482 &details->h_age_commitment), 483 TALER_TESTING_trait_end () 484 }; 485 486 if (idx >= aws->num_coins) 487 return GNUNET_NO; 488 489 return TALER_TESTING_get_trait ((aws->expected_response_code == MHD_HTTP_OK) 490 ? &traits[0] /* we have reserve history */ 491 : &traits[1], /* skip reserve history */ 492 ret, 493 trait, 494 idx); 495 } 496 497 498 struct TALER_TESTING_Command 499 TALER_TESTING_cmd_withdraw_with_age_proof (const char *label, 500 const char *reserve_reference, 501 uint8_t max_age, 502 unsigned int 503 expected_response_code, 504 const char *amount, 505 ...) 506 { 507 struct AgeWithdrawState *aws; 508 unsigned int cnt; 509 va_list ap; 510 511 aws = GNUNET_new (struct AgeWithdrawState); 512 aws->reserve_reference = reserve_reference; 513 aws->expected_response_code = expected_response_code; 514 aws->mask = TALER_testing_age_restriction_mask; 515 aws->max_age = TALER_get_lowest_age (&aws->mask, 516 max_age); 517 cnt = 1; 518 va_start (ap, amount); 519 while (NULL != (va_arg (ap, const char *))) 520 cnt++; 521 aws->num_coins = cnt; 522 aws->coin_outputs = GNUNET_new_array (cnt, 523 struct CoinOutputState); 524 va_end (ap); 525 va_start (ap, amount); 526 527 for (unsigned int i = 0; i<aws->num_coins; i++) 528 { 529 struct CoinOutputState *out = &aws->coin_outputs[i]; 530 if (GNUNET_OK != 531 TALER_string_to_amount (amount, 532 &out->amount)) 533 { 534 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 535 "Failed to parse amount `%s' at %s\n", 536 amount, 537 label); 538 GNUNET_assert (0); 539 } 540 /* move on to next vararg! */ 541 amount = va_arg (ap, const char *); 542 } 543 544 GNUNET_assert (NULL == amount); 545 va_end (ap); 546 547 { 548 struct TALER_TESTING_Command cmd = { 549 .cls = aws, 550 .label = label, 551 .run = &age_withdraw_run, 552 .cleanup = &age_withdraw_cleanup, 553 .traits = &age_withdraw_traits, 554 }; 555 556 return cmd; 557 } 558 } 559 560 561 /** 562 * The state for the age-withdraw-reveal operation 563 */ 564 struct AgeRevealWithdrawState 565 { 566 /** 567 * The reference to the CMD resembling the previous call to age-withdraw 568 */ 569 const char *age_withdraw_reference; 570 571 /** 572 * The state to the previous age-withdraw command 573 */ 574 const struct AgeWithdrawState *aws; 575 576 /** 577 * The expected response code from the call to the 578 * age-withdraw-reveal operation 579 */ 580 unsigned int expected_response_code; 581 582 /** 583 * Interpreter state (during command) 584 */ 585 struct TALER_TESTING_Interpreter *is; 586 587 /** 588 * The handle to the reveal-operation 589 */ 590 struct TALER_EXCHANGE_PostRevealWithdrawHandle *handle; 591 592 593 /** 594 * Number of coins, extracted form the age withdraw command 595 */ 596 size_t num_coins; 597 598 /** 599 * The signatures of the @e num_coins coins returned 600 */ 601 struct TALER_DenominationSignature *denom_sigs; 602 603 }; 604 605 606 /** 607 * Callback for the reveal response 608 * 609 * @param cls Closure of type `struct AgeRevealWithdrawState` 610 * @param response The response 611 */ 612 static void 613 age_reveal_withdraw_cb ( 614 void *cls, 615 const struct TALER_EXCHANGE_PostRevealWithdrawResponse *response) 616 { 617 struct AgeRevealWithdrawState *awrs = cls; 618 struct TALER_TESTING_Interpreter *is = awrs->is; 619 620 awrs->handle = NULL; 621 if (awrs->expected_response_code != response->hr.http_status) 622 { 623 TALER_TESTING_unexpected_status_with_body (is, 624 response->hr.http_status, 625 awrs->expected_response_code, 626 response->hr.reply); 627 return; 628 } 629 switch (response->hr.http_status) 630 { 631 case MHD_HTTP_OK: 632 { 633 const struct AgeWithdrawState *aws = awrs->aws; 634 GNUNET_assert (awrs->num_coins == response->details.ok.num_sigs); 635 awrs->denom_sigs = GNUNET_new_array (awrs->num_coins, 636 struct TALER_DenominationSignature); 637 for (size_t n = 0; n < awrs->num_coins; n++) 638 { 639 GNUNET_assert (GNUNET_OK == 640 TALER_denom_sig_unblind ( 641 &awrs->denom_sigs[n], 642 &response->details.ok.blinded_denom_sigs[n], 643 &aws->coin_outputs[n].details.blinding_key, 644 &aws->coin_outputs[n].details.h_coin_pub, 645 &aws->coin_outputs[n].details.blinding_values, 646 &aws->denoms_pub[n].key)); 647 TALER_denom_sig_free (&awrs->denom_sigs[n]); 648 } 649 650 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 651 "age-withdraw reveal success!\n"); 652 GNUNET_free (awrs->denom_sigs); 653 } 654 break; 655 case MHD_HTTP_NOT_FOUND: 656 case MHD_HTTP_FORBIDDEN: 657 /* nothing to check */ 658 break; 659 /* FIXME[oec]: handle more cases !? */ 660 default: 661 /* Unsupported status code (by test harness) */ 662 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 663 "Age withdraw reveal test command does not support status code %u\n", 664 response->hr.http_status); 665 GNUNET_break (0); 666 break; 667 } 668 669 /* We are done with this command, pick the next one */ 670 TALER_TESTING_interpreter_next (is); 671 } 672 673 674 /** 675 * Run the command for age-withdraw-reveal 676 */ 677 static void 678 age_reveal_withdraw_run ( 679 void *cls, 680 const struct TALER_TESTING_Command *cmd, 681 struct TALER_TESTING_Interpreter *is) 682 { 683 struct AgeRevealWithdrawState *awrs = cls; 684 const struct TALER_TESTING_Command *age_withdraw_cmd; 685 const struct AgeWithdrawState *aws; 686 687 (void) cmd; 688 awrs->is = is; 689 690 /* 691 * Get the command and state for the previous call to "age witdraw" 692 */ 693 age_withdraw_cmd = 694 TALER_TESTING_interpreter_lookup_command (is, 695 awrs->age_withdraw_reference); 696 if (NULL == age_withdraw_cmd) 697 { 698 GNUNET_break (0); 699 TALER_TESTING_interpreter_fail (is); 700 return; 701 } 702 GNUNET_assert (age_withdraw_cmd->run == age_withdraw_run); 703 aws = age_withdraw_cmd->cls; 704 awrs->aws = aws; 705 awrs->num_coins = aws->num_coins; 706 707 { 708 struct TALER_RevealWithdrawMasterSeedsP revealed_seeds; 709 size_t j = 0; 710 for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) 711 { 712 if (aws->noreveal_index == k) 713 continue; 714 715 revealed_seeds.tuple[j] = aws->kappa_seed.tuple[k]; 716 j++; 717 } 718 719 awrs->handle = 720 TALER_EXCHANGE_post_reveal_withdraw_create ( 721 TALER_TESTING_interpreter_get_context (is), 722 TALER_TESTING_get_exchange_url (is), 723 aws->num_coins, 724 &aws->planchets_h, 725 &revealed_seeds); 726 GNUNET_assert (NULL != awrs->handle); 727 GNUNET_assert (TALER_EC_NONE == 728 TALER_EXCHANGE_post_reveal_withdraw_start ( 729 awrs->handle, 730 &age_reveal_withdraw_cb, 731 awrs)); 732 } 733 } 734 735 736 /** 737 * Free the state of a "age-withdraw-reveal" CMD, and possibly 738 * cancel a pending operation thereof 739 * 740 * @param cls Closure of type `struct AgeRevealWithdrawState` 741 * @param cmd The command being freed. 742 */ 743 static void 744 age_reveal_withdraw_cleanup ( 745 void *cls, 746 const struct TALER_TESTING_Command *cmd) 747 { 748 struct AgeRevealWithdrawState *awrs = cls; 749 750 if (NULL != awrs->handle) 751 { 752 TALER_TESTING_command_incomplete (awrs->is, 753 cmd->label); 754 TALER_EXCHANGE_post_reveal_withdraw_cancel (awrs->handle); 755 awrs->handle = NULL; 756 } 757 GNUNET_free (awrs->denom_sigs); 758 awrs->denom_sigs = NULL; 759 GNUNET_free (awrs); 760 } 761 762 763 /** 764 * Offer internal data of a "age withdraw reveal" CMD state to other commands. 765 * 766 * @param cls Closure of they `struct AgeRevealWithdrawState` 767 * @param[out] ret result (could be anything) 768 * @param trait name of the trait 769 * @param idx index number of the object to offer. 770 * @return #GNUNET_OK on success 771 */ 772 static enum GNUNET_GenericReturnValue 773 age_reveal_withdraw_traits ( 774 void *cls, 775 const void **ret, 776 const char *trait, 777 unsigned int idx) 778 { 779 struct AgeRevealWithdrawState *awrs = cls; 780 struct TALER_TESTING_Trait traits[] = { 781 TALER_TESTING_make_trait_denom_sig (idx, 782 &awrs->denom_sigs[idx]), 783 /* FIXME: shall we provide the traits from the previous 784 * call to "age withdraw" as well? */ 785 TALER_TESTING_trait_end () 786 }; 787 788 if (idx >= awrs->num_coins) 789 return GNUNET_NO; 790 791 return TALER_TESTING_get_trait (traits, 792 ret, 793 trait, 794 idx); 795 } 796 797 798 struct TALER_TESTING_Command 799 TALER_TESTING_cmd_withdraw_reveal_age_proof ( 800 const char *label, 801 const char *age_withdraw_reference, 802 unsigned int expected_response_code) 803 { 804 struct AgeRevealWithdrawState *awrs = 805 GNUNET_new (struct AgeRevealWithdrawState); 806 807 awrs->age_withdraw_reference = age_withdraw_reference; 808 awrs->expected_response_code = expected_response_code; 809 { 810 struct TALER_TESTING_Command cmd = { 811 .cls = awrs, 812 .label = label, 813 .run = age_reveal_withdraw_run, 814 .cleanup = age_reveal_withdraw_cleanup, 815 .traits = age_reveal_withdraw_traits, 816 }; 817 818 return cmd; 819 } 820 } 821 822 823 /* end of testing_api_cmd_age_withdraw.c */