LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_recoup.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 78.8 % 193 152
Test Date: 2026-10-17 15:46:49 Functions: 100.0 % 7 7

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2018-2026 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU General Public License along with
      14              :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file testing/testing_api_cmd_recoup.c
      18              :  * @brief Implement the /recoup-withdraw test command.
      19              :  * @author Marcello Stanisci
      20              :  * @author Christian Grothoff
      21              :  * @author Özgür Kesim
      22              :  */
      23              : #include "platform.h"
      24              : #include "taler/taler_json_lib.h"
      25              : #include <gnunet/gnunet_curl_lib.h>
      26              : #include "taler/taler_testing_lib.h"
      27              : 
      28              : 
      29              : /**
      30              :  * State for a "recoup-withdraw" CMD.
      31              :  */
      32              : struct RecoupState
      33              : {
      34              :   /**
      35              :    * Expected HTTP status code.
      36              :    */
      37              :   unsigned int expected_response_code;
      38              : 
      39              :   /**
      40              :    * Label of the withdraw command the coins came from.
      41              :    */
      42              :   char *withdraw_reference;
      43              : 
      44              :   /**
      45              :    * Indices (in the withdraw command) of the coins to recoup.
      46              :    */
      47              :   unsigned int *indices;
      48              : 
      49              :   /**
      50              :    * Number of entries in @e indices, @e ches, @e rhes and @e coin_pubs.
      51              :    */
      52              :   size_t num_indices;
      53              : 
      54              :   /**
      55              :    * Amount we expect to be recouped per coin, invalid if
      56              :    * not specified.
      57              :    */
      58              :   struct TALER_Amount amount;
      59              : 
      60              :   /**
      61              :    * Interpreter state.
      62              :    */
      63              :   struct TALER_TESTING_Interpreter *is;
      64              : 
      65              :   /**
      66              :    * Handle to the request.
      67              :    */
      68              :   struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph;
      69              : 
      70              :   /**
      71              :    * Reserve that is credited.
      72              :    */
      73              :   struct TALER_ReservePublicKeyP reserve_pub;
      74              : 
      75              :   /**
      76              :    * Coin history entries we expect, one per recouped coin.
      77              :    */
      78              :   struct TALER_EXCHANGE_CoinHistoryEntry *ches;
      79              : 
      80              :   /**
      81              :    * Reserve history entries we expect, one per recouped coin.
      82              :    */
      83              :   struct TALER_EXCHANGE_ReserveHistoryEntry *rhes;
      84              : 
      85              :   /**
      86              :    * Public keys of the recouped coins.
      87              :    */
      88              :   struct TALER_CoinSpendPublicKeyP *coin_pubs;
      89              : };
      90              : 
      91              : 
      92              : /**
      93              :  * Check the response.
      94              :  *
      95              :  * @param cls closure, our `struct RecoupState`
      96              :  * @param rr response details
      97              :  */
      98              : static void
      99           22 : recoup_cb (void *cls,
     100              :            const struct TALER_EXCHANGE_PostRecoupWithdrawResponse *rr)
     101              : {
     102           22 :   struct RecoupState *ps = cls;
     103           22 :   const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
     104           22 :   struct TALER_TESTING_Interpreter *is = ps->is;
     105              : 
     106           22 :   ps->ph = NULL;
     107           22 :   if (ps->expected_response_code != hr->http_status)
     108              :   {
     109            0 :     TALER_TESTING_unexpected_status (is,
     110              :                                      hr->http_status,
     111              :                                      ps->expected_response_code);
     112            0 :     return;
     113              :   }
     114           22 :   switch (hr->http_status)
     115              :   {
     116           18 :   case MHD_HTTP_OK:
     117           18 :     if (0 != GNUNET_memcmp (&rr->details.ok.reserve_pub,
     118              :                             &ps->reserve_pub))
     119              :     {
     120            0 :       GNUNET_break (0);
     121            0 :       TALER_TESTING_interpreter_fail (is);
     122            0 :       return;
     123              :     }
     124           18 :     if (rr->details.ok.num_recoups != ps->num_indices)
     125              :     {
     126            0 :       GNUNET_break (0);
     127            0 :       TALER_TESTING_interpreter_fail (is);
     128            0 :       return;
     129              :     }
     130           42 :     for (size_t i = 0; i < ps->num_indices; i++)
     131              :     {
     132           24 :       const struct TALER_RecoupedCoin *rc = &rr->details.ok.recoups[i];
     133              : 
     134           24 :       if (0 != GNUNET_memcmp (&rc->coin_pub,
     135              :                               &ps->coin_pubs[i]))
     136              :       {
     137            0 :         GNUNET_break (0);
     138            0 :         TALER_TESTING_interpreter_fail (is);
     139            0 :         return;
     140              :       }
     141           24 :       if ( (GNUNET_OK ==
     142           48 :             TALER_amount_is_valid (&ps->amount)) &&
     143           24 :            (0 != TALER_amount_cmp (&rc->amount,
     144           24 :                                    &ps->amount)) )
     145              :       {
     146            0 :         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     147              :                     "Recouped %s for coin %u, expected %s\n",
     148              :                     TALER_amount2s (&rc->amount),
     149              :                     ps->indices[i],
     150              :                     TALER_amount2s (&ps->amount));
     151            0 :         GNUNET_break (0);
     152            0 :         TALER_TESTING_interpreter_fail (is);
     153            0 :         return;
     154              :       }
     155           24 :       ps->ches[i].amount = rc->amount;
     156           24 :       ps->rhes[i].amount = rc->amount;
     157           24 :       ps->rhes[i].type = TALER_EXCHANGE_RTT_RECOUP;
     158              :     }
     159           18 :     break;
     160            4 :   default:
     161            4 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     162              :                 "Recoup-withdraw failed as expected with HTTP status %u/%d\n",
     163              :                 hr->http_status,
     164              :                 (int) hr->ec);
     165            4 :     break;
     166              :   }
     167           22 :   TALER_TESTING_interpreter_next (is);
     168              : }
     169              : 
     170              : 
     171              : /**
     172              :  * Run the command.
     173              :  *
     174              :  * @param cls closure.
     175              :  * @param cmd the command to execute.
     176              :  * @param is the interpreter state.
     177              :  */
     178              : static void
     179           22 : recoup_run (void *cls,
     180              :             const struct TALER_TESTING_Command *cmd,
     181              :             struct TALER_TESTING_Interpreter *is)
     182              : {
     183           22 :   struct RecoupState *ps = cls;
     184              :   const struct TALER_TESTING_Command *coin_cmd;
     185              :   const struct TALER_ReservePublicKeyP *reserve_pub;
     186              :   const struct TALER_WithdrawMasterSeedP *seed;
     187              :   const struct TALER_HashBlindedPlanchetsP *h_planchets;
     188              :   struct TALER_BlindingMasterSeedP blinding_seed;
     189           22 :   bool have_cs = false;
     190           22 :   size_t num_coins = 0;
     191              : 
     192              :   (void) cmd;
     193           22 :   ps->is = is;
     194           22 :   coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
     195           22 :                                                        ps->withdraw_reference);
     196           22 :   if (NULL == coin_cmd)
     197              :   {
     198            0 :     GNUNET_break (0);
     199            0 :     TALER_TESTING_interpreter_fail (is);
     200            0 :     return;
     201              :   }
     202           22 :   if ( (GNUNET_OK !=
     203           22 :         TALER_TESTING_get_trait_reserve_pub (coin_cmd,
     204           22 :                                              &reserve_pub)) ||
     205              :        (GNUNET_OK !=
     206           22 :         TALER_TESTING_get_trait_withdraw_seed (coin_cmd,
     207           22 :                                                &seed)) ||
     208              :        (GNUNET_OK !=
     209           22 :         TALER_TESTING_get_trait_withdraw_commitment (coin_cmd,
     210              :                                                      &h_planchets)) )
     211              :   {
     212            0 :     GNUNET_break (0);
     213            0 :     TALER_TESTING_interpreter_fail (is);
     214            0 :     return;
     215              :   }
     216           22 :   ps->reserve_pub = *reserve_pub;
     217              :   /* count the coins of the withdraw operation */
     218           56 :   while (num_coins < TALER_MAX_COINS)
     219              :   {
     220              :     const struct TALER_EXCHANGE_DenomPublicKey *pk;
     221              : 
     222           56 :     if (GNUNET_OK !=
     223           56 :         TALER_TESTING_get_trait_denom_pub (coin_cmd,
     224              :                                            (unsigned int) num_coins,
     225              :                                            &pk))
     226           22 :       break;
     227           34 :     num_coins++;
     228              :   }
     229           22 :   if (0 == num_coins)
     230              :   {
     231            0 :     GNUNET_break (0);
     232            0 :     TALER_TESTING_interpreter_fail (is);
     233            0 :     return;
     234              :   }
     235           50 :   for (size_t i = 0; i < ps->num_indices; i++)
     236           28 :     if (ps->indices[i] >= num_coins)
     237              :     {
     238            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     239              :                   "Coin index %u out of range for command %s (%u coins)\n",
     240              :                   ps->indices[i],
     241              :                   ps->withdraw_reference,
     242              :                   (unsigned int) num_coins);
     243            0 :       GNUNET_break (0);
     244            0 :       TALER_TESTING_interpreter_fail (is);
     245            0 :       return;
     246              :     }
     247              :   /* the CS blinding seed is derived from the withdraw seed */
     248           22 :   TALER_cs_withdraw_seed_to_blinding_seed (seed,
     249              :                                            &blinding_seed);
     250           22 :   {
     251           22 :     struct TALER_PlanchetMasterSecretP secrets[num_coins];
     252           22 :     struct TALER_EXCHANGE_RecoupCoin coins[num_coins];
     253           22 :     size_t k = 0;
     254              : 
     255           22 :     TALER_withdraw_expand_secrets (num_coins,
     256              :                                    seed,
     257              :                                    secrets);
     258           22 :     memset (coins,
     259              :             0,
     260              :             sizeof (coins));
     261           56 :     for (size_t i = 0; i < num_coins; i++)
     262              :     {
     263           34 :       struct TALER_EXCHANGE_RecoupCoin *c = &coins[i];
     264           34 :       const struct TALER_AgeCommitmentHashP *hac = NULL;
     265           34 :       bool recoup = false;
     266              : 
     267           34 :       if ( (GNUNET_OK !=
     268           34 :             TALER_TESTING_get_trait_denom_pub (coin_cmd,
     269              :                                                (unsigned int) i,
     270           34 :                                                &c->pk)) ||
     271              :            (GNUNET_OK !=
     272           34 :             TALER_TESTING_get_trait_exchange_blinding_values (
     273              :               coin_cmd,
     274              :               (unsigned int) i,
     275              :               &c->exchange_vals)) )
     276              :       {
     277            0 :         GNUNET_break (0);
     278            0 :         TALER_TESTING_interpreter_fail (is);
     279            0 :         return;
     280              :       }
     281              :       /* only denominations with age restriction have a commitment */
     282           40 :       if ( (0 != c->pk->key.age_mask.bits) &&
     283              :            (GNUNET_OK ==
     284            6 :             TALER_TESTING_get_trait_h_age_commitment (coin_cmd,
     285              :                                                       (unsigned int) i,
     286              :                                                       &hac)) )
     287            6 :         c->h_age_commitment = hac; /* may still be NULL */
     288              :       /* commands with per-coin secrets (batch and age withdraw) expose
     289              :          them; the single withdraw derives them from the seed */
     290           34 :       if (GNUNET_OK !=
     291           34 :           TALER_TESTING_get_trait_planchet_secrets (coin_cmd,
     292              :                                                     (unsigned int) i,
     293              :                                                     &c->ps))
     294           16 :         c->ps = &secrets[i];
     295           34 :       if (GNUNET_CRYPTO_BSA_CS == c->pk->key.bsign_pub_key->cipher)
     296           17 :         have_cs = true;
     297           86 :       for (size_t j = 0; j < ps->num_indices; j++)
     298           52 :         if (ps->indices[j] == i)
     299           28 :           recoup = true;
     300           34 :       if (! recoup)
     301            6 :         continue;
     302           28 :       if (GNUNET_OK !=
     303           28 :           TALER_TESTING_get_trait_denom_sig (coin_cmd,
     304              :                                              (unsigned int) i,
     305              :                                              &c->denom_sig))
     306              :       {
     307            0 :         GNUNET_break (0);
     308            0 :         TALER_TESTING_interpreter_fail (is);
     309            0 :         return;
     310              :       }
     311              :       /* what the histories should show for this coin */
     312              :       {
     313           28 :         struct TALER_EXCHANGE_CoinHistoryEntry *che = &ps->ches[k];
     314           28 :         struct TALER_EXCHANGE_ReserveHistoryEntry *rhe = &ps->rhes[k];
     315              :         struct TALER_CoinSpendPrivateKeyP coin_priv;
     316           28 :         const struct TALER_ExchangeBlindingValues *vals = c->exchange_vals;
     317              : 
     318           28 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     319              :                     "Trying to recoup coin %u of denomination '%s'\n",
     320              :                     (unsigned int) i,
     321              :                     TALER_B2S (&c->pk->h_key));
     322           28 :         TALER_planchet_setup_coin_priv (c->ps,
     323              :                                         vals,
     324              :                                         &coin_priv);
     325           28 :         GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
     326           28 :                                             &ps->coin_pubs[k].eddsa_pub);
     327           28 :         che->type = TALER_EXCHANGE_CTT_RECOUP;
     328           28 :         che->amount = ps->amount;
     329           28 :         che->details.recoup.reserve_pub = ps->reserve_pub;
     330           28 :         che->details.recoup.planchets_h = *h_planchets;
     331           28 :         che->details.recoup.coin_index = (uint32_t) i;
     332           28 :         TALER_planchet_blinding_secret_create (c->ps,
     333              :                                                vals,
     334              :                                                &che->details.recoup.coin_bks);
     335              :         /* exercise the documented contract: the request needs no
     336              :            exchange values for RSA denominations */
     337           28 :         if (GNUNET_CRYPTO_BSA_RSA == c->pk->key.bsign_pub_key->cipher)
     338           14 :           c->exchange_vals = NULL;
     339           28 :         TALER_wallet_recoup_sign (&c->pk->h_key,
     340           28 :                                   &che->details.recoup.coin_bks,
     341              :                                   &coin_priv,
     342              :                                   &che->details.recoup.coin_sig);
     343           28 :         rhe->amount = ps->amount;
     344           28 :         rhe->details.recoup_details.coin_pub = ps->coin_pubs[k];
     345           28 :         rhe->details.recoup_details.planchets_h = *h_planchets;
     346           28 :         k++;
     347              :       }
     348              :     }
     349           44 :     ps->ph = TALER_EXCHANGE_post_recoup_withdraw_create (
     350              :       TALER_TESTING_interpreter_get_context (is),
     351              :       TALER_TESTING_get_exchange_url (is),
     352              :       TALER_TESTING_get_keys (is),
     353           22 :       &ps->reserve_pub,
     354              :       h_planchets,
     355              :       have_cs ? &blinding_seed : NULL,
     356              :       num_coins,
     357              :       coins);
     358              :   }
     359           22 :   if (NULL == ps->ph)
     360              :   {
     361            0 :     GNUNET_break (0);
     362            0 :     TALER_TESTING_interpreter_fail (is);
     363            0 :     return;
     364              :   }
     365           22 :   GNUNET_assert (TALER_EC_NONE ==
     366              :                  TALER_EXCHANGE_post_recoup_withdraw_start (ps->ph,
     367              :                                                             &recoup_cb,
     368              :                                                             ps));
     369              : }
     370              : 
     371              : 
     372              : /**
     373              :  * Cleanup the state.
     374              :  *
     375              :  * @param cls closure, must be a `struct RecoupState`.
     376              :  * @param cmd the command which is being cleaned up.
     377              :  */
     378              : static void
     379           22 : recoup_cleanup (void *cls,
     380              :                 const struct TALER_TESTING_Command *cmd)
     381              : {
     382           22 :   struct RecoupState *ps = cls;
     383              : 
     384              :   (void) cmd;
     385           22 :   if (NULL != ps->ph)
     386              :   {
     387            0 :     TALER_EXCHANGE_post_recoup_withdraw_cancel (ps->ph);
     388            0 :     ps->ph = NULL;
     389              :   }
     390           22 :   GNUNET_free (ps->withdraw_reference);
     391           22 :   GNUNET_free (ps->indices);
     392           22 :   GNUNET_free (ps->ches);
     393           22 :   GNUNET_free (ps->rhes);
     394           22 :   GNUNET_free (ps->coin_pubs);
     395           22 :   GNUNET_free (ps);
     396           22 : }
     397              : 
     398              : 
     399              : /**
     400              :  * Offer internal data to other commands: one coin history entry
     401              :  * and one reserve history entry per recouped coin.
     402              :  *
     403              :  * @param cls closure
     404              :  * @param[out] ret result (could be anything)
     405              :  * @param trait name of the trait
     406              :  * @param index index number of the object to extract.
     407              :  * @return #GNUNET_OK on success
     408              :  */
     409              : static enum GNUNET_GenericReturnValue
     410           34 : recoup_traits (void *cls,
     411              :                const void **ret,
     412              :                const char *trait,
     413              :                unsigned int index)
     414              : {
     415           34 :   struct RecoupState *ps = cls;
     416              : 
     417           34 :   if (index >= ps->num_indices)
     418            8 :     return GNUNET_NO;
     419           26 :   if (TALER_EXCHANGE_RTT_RECOUP != ps->rhes[index].type)
     420            4 :     return GNUNET_SYSERR; /* request did not succeed: no traits */
     421              :   {
     422              :     struct TALER_TESTING_Trait traits[] = {
     423           22 :       TALER_TESTING_make_trait_reserve_pub (&ps->reserve_pub),
     424           22 :       TALER_TESTING_make_trait_reserve_history (index,
     425           22 :                                                 &ps->rhes[index]),
     426           22 :       TALER_TESTING_make_trait_coin_history (index,
     427           22 :                                              &ps->ches[index]),
     428           22 :       TALER_TESTING_make_trait_coin_pub (index,
     429           22 :                                          &ps->coin_pubs[index]),
     430           22 :       TALER_TESTING_trait_end ()
     431              :     };
     432              : 
     433           22 :     return TALER_TESTING_get_trait (traits,
     434              :                                     ret,
     435              :                                     trait,
     436              :                                     index);
     437              :   }
     438              : }
     439              : 
     440              : 
     441              : /**
     442              :  * Create the command state shared by both constructors.
     443              :  *
     444              :  * @param label the command label
     445              :  * @param expected_response_code expected HTTP status code
     446              :  * @param withdraw_reference label of the withdraw command
     447              :  * @param indices coins to recoup, becomes owned by the state
     448              :  * @param num_indices length of @a indices
     449              :  * @param amount expected amount per coin, NULL for none
     450              :  * @return the command
     451              :  */
     452              : static struct TALER_TESTING_Command
     453           22 : make_recoup_cmd (const char *label,
     454              :                  unsigned int expected_response_code,
     455              :                  const char *withdraw_reference,
     456              :                  unsigned int *indices,
     457              :                  size_t num_indices,
     458              :                  const char *amount)
     459              : {
     460              :   struct RecoupState *ps;
     461              : 
     462           22 :   ps = GNUNET_new (struct RecoupState);
     463           22 :   ps->expected_response_code = expected_response_code;
     464           22 :   ps->withdraw_reference = GNUNET_strdup (withdraw_reference);
     465           22 :   ps->indices = indices;
     466           22 :   ps->num_indices = num_indices;
     467           22 :   ps->ches = GNUNET_new_array (num_indices,
     468              :                                struct TALER_EXCHANGE_CoinHistoryEntry);
     469           22 :   ps->rhes = GNUNET_new_array (num_indices,
     470              :                                struct TALER_EXCHANGE_ReserveHistoryEntry);
     471           22 :   ps->coin_pubs = GNUNET_new_array (num_indices,
     472              :                                     struct TALER_CoinSpendPublicKeyP);
     473           44 :   if ( (NULL != amount) &&
     474              :        (GNUNET_OK !=
     475           22 :         TALER_string_to_amount (amount,
     476              :                                 &ps->amount)) )
     477              :   {
     478            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     479              :                 "Failed to parse amount `%s' at %s\n",
     480              :                 amount,
     481              :                 label);
     482            0 :     GNUNET_assert (0);
     483              :   }
     484              :   {
     485           22 :     struct TALER_TESTING_Command cmd = {
     486              :       .cls = ps,
     487              :       .label = label,
     488              :       .run = &recoup_run,
     489              :       .cleanup = &recoup_cleanup,
     490              :       .traits = &recoup_traits
     491              :     };
     492              : 
     493           22 :     return cmd;
     494              :   }
     495              : }
     496              : 
     497              : 
     498              : struct TALER_TESTING_Command
     499           16 : TALER_TESTING_cmd_recoup (const char *label,
     500              :                           unsigned int expected_response_code,
     501              :                           const char *coin_reference,
     502              :                           const char *amount)
     503              : {
     504              :   char *cref;
     505              :   unsigned int idx;
     506              :   unsigned int *indices;
     507              :   struct TALER_TESTING_Command cmd;
     508              : 
     509           16 :   GNUNET_assert (GNUNET_OK ==
     510              :                  TALER_TESTING_parse_coin_reference (coin_reference,
     511              :                                                      &cref,
     512              :                                                      &idx));
     513           16 :   indices = GNUNET_new_array (1,
     514              :                               unsigned int);
     515           16 :   indices[0] = idx;
     516           16 :   cmd = make_recoup_cmd (label,
     517              :                          expected_response_code,
     518              :                          cref,
     519              :                          indices,
     520              :                          1,
     521              :                          amount);
     522           16 :   GNUNET_free (cref);
     523           16 :   return cmd;
     524              : }
     525              : 
     526              : 
     527              : struct TALER_TESTING_Command
     528            6 : TALER_TESTING_cmd_recoup_batch (const char *label,
     529              :                                 unsigned int expected_response_code,
     530              :                                 const char *withdraw_reference,
     531              :                                 const char *indices,
     532              :                                 const char *amount)
     533              : {
     534              :   unsigned int *idx;
     535              :   size_t num;
     536              : 
     537            6 :   idx = TALER_TESTING_parse_index_list (indices,
     538              :                                         &num);
     539            6 :   GNUNET_assert (NULL != idx);
     540            6 :   return make_recoup_cmd (label,
     541              :                           expected_response_code,
     542              :                           withdraw_reference,
     543              :                           idx,
     544              :                           num,
     545              :                           amount);
     546              : }
     547              : 
     548              : 
     549              : /* end of testing_api_cmd_recoup.c */
        

Generated by: LCOV version 2.0-1