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