LCOV - code coverage report
Current view: top level - lib - exchange_api_purse_merge.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 54.4 % 160 87
Test Date: 2026-01-24 09:31:50 Functions: 100.0 % 3 3

            Line data    Source code
       1              : /*
       2              :    This file is part of TALER
       3              :    Copyright (C) 2022-2023 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
      15              :    <http://www.gnu.org/licenses/>
      16              :  */
      17              : /**
      18              :  * @file lib/exchange_api_purse_merge.c
      19              :  * @brief Implementation of the client to merge a purse
      20              :  *        into an account
      21              :  * @author Christian Grothoff
      22              :  */
      23              : #include "taler/platform.h"
      24              : #include <jansson.h>
      25              : #include <microhttpd.h> /* just for HTTP status codes */
      26              : #include <gnunet/gnunet_util_lib.h>
      27              : #include <gnunet/gnunet_json_lib.h>
      28              : #include <gnunet/gnunet_curl_lib.h>
      29              : #include "taler/taler_json_lib.h"
      30              : #include "taler/taler_exchange_service.h"
      31              : #include "exchange_api_handle.h"
      32              : #include "exchange_api_common.h"
      33              : #include "taler/taler_signatures.h"
      34              : #include "exchange_api_curl_defaults.h"
      35              : 
      36              : 
      37              : /**
      38              :  * @brief A purse merge with deposit handle
      39              :  */
      40              : struct TALER_EXCHANGE_AccountMergeHandle
      41              : {
      42              : 
      43              :   /**
      44              :    * The keys of the exchange this request handle will use
      45              :    */
      46              :   struct TALER_EXCHANGE_Keys *keys;
      47              : 
      48              :   /**
      49              :    * The url for this request.
      50              :    */
      51              :   char *url;
      52              : 
      53              :   /**
      54              :    * Context for #TEH_curl_easy_post(). Keeps the data that must
      55              :    * persist for Curl to make the upload.
      56              :    */
      57              :   struct TALER_CURL_PostContext ctx;
      58              : 
      59              :   /**
      60              :    * Handle for the request.
      61              :    */
      62              :   struct GNUNET_CURL_Job *job;
      63              : 
      64              :   /**
      65              :    * Function to call with the result.
      66              :    */
      67              :   TALER_EXCHANGE_AccountMergeCallback cb;
      68              : 
      69              :   /**
      70              :    * Closure for @a cb.
      71              :    */
      72              :   void *cb_cls;
      73              : 
      74              :   /**
      75              :    * Base URL of the provider hosting the @e reserve_pub.
      76              :    */
      77              :   char *provider_url;
      78              : 
      79              :   /**
      80              :    * Signature for our operation.
      81              :    */
      82              :   struct TALER_PurseMergeSignatureP merge_sig;
      83              : 
      84              :   /**
      85              :    * Expected value in the purse after fees.
      86              :    */
      87              :   struct TALER_Amount purse_value_after_fees;
      88              : 
      89              :   /**
      90              :    * Public key of the reserve public key.
      91              :    */
      92              :   struct TALER_ReservePublicKeyP reserve_pub;
      93              : 
      94              :   /**
      95              :    * Public key of the purse.
      96              :    */
      97              :   struct TALER_PurseContractPublicKeyP purse_pub;
      98              : 
      99              :   /**
     100              :    * Hash over the purse's contrac terms.
     101              :    */
     102              :   struct TALER_PrivateContractHashP h_contract_terms;
     103              : 
     104              :   /**
     105              :    * When does the purse expire.
     106              :    */
     107              :   struct GNUNET_TIME_Timestamp purse_expiration;
     108              : 
     109              :   /**
     110              :    * Our merge key.
     111              :    */
     112              :   struct TALER_PurseMergePrivateKeyP merge_priv;
     113              : 
     114              :   /**
     115              :    * Reserve signature affirming the merge.
     116              :    */
     117              :   struct TALER_ReserveSignatureP reserve_sig;
     118              : 
     119              : };
     120              : 
     121              : 
     122              : /**
     123              :  * Function called when we're done processing the
     124              :  * HTTP /purse/$PID/merge request.
     125              :  *
     126              :  * @param cls the `struct TALER_EXCHANGE_AccountMergeHandle`
     127              :  * @param response_code HTTP response code, 0 on error
     128              :  * @param response parsed JSON result, NULL on error
     129              :  */
     130              : static void
     131            6 : handle_purse_merge_finished (void *cls,
     132              :                              long response_code,
     133              :                              const void *response)
     134              : {
     135            6 :   struct TALER_EXCHANGE_AccountMergeHandle *pch = cls;
     136            6 :   const json_t *j = response;
     137            6 :   struct TALER_EXCHANGE_AccountMergeResponse dr = {
     138              :     .hr.reply = j,
     139            6 :     .hr.http_status = (unsigned int) response_code,
     140            6 :     .reserve_sig = &pch->reserve_sig
     141              :   };
     142              : 
     143            6 :   pch->job = NULL;
     144            6 :   switch (response_code)
     145              :   {
     146            0 :   case 0:
     147            0 :     dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     148            0 :     break;
     149            3 :   case MHD_HTTP_OK:
     150              :     {
     151              :       struct TALER_Amount total_deposited;
     152              :       struct GNUNET_JSON_Specification spec[] = {
     153            3 :         GNUNET_JSON_spec_fixed_auto ("exchange_sig",
     154              :                                      &dr.details.ok.exchange_sig),
     155            3 :         GNUNET_JSON_spec_fixed_auto ("exchange_pub",
     156              :                                      &dr.details.ok.exchange_pub),
     157            3 :         GNUNET_JSON_spec_timestamp ("exchange_timestamp",
     158              :                                     &dr.details.ok.etime),
     159            3 :         TALER_JSON_spec_amount ("merge_amount",
     160            3 :                                 pch->purse_value_after_fees.currency,
     161              :                                 &total_deposited),
     162            3 :         GNUNET_JSON_spec_end ()
     163              :       };
     164              : 
     165            3 :       if (GNUNET_OK !=
     166            3 :           GNUNET_JSON_parse (j,
     167              :                              spec,
     168              :                              NULL, NULL))
     169              :       {
     170            0 :         GNUNET_break_op (0);
     171            0 :         dr.hr.http_status = 0;
     172            0 :         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     173            0 :         break;
     174              :       }
     175            3 :       if (GNUNET_OK !=
     176            3 :           TALER_EXCHANGE_test_signing_key (pch->keys,
     177              :                                            &dr.details.ok.exchange_pub))
     178              :       {
     179            0 :         GNUNET_break_op (0);
     180            0 :         dr.hr.http_status = 0;
     181            0 :         dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
     182            0 :         break;
     183              :       }
     184            3 :       if (GNUNET_OK !=
     185            3 :           TALER_exchange_online_purse_merged_verify (
     186              :             dr.details.ok.etime,
     187              :             pch->purse_expiration,
     188            3 :             &pch->purse_value_after_fees,
     189            3 :             &pch->purse_pub,
     190            3 :             &pch->h_contract_terms,
     191            3 :             &pch->reserve_pub,
     192            3 :             pch->provider_url,
     193              :             &dr.details.ok.exchange_pub,
     194              :             &dr.details.ok.exchange_sig))
     195              :       {
     196            0 :         GNUNET_break_op (0);
     197            0 :         dr.hr.http_status = 0;
     198            0 :         dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
     199            0 :         break;
     200              :       }
     201              :     }
     202            3 :     break;
     203            0 :   case MHD_HTTP_BAD_REQUEST:
     204              :     /* This should never happen, either us or the exchange is buggy
     205              :        (or API version conflict); just pass JSON reply to the application */
     206            0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     207            0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     208            0 :     break;
     209            0 :   case MHD_HTTP_PAYMENT_REQUIRED:
     210              :     /* purse was not (yet) full */
     211            0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     212            0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     213            0 :     break;
     214            0 :   case MHD_HTTP_FORBIDDEN:
     215            0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     216            0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     217              :     /* Nothing really to verify, exchange says one of the signatures is
     218              :        invalid; as we checked them, this should never happen, we
     219              :        should pass the JSON reply to the application */
     220            0 :     break;
     221            0 :   case MHD_HTTP_NOT_FOUND:
     222            0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     223            0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     224              :     /* Nothing really to verify, this should never
     225              :        happen, we should pass the JSON reply to the application */
     226            0 :     break;
     227            2 :   case MHD_HTTP_CONFLICT:
     228              :     {
     229              :       struct TALER_PurseMergePublicKeyP merge_pub;
     230              : 
     231            2 :       GNUNET_CRYPTO_eddsa_key_get_public (&pch->merge_priv.eddsa_priv,
     232              :                                           &merge_pub.eddsa_pub);
     233            2 :       if (GNUNET_OK !=
     234            2 :           TALER_EXCHANGE_check_purse_merge_conflict_ (
     235            2 :             &pch->merge_sig,
     236              :             &merge_pub,
     237            2 :             &pch->purse_pub,
     238            2 :             pch->provider_url,
     239              :             j))
     240              :       {
     241            0 :         GNUNET_break_op (0);
     242            0 :         dr.hr.http_status = 0;
     243            0 :         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     244            0 :         break;
     245              :       }
     246            2 :       break;
     247              :     }
     248              :     break;
     249            0 :   case MHD_HTTP_GONE:
     250              :     /* could happen if denomination was revoked */
     251              :     /* Note: one might want to check /keys for revocation
     252              :        signature here, alas tricky in case our /keys
     253              :        is outdated => left to clients */
     254            0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     255            0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     256            0 :     break;
     257            1 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     258              :     {
     259              :       struct GNUNET_JSON_Specification spec[] = {
     260            1 :         GNUNET_JSON_spec_uint64 (
     261              :           "requirement_row",
     262              :           &dr.details.unavailable_for_legal_reasons.requirement_row),
     263            1 :         GNUNET_JSON_spec_end ()
     264              :       };
     265              : 
     266            1 :       if (GNUNET_OK !=
     267            1 :           GNUNET_JSON_parse (j,
     268              :                              spec,
     269              :                              NULL, NULL))
     270              :       {
     271            0 :         GNUNET_break_op (0);
     272            0 :         dr.hr.http_status = 0;
     273            0 :         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     274            0 :         break;
     275              :       }
     276              :     }
     277            1 :     break;
     278            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     279            0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     280            0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     281              :     /* Server had an internal issue; we should retry, but this API
     282              :        leaves this to the application */
     283            0 :     break;
     284            0 :   default:
     285              :     /* unexpected response code */
     286            0 :     dr.hr.ec = TALER_JSON_get_error_code (j);
     287            0 :     dr.hr.hint = TALER_JSON_get_error_hint (j);
     288            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     289              :                 "Unexpected response code %u/%d for exchange deposit\n",
     290              :                 (unsigned int) response_code,
     291              :                 dr.hr.ec);
     292            0 :     GNUNET_break_op (0);
     293            0 :     break;
     294              :   }
     295            6 :   pch->cb (pch->cb_cls,
     296              :            &dr);
     297            6 :   TALER_EXCHANGE_account_merge_cancel (pch);
     298            6 : }
     299              : 
     300              : 
     301              : struct TALER_EXCHANGE_AccountMergeHandle *
     302            6 : TALER_EXCHANGE_account_merge (
     303              :   struct GNUNET_CURL_Context *ctx,
     304              :   const char *url,
     305              :   struct TALER_EXCHANGE_Keys *keys,
     306              :   const char *reserve_exchange_url,
     307              :   const struct TALER_ReservePrivateKeyP *reserve_priv,
     308              :   const struct TALER_PurseContractPublicKeyP *purse_pub,
     309              :   const struct TALER_PurseMergePrivateKeyP *merge_priv,
     310              :   const struct TALER_PrivateContractHashP *h_contract_terms,
     311              :   uint8_t min_age,
     312              :   const struct TALER_Amount *purse_value_after_fees,
     313              :   struct GNUNET_TIME_Timestamp purse_expiration,
     314              :   struct GNUNET_TIME_Timestamp merge_timestamp,
     315              :   TALER_EXCHANGE_AccountMergeCallback cb,
     316              :   void *cb_cls)
     317              : {
     318              :   struct TALER_EXCHANGE_AccountMergeHandle *pch;
     319              :   json_t *merge_obj;
     320              :   CURL *eh;
     321              :   char arg_str[sizeof (pch->purse_pub) * 2 + 32];
     322              :   struct TALER_NormalizedPayto reserve_url;
     323              : 
     324            6 :   pch = GNUNET_new (struct TALER_EXCHANGE_AccountMergeHandle);
     325            6 :   pch->merge_priv = *merge_priv;
     326            6 :   pch->cb = cb;
     327            6 :   pch->cb_cls = cb_cls;
     328            6 :   pch->purse_pub = *purse_pub;
     329            6 :   pch->h_contract_terms = *h_contract_terms;
     330            6 :   pch->purse_expiration = purse_expiration;
     331            6 :   pch->purse_value_after_fees = *purse_value_after_fees;
     332            6 :   if (NULL == reserve_exchange_url)
     333            6 :     pch->provider_url = GNUNET_strdup (url);
     334              :   else
     335            0 :     pch->provider_url = GNUNET_strdup (reserve_exchange_url);
     336            6 :   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
     337              :                                       &pch->reserve_pub.eddsa_pub);
     338              : 
     339              :   {
     340              :     char pub_str[sizeof (*purse_pub) * 2];
     341              :     char *end;
     342              : 
     343            6 :     end = GNUNET_STRINGS_data_to_string (
     344              :       purse_pub,
     345              :       sizeof (*purse_pub),
     346              :       pub_str,
     347              :       sizeof (pub_str));
     348            6 :     *end = '\0';
     349            6 :     GNUNET_snprintf (arg_str,
     350              :                      sizeof (arg_str),
     351              :                      "purses/%s/merge",
     352              :                      pub_str);
     353              :   }
     354            6 :   reserve_url = TALER_reserve_make_payto (pch->provider_url,
     355            6 :                                           &pch->reserve_pub);
     356            6 :   if (NULL == reserve_url.normalized_payto)
     357              :   {
     358            0 :     GNUNET_break (0);
     359            0 :     GNUNET_free (pch->provider_url);
     360            0 :     GNUNET_free (pch);
     361            0 :     return NULL;
     362              :   }
     363            6 :   pch->url = TALER_url_join (url,
     364              :                              arg_str,
     365              :                              NULL);
     366            6 :   if (NULL == pch->url)
     367              :   {
     368            0 :     GNUNET_break (0);
     369            0 :     GNUNET_free (reserve_url.normalized_payto);
     370            0 :     GNUNET_free (pch->provider_url);
     371            0 :     GNUNET_free (pch);
     372            0 :     return NULL;
     373              :   }
     374            6 :   TALER_wallet_purse_merge_sign (reserve_url,
     375              :                                  merge_timestamp,
     376              :                                  purse_pub,
     377              :                                  merge_priv,
     378              :                                  &pch->merge_sig);
     379              :   {
     380              :     struct TALER_Amount zero_purse_fee;
     381              : 
     382            6 :     GNUNET_assert (GNUNET_OK ==
     383              :                    TALER_amount_set_zero (purse_value_after_fees->currency,
     384              :                                           &zero_purse_fee));
     385            6 :     TALER_wallet_account_merge_sign (merge_timestamp,
     386              :                                      purse_pub,
     387              :                                      purse_expiration,
     388              :                                      h_contract_terms,
     389              :                                      purse_value_after_fees,
     390              :                                      &zero_purse_fee,
     391              :                                      min_age,
     392              :                                      TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
     393              :                                      reserve_priv,
     394              :                                      &pch->reserve_sig);
     395              :   }
     396            6 :   merge_obj = GNUNET_JSON_PACK (
     397              :     TALER_JSON_pack_normalized_payto ("payto_uri",
     398              :                                       reserve_url),
     399              :     GNUNET_JSON_pack_data_auto ("merge_sig",
     400              :                                 &pch->merge_sig),
     401              :     GNUNET_JSON_pack_data_auto ("reserve_sig",
     402              :                                 &pch->reserve_sig),
     403              :     GNUNET_JSON_pack_timestamp ("merge_timestamp",
     404              :                                 merge_timestamp));
     405            6 :   GNUNET_assert (NULL != merge_obj);
     406            6 :   GNUNET_free (reserve_url.normalized_payto);
     407            6 :   eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
     408           12 :   if ( (NULL == eh) ||
     409              :        (GNUNET_OK !=
     410            6 :         TALER_curl_easy_post (&pch->ctx,
     411              :                               eh,
     412              :                               merge_obj)) )
     413              :   {
     414            0 :     GNUNET_break (0);
     415            0 :     if (NULL != eh)
     416            0 :       curl_easy_cleanup (eh);
     417            0 :     json_decref (merge_obj);
     418            0 :     GNUNET_free (pch->provider_url);
     419            0 :     GNUNET_free (pch->url);
     420            0 :     GNUNET_free (pch);
     421            0 :     return NULL;
     422              :   }
     423            6 :   json_decref (merge_obj);
     424            6 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     425              :               "URL for purse merge: `%s'\n",
     426              :               pch->url);
     427            6 :   pch->keys = TALER_EXCHANGE_keys_incref (keys);
     428           12 :   pch->job = GNUNET_CURL_job_add2 (ctx,
     429              :                                    eh,
     430            6 :                                    pch->ctx.headers,
     431              :                                    &handle_purse_merge_finished,
     432              :                                    pch);
     433            6 :   return pch;
     434              : }
     435              : 
     436              : 
     437              : void
     438            6 : TALER_EXCHANGE_account_merge_cancel (
     439              :   struct TALER_EXCHANGE_AccountMergeHandle *pch)
     440              : {
     441            6 :   if (NULL != pch->job)
     442              :   {
     443            0 :     GNUNET_CURL_job_cancel (pch->job);
     444            0 :     pch->job = NULL;
     445              :   }
     446            6 :   GNUNET_free (pch->url);
     447            6 :   GNUNET_free (pch->provider_url);
     448            6 :   TALER_curl_easy_post_finished (&pch->ctx);
     449            6 :   TALER_EXCHANGE_keys_decref (pch->keys);
     450            6 :   GNUNET_free (pch);
     451            6 : }
     452              : 
     453              : 
     454              : /* end of exchange_api_purse_merge.c */
        

Generated by: LCOV version 2.0-1