LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_reserve_purse.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 86.4 % 110 95
Test Date: 2026-01-18 12:54:31 Functions: 100.0 % 5 5

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2022, 2024 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_reserve_purse.c
      21              :  * @brief command for testing /reserves/$PID/purse
      22              :  * @author Christian Grothoff
      23              :  */
      24              : #include "taler/platform.h"
      25              : #include "taler/taler_json_lib.h"
      26              : #include <gnunet/gnunet_curl_lib.h>
      27              : #include "taler/taler_testing_lib.h"
      28              : #include "taler/taler_signatures.h"
      29              : #include "taler/backoff.h"
      30              : 
      31              : 
      32              : /**
      33              :  * State for a "purse create with merge" CMD.
      34              :  */
      35              : struct ReservePurseState
      36              : {
      37              : 
      38              :   /**
      39              :    * Merge time (local time when the command was
      40              :    * executed).
      41              :    */
      42              :   struct GNUNET_TIME_Timestamp merge_timestamp;
      43              : 
      44              :   /**
      45              :    * Account (reserve) private key.
      46              :    */
      47              :   union TALER_AccountPrivateKeyP account_priv;
      48              : 
      49              :   /**
      50              :    * Account (reserve) public key.
      51              :    */
      52              :   union TALER_AccountPublicKeyP account_pub;
      53              : 
      54              :   /**
      55              :    * Reserve signature generated for the request
      56              :    * (client-side).
      57              :    */
      58              :   struct TALER_ReserveSignatureP reserve_sig;
      59              : 
      60              :   /**
      61              :    * Private key of the purse.
      62              :    */
      63              :   struct TALER_PurseContractPrivateKeyP purse_priv;
      64              : 
      65              :   /**
      66              :    * Public key of the purse.
      67              :    */
      68              :   struct TALER_PurseContractPublicKeyP purse_pub;
      69              : 
      70              :   /**
      71              :    * Private key with the merge capability.
      72              :    */
      73              :   struct TALER_PurseMergePrivateKeyP merge_priv;
      74              : 
      75              :   /**
      76              :    * Public key of the merge capability.
      77              :    */
      78              :   struct TALER_PurseMergePublicKeyP merge_pub;
      79              : 
      80              :   /**
      81              :    * Private key to decrypt the contract.
      82              :    */
      83              :   struct TALER_ContractDiffiePrivateP contract_priv;
      84              : 
      85              :   /**
      86              :    * Handle while operation is running.
      87              :    */
      88              :   struct TALER_EXCHANGE_PurseCreateMergeHandle *dh;
      89              : 
      90              :   /**
      91              :    * When will the purse expire?
      92              :    */
      93              :   struct GNUNET_TIME_Relative expiration_rel;
      94              : 
      95              :   /**
      96              :    * When will the purse expire?
      97              :    */
      98              :   struct GNUNET_TIME_Timestamp purse_expiration;
      99              : 
     100              :   /**
     101              :    * Hash of the payto://-URI for the reserve we are
     102              :    * merging into.
     103              :    */
     104              :   struct TALER_NormalizedPaytoHashP h_payto;
     105              : 
     106              :   /**
     107              :    * Set to the KYC requirement row *if* the exchange replied with
     108              :    * a request for KYC.
     109              :    */
     110              :   uint64_t requirement_row;
     111              : 
     112              :   /**
     113              :    * Contract terms for the purse.
     114              :    */
     115              :   json_t *contract_terms;
     116              : 
     117              :   /**
     118              :    * Reference to the reserve, or NULL (!).
     119              :    */
     120              :   const char *reserve_ref;
     121              : 
     122              :   /**
     123              :    * Interpreter state.
     124              :    */
     125              :   struct TALER_TESTING_Interpreter *is;
     126              : 
     127              :   /**
     128              :    * Expected HTTP response code.
     129              :    */
     130              :   unsigned int expected_response_code;
     131              : 
     132              :   /**
     133              :    * True to pay the purse fee.
     134              :    */
     135              :   bool pay_purse_fee;
     136              : };
     137              : 
     138              : 
     139              : /**
     140              :  * Callback to analyze the /reserves/$PID/purse response, just used to check if
     141              :  * the response code is acceptable.
     142              :  *
     143              :  * @param cls closure.
     144              :  * @param dr purse response details
     145              :  */
     146              : static void
     147           14 : purse_cb (void *cls,
     148              :           const struct TALER_EXCHANGE_PurseCreateMergeResponse *dr)
     149              : {
     150           14 :   struct ReservePurseState *ds = cls;
     151              : 
     152           14 :   ds->dh = NULL;
     153           14 :   ds->reserve_sig = *dr->reserve_sig;
     154           14 :   if (ds->expected_response_code != dr->hr.http_status)
     155              :   {
     156            0 :     TALER_TESTING_unexpected_status (ds->is,
     157              :                                      dr->hr.http_status,
     158              :                                      ds->expected_response_code);
     159            0 :     return;
     160              :   }
     161           14 :   switch (dr->hr.http_status)
     162              :   {
     163            1 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     164              :     /* KYC required */
     165            1 :     ds->requirement_row =
     166            1 :       dr->details.unavailable_for_legal_reasons.requirement_row;
     167            1 :     GNUNET_break (0 ==
     168              :                   GNUNET_memcmp (
     169              :                     &ds->h_payto,
     170              :                     &dr->details.unavailable_for_legal_reasons.h_payto));
     171            1 :     break;
     172              :   }
     173           14 :   TALER_TESTING_interpreter_next (ds->is);
     174              : }
     175              : 
     176              : 
     177              : /**
     178              :  * Run the command.
     179              :  *
     180              :  * @param cls closure.
     181              :  * @param cmd the command to execute.
     182              :  * @param is the interpreter state.
     183              :  */
     184              : static void
     185           14 : purse_run (void *cls,
     186              :            const struct TALER_TESTING_Command *cmd,
     187              :            struct TALER_TESTING_Interpreter *is)
     188              : {
     189           14 :   struct ReservePurseState *ds = cls;
     190              :   const struct TALER_ReservePrivateKeyP *reserve_priv;
     191              :   const struct TALER_TESTING_Command *ref;
     192              : 
     193              :   (void) cmd;
     194           14 :   ds->is = is;
     195           14 :   ref = TALER_TESTING_interpreter_lookup_command (ds->is,
     196              :                                                   ds->reserve_ref);
     197           14 :   GNUNET_assert (NULL != ref);
     198           14 :   if (GNUNET_OK !=
     199           14 :       TALER_TESTING_get_trait_reserve_priv (ref,
     200              :                                             &reserve_priv))
     201              :   {
     202            0 :     GNUNET_break (0);
     203            0 :     TALER_TESTING_interpreter_fail (ds->is);
     204            0 :     return;
     205              :   }
     206           14 :   ds->account_priv.reserve_priv = *reserve_priv;
     207           14 :   GNUNET_CRYPTO_eddsa_key_create (
     208              :     &ds->purse_priv.eddsa_priv);
     209           14 :   GNUNET_CRYPTO_eddsa_key_get_public (
     210           14 :     &ds->purse_priv.eddsa_priv,
     211              :     &ds->purse_pub.eddsa_pub);
     212           14 :   GNUNET_CRYPTO_eddsa_key_get_public (
     213           14 :     &ds->account_priv.reserve_priv.eddsa_priv,
     214              :     &ds->account_pub.reserve_pub.eddsa_pub);
     215           14 :   GNUNET_CRYPTO_eddsa_key_create (
     216              :     &ds->merge_priv.eddsa_priv);
     217           14 :   GNUNET_CRYPTO_eddsa_key_get_public (
     218           14 :     &ds->merge_priv.eddsa_priv,
     219              :     &ds->merge_pub.eddsa_pub);
     220           14 :   GNUNET_CRYPTO_ecdhe_key_create (
     221              :     &ds->contract_priv.ecdhe_priv);
     222              :   ds->purse_expiration
     223           14 :     = GNUNET_TIME_absolute_to_timestamp (
     224              :         GNUNET_TIME_relative_to_absolute (
     225              :           ds->expiration_rel));
     226              : 
     227              :   {
     228              :     struct TALER_NormalizedPayto payto_uri;
     229              :     const char *exchange_url;
     230              :     const struct TALER_TESTING_Command *exchange_cmd;
     231              : 
     232           14 :     exchange_cmd = TALER_TESTING_interpreter_get_command (is,
     233              :                                                           "exchange");
     234           14 :     if (NULL == exchange_cmd)
     235              :     {
     236            0 :       GNUNET_break (0);
     237            0 :       TALER_TESTING_interpreter_fail (is);
     238            0 :       return;
     239              :     }
     240           14 :     GNUNET_assert (
     241              :       GNUNET_OK ==
     242              :       TALER_TESTING_get_trait_exchange_url (
     243              :         exchange_cmd,
     244              :         &exchange_url));
     245              :     payto_uri
     246           14 :       = TALER_reserve_make_payto (
     247              :           exchange_url,
     248           14 :           &ds->account_pub.reserve_pub);
     249           14 :     TALER_normalized_payto_hash (payto_uri,
     250              :                                  &ds->h_payto);
     251           14 :     GNUNET_free (payto_uri.normalized_payto);
     252              :   }
     253              : 
     254           14 :   GNUNET_assert (0 ==
     255              :                  json_object_set_new (
     256              :                    ds->contract_terms,
     257              :                    "pay_deadline",
     258              :                    GNUNET_JSON_from_timestamp (ds->purse_expiration)));
     259           14 :   ds->merge_timestamp = GNUNET_TIME_timestamp_get ();
     260           14 :   ds->dh = TALER_EXCHANGE_purse_create_with_merge (
     261              :     TALER_TESTING_interpreter_get_context (is),
     262              :     TALER_TESTING_get_exchange_url (is),
     263              :     TALER_TESTING_get_keys (is),
     264           14 :     &ds->account_priv.reserve_priv,
     265           14 :     &ds->purse_priv,
     266           14 :     &ds->merge_priv,
     267           14 :     &ds->contract_priv,
     268           14 :     ds->contract_terms,
     269              :     true /* upload contract */,
     270           14 :     ds->pay_purse_fee,
     271              :     ds->merge_timestamp,
     272              :     &purse_cb,
     273              :     ds);
     274           14 :   if (NULL == ds->dh)
     275              :   {
     276            0 :     GNUNET_break (0);
     277            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     278              :                 "Could not purse reserve\n");
     279            0 :     TALER_TESTING_interpreter_fail (is);
     280            0 :     return;
     281              :   }
     282              : }
     283              : 
     284              : 
     285              : /**
     286              :  * Free the state of a "purse" CMD, and possibly cancel a
     287              :  * pending operation thereof.
     288              :  *
     289              :  * @param cls closure, must be a `struct ReservePurseState`.
     290              :  * @param cmd the command which is being cleaned up.
     291              :  */
     292              : static void
     293           14 : purse_cleanup (void *cls,
     294              :                const struct TALER_TESTING_Command *cmd)
     295              : {
     296           14 :   struct ReservePurseState *ds = cls;
     297              : 
     298           14 :   if (NULL != ds->dh)
     299              :   {
     300            0 :     TALER_TESTING_command_incomplete (ds->is,
     301              :                                       cmd->label);
     302            0 :     TALER_EXCHANGE_purse_create_with_merge_cancel (ds->dh);
     303            0 :     ds->dh = NULL;
     304              :   }
     305           14 :   json_decref (ds->contract_terms);
     306           14 :   GNUNET_free (ds);
     307           14 : }
     308              : 
     309              : 
     310              : /**
     311              :  * Offer internal data from a "purse" CMD, to other commands.
     312              :  *
     313              :  * @param cls closure.
     314              :  * @param[out] ret result.
     315              :  * @param trait name of the trait.
     316              :  * @param index index number of the object to offer.
     317              :  * @return #GNUNET_OK on success.
     318              :  */
     319              : static enum GNUNET_GenericReturnValue
     320           51 : purse_traits (void *cls,
     321              :               const void **ret,
     322              :               const char *trait,
     323              :               unsigned int index)
     324              : {
     325           51 :   struct ReservePurseState *ds = cls;
     326              :   struct TALER_TESTING_Trait traits[] = {
     327           51 :     TALER_TESTING_make_trait_timestamp (
     328              :       0,
     329           51 :       &ds->merge_timestamp),
     330           51 :     TALER_TESTING_make_trait_contract_terms (
     331           51 :       ds->contract_terms),
     332           51 :     TALER_TESTING_make_trait_purse_priv (
     333           51 :       &ds->purse_priv),
     334           51 :     TALER_TESTING_make_trait_purse_pub (
     335           51 :       &ds->purse_pub),
     336           51 :     TALER_TESTING_make_trait_merge_priv (
     337           51 :       &ds->merge_priv),
     338           51 :     TALER_TESTING_make_trait_merge_pub (
     339           51 :       &ds->merge_pub),
     340           51 :     TALER_TESTING_make_trait_contract_priv (
     341           51 :       &ds->contract_priv),
     342           51 :     TALER_TESTING_make_trait_account_priv (
     343           51 :       &ds->account_priv),
     344           51 :     TALER_TESTING_make_trait_account_pub (
     345           51 :       &ds->account_pub),
     346           51 :     TALER_TESTING_make_trait_reserve_priv (
     347           51 :       &ds->account_priv.reserve_priv),
     348           51 :     TALER_TESTING_make_trait_reserve_pub (
     349           51 :       &ds->account_pub.reserve_pub),
     350           51 :     TALER_TESTING_make_trait_reserve_sig (
     351           51 :       &ds->reserve_sig),
     352           51 :     TALER_TESTING_make_trait_legi_requirement_row (
     353           51 :       &ds->requirement_row),
     354           51 :     TALER_TESTING_make_trait_h_normalized_payto (
     355           51 :       &ds->h_payto),
     356           51 :     TALER_TESTING_trait_end ()
     357              :   };
     358              : 
     359           51 :   return TALER_TESTING_get_trait (traits,
     360              :                                   ret,
     361              :                                   trait,
     362              :                                   index);
     363              : }
     364              : 
     365              : 
     366              : struct TALER_TESTING_Command
     367           14 : TALER_TESTING_cmd_purse_create_with_reserve (
     368              :   const char *label,
     369              :   unsigned int expected_http_status,
     370              :   const char *contract_terms,
     371              :   bool upload_contract,
     372              :   bool pay_purse_fee,
     373              :   struct GNUNET_TIME_Relative expiration,
     374              :   const char *reserve_ref)
     375              : {
     376              :   struct ReservePurseState *ds;
     377              :   json_error_t err;
     378              : 
     379           14 :   ds = GNUNET_new (struct ReservePurseState);
     380           14 :   ds->expiration_rel = expiration;
     381           14 :   ds->contract_terms = json_loads (contract_terms,
     382              :                                    0 /* flags */,
     383              :                                    &err);
     384           14 :   GNUNET_assert (NULL != ds->contract_terms);
     385           14 :   ds->pay_purse_fee = pay_purse_fee;
     386           14 :   ds->reserve_ref = reserve_ref;
     387           14 :   ds->expected_response_code = expected_http_status;
     388              : 
     389              :   {
     390           14 :     struct TALER_TESTING_Command cmd = {
     391              :       .cls = ds,
     392              :       .label = label,
     393              :       .run = &purse_run,
     394              :       .cleanup = &purse_cleanup,
     395              :       .traits = &purse_traits
     396              :     };
     397              : 
     398           14 :     return cmd;
     399              :   }
     400              : }
     401              : 
     402              : 
     403              : /* end of testing_api_cmd_reserve_purse.c */
        

Generated by: LCOV version 2.0-1