exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

exchange_api_get-purses-PURSE_PUB-merge.c (10739B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022-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
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file lib/exchange_api_get-purses-PURSE_PUB-merge.c
     19  * @brief Implementation of the /purses/ GET request
     20  * @author Christian Grothoff
     21  */
     22 #include <jansson.h>
     23 #include <microhttpd.h> /* just for HTTP status codes */
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_json_lib.h>
     26 #include <gnunet/gnunet_curl_lib.h>
     27 #include "taler/taler_json_lib.h"
     28 #include "exchange_api_handle.h"
     29 #include "taler/taler_signatures.h"
     30 #include "exchange_api_curl_defaults.h"
     31 
     32 
     33 /**
     34  * @brief A GET /purses/$PURSE_PUB/merge Handle
     35  */
     36 struct TALER_EXCHANGE_GetPursesHandle
     37 {
     38 
     39   /**
     40    * Base URL of the exchange.
     41    */
     42   char *base_url;
     43 
     44   /**
     45    * The url for this request.
     46    */
     47   char *url;
     48 
     49   /**
     50    * The keys of the exchange this request handle will use.
     51    */
     52   struct TALER_EXCHANGE_Keys *keys;
     53 
     54   /**
     55    * Handle for the request.
     56    */
     57   struct GNUNET_CURL_Job *job;
     58 
     59   /**
     60    * Function to call with the result.
     61    */
     62   TALER_EXCHANGE_GetPursesCallback cb;
     63 
     64   /**
     65    * Closure for @e cb.
     66    */
     67   TALER_EXCHANGE_GET_PURSES_RESULT_CLOSURE *cb_cls;
     68 
     69   /**
     70    * CURL context to use.
     71    */
     72   struct GNUNET_CURL_Context *ctx;
     73 
     74   /**
     75    * Public key of the purse being queried.
     76    */
     77   struct TALER_PurseContractPublicKeyP purse_pub;
     78 
     79   /**
     80    * Options for the request.
     81    */
     82   struct
     83   {
     84     /**
     85      * How long to wait for a change to happen.
     86      */
     87     struct GNUNET_TIME_Relative timeout;
     88 
     89     /**
     90      * True to wait for a merge event, false to wait for a deposit event.
     91      */
     92     bool wait_for_merge;
     93   } options;
     94 
     95 };
     96 
     97 
     98 /**
     99  * Function called when we're done processing the
    100  * HTTP /purses/$PID GET request.
    101  *
    102  * @param cls the `struct TALER_EXCHANGE_GetPursesHandle`
    103  * @param response_code HTTP response code, 0 on error
    104  * @param response parsed JSON result, NULL on error
    105  */
    106 static void
    107 handle_purse_get_finished (void *cls,
    108                            long response_code,
    109                            const void *response)
    110 {
    111   struct TALER_EXCHANGE_GetPursesHandle *gph = cls;
    112   const json_t *j = response;
    113   struct TALER_EXCHANGE_GetPursesResponse dr = {
    114     .hr.reply = j,
    115     .hr.http_status = (unsigned int) response_code
    116   };
    117 
    118   gph->job = NULL;
    119   switch (response_code)
    120   {
    121   case 0:
    122     dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    123     break;
    124   case MHD_HTTP_OK:
    125     {
    126       bool no_merge = false;
    127       bool no_deposit = false;
    128       struct TALER_ExchangePublicKeyP exchange_pub;
    129       struct TALER_ExchangeSignatureP exchange_sig;
    130       struct GNUNET_JSON_Specification spec[] = {
    131         GNUNET_JSON_spec_mark_optional (
    132           GNUNET_JSON_spec_timestamp ("merge_timestamp",
    133                                       &dr.details.ok.merge_timestamp),
    134           &no_merge),
    135         GNUNET_JSON_spec_mark_optional (
    136           GNUNET_JSON_spec_timestamp ("deposit_timestamp",
    137                                       &dr.details.ok.deposit_timestamp),
    138           &no_deposit),
    139         TALER_JSON_spec_amount_any ("balance",
    140                                     &dr.details.ok.balance),
    141         GNUNET_JSON_spec_timestamp ("purse_expiration",
    142                                     &dr.details.ok.purse_expiration),
    143         GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    144                                      &exchange_pub),
    145         GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    146                                      &exchange_sig),
    147         GNUNET_JSON_spec_end ()
    148       };
    149 
    150       if (GNUNET_OK !=
    151           GNUNET_JSON_parse (j,
    152                              spec,
    153                              NULL, NULL))
    154       {
    155         GNUNET_break_op (0);
    156         dr.hr.http_status = 0;
    157         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    158         break;
    159       }
    160       if (GNUNET_OK !=
    161           TALER_EXCHANGE_test_signing_key (gph->keys,
    162                                            &exchange_pub))
    163       {
    164         GNUNET_break_op (0);
    165         dr.hr.http_status = 0;
    166         dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE;
    167         break;
    168       }
    169       if (GNUNET_OK !=
    170           TALER_exchange_online_purse_status_verify (
    171             dr.details.ok.merge_timestamp,
    172             dr.details.ok.deposit_timestamp,
    173             &dr.details.ok.balance,
    174             &exchange_pub,
    175             &exchange_sig))
    176       {
    177         GNUNET_break_op (0);
    178         dr.hr.http_status = 0;
    179         dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE;
    180         break;
    181       }
    182       gph->cb (gph->cb_cls,
    183                &dr);
    184       TALER_EXCHANGE_get_purses_cancel (gph);
    185       return;
    186     }
    187   case MHD_HTTP_BAD_REQUEST:
    188     dr.hr.ec = TALER_JSON_get_error_code (j);
    189     dr.hr.hint = TALER_JSON_get_error_hint (j);
    190     /* This should never happen, either us or the exchange is buggy
    191        (or API version conflict); just pass JSON reply to the application */
    192     break;
    193   case MHD_HTTP_FORBIDDEN:
    194     dr.hr.ec = TALER_JSON_get_error_code (j);
    195     dr.hr.hint = TALER_JSON_get_error_hint (j);
    196     /* Nothing really to verify, exchange says one of the signatures is
    197        invalid; as we checked them, this should never happen, we
    198        should pass the JSON reply to the application */
    199     break;
    200   case MHD_HTTP_NOT_FOUND:
    201     dr.hr.ec = TALER_JSON_get_error_code (j);
    202     dr.hr.hint = TALER_JSON_get_error_hint (j);
    203     /* Exchange does not know about transaction;
    204        we should pass the reply to the application */
    205     break;
    206   case MHD_HTTP_GONE:
    207     /* purse expired */
    208     dr.hr.ec = TALER_JSON_get_error_code (j);
    209     dr.hr.hint = TALER_JSON_get_error_hint (j);
    210     break;
    211   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    212     dr.hr.ec = TALER_JSON_get_error_code (j);
    213     dr.hr.hint = TALER_JSON_get_error_hint (j);
    214     /* Server had an internal issue; we should retry, but this API
    215        leaves this to the application */
    216     break;
    217   default:
    218     /* unexpected response code */
    219     dr.hr.ec = TALER_JSON_get_error_code (j);
    220     dr.hr.hint = TALER_JSON_get_error_hint (j);
    221     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    222                 "Unexpected response code %u/%d for exchange GET purses\n",
    223                 (unsigned int) response_code,
    224                 (int) dr.hr.ec);
    225     GNUNET_break_op (0);
    226     break;
    227   }
    228   gph->cb (gph->cb_cls,
    229            &dr);
    230   TALER_EXCHANGE_get_purses_cancel (gph);
    231 }
    232 
    233 
    234 struct TALER_EXCHANGE_GetPursesHandle *
    235 TALER_EXCHANGE_get_purses_create (
    236   struct GNUNET_CURL_Context *ctx,
    237   const char *url,
    238   struct TALER_EXCHANGE_Keys *keys,
    239   const struct TALER_PurseContractPublicKeyP *purse_pub)
    240 {
    241   struct TALER_EXCHANGE_GetPursesHandle *gph;
    242 
    243   gph = GNUNET_new (struct TALER_EXCHANGE_GetPursesHandle);
    244   gph->ctx = ctx;
    245   gph->base_url = GNUNET_strdup (url);
    246   gph->keys = TALER_EXCHANGE_keys_incref (keys);
    247   gph->purse_pub = *purse_pub;
    248   return gph;
    249 }
    250 
    251 
    252 enum GNUNET_GenericReturnValue
    253 TALER_EXCHANGE_get_purses_set_options_ (
    254   struct TALER_EXCHANGE_GetPursesHandle *gph,
    255   unsigned int num_options,
    256   const struct TALER_EXCHANGE_GetPursesOptionValue *options)
    257 {
    258   for (unsigned int i = 0; i < num_options; i++)
    259   {
    260     switch (options[i].option)
    261     {
    262     case TALER_EXCHANGE_GET_PURSES_OPTION_END:
    263       return GNUNET_OK;
    264     case TALER_EXCHANGE_GET_PURSES_OPTION_TIMEOUT:
    265       gph->options.timeout = options[i].details.timeout;
    266       break;
    267     case TALER_EXCHANGE_GET_PURSES_OPTION_WAIT_FOR_MERGE:
    268       gph->options.wait_for_merge = options[i].details.wait_for_merge;
    269       break;
    270     default:
    271       GNUNET_break (0);
    272       return GNUNET_SYSERR;
    273     }
    274   }
    275   return GNUNET_OK;
    276 }
    277 
    278 
    279 enum TALER_ErrorCode
    280 TALER_EXCHANGE_get_purses_start (
    281   struct TALER_EXCHANGE_GetPursesHandle *gph,
    282   TALER_EXCHANGE_GetPursesCallback cb,
    283   TALER_EXCHANGE_GET_PURSES_RESULT_CLOSURE *cb_cls)
    284 {
    285   char arg_str[sizeof (gph->purse_pub) * 2 + 64];
    286   CURL *eh;
    287   unsigned int tms
    288     = (unsigned int) gph->options.timeout.rel_value_us
    289       / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
    290 
    291   if (NULL != gph->job)
    292   {
    293     GNUNET_break (0);
    294     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    295   }
    296   gph->cb = cb;
    297   gph->cb_cls = cb_cls;
    298   {
    299     char cpub_str[sizeof (gph->purse_pub) * 2];
    300     char *end;
    301     char timeout_str[32];
    302 
    303     end = GNUNET_STRINGS_data_to_string (&gph->purse_pub,
    304                                          sizeof (gph->purse_pub),
    305                                          cpub_str,
    306                                          sizeof (cpub_str));
    307     *end = '\0';
    308     GNUNET_snprintf (timeout_str,
    309                      sizeof (timeout_str),
    310                      "%u",
    311                      tms);
    312     GNUNET_snprintf (arg_str,
    313                      sizeof (arg_str),
    314                      "purses/%s/%s",
    315                      cpub_str,
    316                      gph->options.wait_for_merge
    317                      ? "merge"
    318                      : "deposit");
    319     gph->url = TALER_url_join (gph->base_url,
    320                                arg_str,
    321                                "timeout_ms",
    322                                (0 == tms)
    323                                 ? NULL
    324                                 : timeout_str,
    325                                NULL);
    326   }
    327   if (NULL == gph->url)
    328     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    329   eh = TALER_EXCHANGE_curl_easy_get_ (gph->url);
    330   if (NULL == eh)
    331     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    332   if (0 != tms)
    333   {
    334     GNUNET_break (CURLE_OK ==
    335                   curl_easy_setopt (eh,
    336                                     CURLOPT_TIMEOUT_MS,
    337                                     (long) (tms + 100L)));
    338   }
    339   gph->job = GNUNET_CURL_job_add (gph->ctx,
    340                                   eh,
    341                                   &handle_purse_get_finished,
    342                                   gph);
    343   if (NULL == gph->job)
    344     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    345   return TALER_EC_NONE;
    346 }
    347 
    348 
    349 void
    350 TALER_EXCHANGE_get_purses_cancel (
    351   struct TALER_EXCHANGE_GetPursesHandle *gph)
    352 {
    353   if (NULL != gph->job)
    354   {
    355     GNUNET_CURL_job_cancel (gph->job);
    356     gph->job = NULL;
    357   }
    358   GNUNET_free (gph->url);
    359   GNUNET_free (gph->base_url);
    360   TALER_EXCHANGE_keys_decref (gph->keys);
    361   GNUNET_free (gph);
    362 }
    363 
    364 
    365 /* end of exchange_api_get-purses-PURSE_PUB-merge.c */