exchange

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

exchange_api_post-reserves-RESERVE_PUB-close.c (12177B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-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_post-reserves-RESERVE_PUB-close.c
     19  * @brief Implementation of the POST /reserves/$RESERVE_PUB/close requests
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include <jansson.h>
     24 #include <microhttpd.h> /* just for HTTP close codes */
     25 #include <gnunet/gnunet_util_lib.h>
     26 #include <gnunet/gnunet_json_lib.h>
     27 #include <gnunet/gnunet_curl_lib.h>
     28 #include "taler/taler_exchange_service.h"
     29 #include "taler/taler_json_lib.h"
     30 #include "exchange_api_handle.h"
     31 #include "taler/taler_signatures.h"
     32 #include "exchange_api_curl_defaults.h"
     33 
     34 
     35 /**
     36  * @brief A POST /reserves/$RID/close Handle
     37  */
     38 struct TALER_EXCHANGE_PostReservesCloseHandle
     39 {
     40 
     41   /**
     42    * Reference to the execution context.
     43    */
     44   struct GNUNET_CURL_Context *ctx;
     45 
     46   /**
     47    * Base URL of the exchange.
     48    */
     49   char *base_url;
     50 
     51   /**
     52    * The url for this request, set during _start.
     53    */
     54   char *url;
     55 
     56   /**
     57    * Context for #TEH_curl_easy_post(). Keeps the data that must
     58    * persist for Curl to make the upload.
     59    */
     60   struct TALER_CURL_PostContext post_ctx;
     61 
     62   /**
     63    * Handle for the request.
     64    */
     65   struct GNUNET_CURL_Job *job;
     66 
     67   /**
     68    * Function to call with the result.
     69    */
     70   TALER_EXCHANGE_PostReservesCloseCallback cb;
     71 
     72   /**
     73    * Closure for @a cb.
     74    */
     75   TALER_EXCHANGE_POST_RESERVES_CLOSE_RESULT_CLOSURE *cb_cls;
     76 
     77   /**
     78    * Private key of the reserve.
     79    */
     80   struct TALER_ReservePrivateKeyP reserve_priv;
     81 
     82   /**
     83    * Public key of the reserve we are querying.
     84    */
     85   struct TALER_ReservePublicKeyP reserve_pub;
     86 
     87   /**
     88    * Target payto URI to send the reserve balance to (optional).
     89    */
     90   struct TALER_FullPayto target_payto_uri;
     91 
     92   /**
     93    * When did we make the request.
     94    */
     95   struct GNUNET_TIME_Timestamp ts;
     96 
     97 };
     98 
     99 
    100 /**
    101  * We received an #MHD_HTTP_OK close code. Handle the JSON
    102  * response.
    103  *
    104  * @param prch handle of the request
    105  * @param j JSON response
    106  * @return #GNUNET_OK on success
    107  */
    108 static enum GNUNET_GenericReturnValue
    109 handle_reserves_close_ok (struct TALER_EXCHANGE_PostReservesCloseHandle *prch,
    110                           const json_t *j)
    111 {
    112   struct TALER_EXCHANGE_PostReservesCloseResponse rs = {
    113     .hr.reply = j,
    114     .hr.http_status = MHD_HTTP_OK,
    115   };
    116   struct GNUNET_JSON_Specification spec[] = {
    117     TALER_JSON_spec_amount_any ("wire_amount",
    118                                 &rs.details.ok.wire_amount),
    119     GNUNET_JSON_spec_end ()
    120   };
    121 
    122   if (GNUNET_OK !=
    123       GNUNET_JSON_parse (j,
    124                          spec,
    125                          NULL,
    126                          NULL))
    127   {
    128     GNUNET_break_op (0);
    129     return GNUNET_SYSERR;
    130   }
    131   prch->cb (prch->cb_cls,
    132             &rs);
    133   prch->cb = NULL;
    134   GNUNET_JSON_parse_free (spec);
    135   return GNUNET_OK;
    136 }
    137 
    138 
    139 /**
    140  * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS close code. Handle the JSON
    141  * response.
    142  *
    143  * @param prch handle of the request
    144  * @param j JSON response
    145  * @return #GNUNET_OK on success
    146  */
    147 static enum GNUNET_GenericReturnValue
    148 handle_reserves_close_kyc (struct TALER_EXCHANGE_PostReservesCloseHandle *prch,
    149                            const json_t *j)
    150 {
    151   struct TALER_EXCHANGE_PostReservesCloseResponse rs = {
    152     .hr.reply = j,
    153     .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
    154   };
    155   struct GNUNET_JSON_Specification spec[] = {
    156     GNUNET_JSON_spec_fixed_auto (
    157       "h_payto",
    158       &rs.details.unavailable_for_legal_reasons.h_payto),
    159     GNUNET_JSON_spec_uint64 (
    160       "requirement_row",
    161       &rs.details.unavailable_for_legal_reasons.requirement_row),
    162     GNUNET_JSON_spec_end ()
    163   };
    164 
    165   if (GNUNET_OK !=
    166       GNUNET_JSON_parse (j,
    167                          spec,
    168                          NULL,
    169                          NULL))
    170   {
    171     GNUNET_break_op (0);
    172     return GNUNET_SYSERR;
    173   }
    174   prch->cb (prch->cb_cls,
    175             &rs);
    176   prch->cb = NULL;
    177   GNUNET_JSON_parse_free (spec);
    178   return GNUNET_OK;
    179 }
    180 
    181 
    182 /**
    183  * Function called when we're done processing the
    184  * HTTP /reserves/$RID/close request.
    185  *
    186  * @param cls the `struct TALER_EXCHANGE_PostReservesCloseHandle`
    187  * @param response_code HTTP response code, 0 on error
    188  * @param response parsed JSON result, NULL on error
    189  */
    190 static void
    191 handle_reserves_close_finished (void *cls,
    192                                 long response_code,
    193                                 const void *response)
    194 {
    195   struct TALER_EXCHANGE_PostReservesCloseHandle *prch = cls;
    196   const json_t *j = response;
    197   struct TALER_EXCHANGE_PostReservesCloseResponse rs = {
    198     .hr.reply = j,
    199     .hr.http_status = (unsigned int) response_code
    200   };
    201 
    202   prch->job = NULL;
    203   switch (response_code)
    204   {
    205   case 0:
    206     rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    207     break;
    208   case MHD_HTTP_OK:
    209     if (GNUNET_OK !=
    210         handle_reserves_close_ok (prch,
    211                                   j))
    212     {
    213       GNUNET_break_op (0);
    214       rs.hr.http_status = 0;
    215       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    216     }
    217     break;
    218   case MHD_HTTP_BAD_REQUEST:
    219     /* This should never happen, either us or the exchange is buggy
    220        (or API version conflict); just pass JSON reply to the application */
    221     GNUNET_break (0);
    222     rs.hr.ec = TALER_JSON_get_error_code (j);
    223     rs.hr.hint = TALER_JSON_get_error_hint (j);
    224     break;
    225   case MHD_HTTP_FORBIDDEN:
    226     /* This should never happen, either us or the exchange is buggy
    227        (or API version conflict); just pass JSON reply to the application */
    228     GNUNET_break (0);
    229     rs.hr.ec = TALER_JSON_get_error_code (j);
    230     rs.hr.hint = TALER_JSON_get_error_hint (j);
    231     break;
    232   case MHD_HTTP_NOT_FOUND:
    233     /* Nothing really to verify, this should never
    234        happen, we should pass the JSON reply to the application */
    235     rs.hr.ec = TALER_JSON_get_error_code (j);
    236     rs.hr.hint = TALER_JSON_get_error_hint (j);
    237     break;
    238   case MHD_HTTP_CONFLICT:
    239     /* Insufficient balance to inquire for reserve close */
    240     rs.hr.ec = TALER_JSON_get_error_code (j);
    241     rs.hr.hint = TALER_JSON_get_error_hint (j);
    242     break;
    243   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    244     if (GNUNET_OK !=
    245         handle_reserves_close_kyc (prch,
    246                                    j))
    247     {
    248       GNUNET_break_op (0);
    249       rs.hr.http_status = 0;
    250       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    251     }
    252     break;
    253   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    254     /* Server had an internal issue; we should retry, but this API
    255        leaves this to the application */
    256     rs.hr.ec = TALER_JSON_get_error_code (j);
    257     rs.hr.hint = TALER_JSON_get_error_hint (j);
    258     break;
    259   default:
    260     /* unexpected response code */
    261     GNUNET_break_op (0);
    262     rs.hr.ec = TALER_JSON_get_error_code (j);
    263     rs.hr.hint = TALER_JSON_get_error_hint (j);
    264     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    265                 "Unexpected response code %u/%d for reserves close\n",
    266                 (unsigned int) response_code,
    267                 (int) rs.hr.ec);
    268     break;
    269   }
    270   if (NULL != prch->cb)
    271   {
    272     prch->cb (prch->cb_cls,
    273               &rs);
    274     prch->cb = NULL;
    275   }
    276   TALER_EXCHANGE_post_reserves_close_cancel (prch);
    277 }
    278 
    279 
    280 struct TALER_EXCHANGE_PostReservesCloseHandle *
    281 TALER_EXCHANGE_post_reserves_close_create (
    282   struct GNUNET_CURL_Context *ctx,
    283   const char *url,
    284   const struct TALER_ReservePrivateKeyP *reserve_priv)
    285 {
    286   struct TALER_EXCHANGE_PostReservesCloseHandle *prch;
    287 
    288   prch = GNUNET_new (struct TALER_EXCHANGE_PostReservesCloseHandle);
    289   prch->ctx = ctx;
    290   prch->base_url = GNUNET_strdup (url);
    291   prch->reserve_priv = *reserve_priv;
    292   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
    293                                       &prch->reserve_pub.eddsa_pub);
    294   prch->target_payto_uri.full_payto = NULL;
    295   return prch;
    296 }
    297 
    298 
    299 enum GNUNET_GenericReturnValue
    300 TALER_EXCHANGE_post_reserves_close_set_options_ (
    301   struct TALER_EXCHANGE_PostReservesCloseHandle *prch,
    302   unsigned int num_options,
    303   const struct TALER_EXCHANGE_PostReservesCloseOptionValue options[])
    304 {
    305   for (unsigned int i = 0; i < num_options; i++)
    306   {
    307     const struct TALER_EXCHANGE_PostReservesCloseOptionValue *opt = &options[i];
    308 
    309     switch (opt->option)
    310     {
    311     case TALER_EXCHANGE_POST_RESERVES_CLOSE_OPTION_END:
    312       return GNUNET_OK;
    313     case TALER_EXCHANGE_POST_RESERVES_CLOSE_OPTION_PAYTO_URI:
    314       GNUNET_free (prch->target_payto_uri.full_payto);
    315       prch->target_payto_uri.full_payto
    316         = GNUNET_strdup (opt->details.payto_uri.full_payto);
    317       break;
    318     }
    319   }
    320   return GNUNET_OK;
    321 }
    322 
    323 
    324 enum TALER_ErrorCode
    325 TALER_EXCHANGE_post_reserves_close_start (
    326   struct TALER_EXCHANGE_PostReservesCloseHandle *prch,
    327   TALER_EXCHANGE_PostReservesCloseCallback cb,
    328   TALER_EXCHANGE_POST_RESERVES_CLOSE_RESULT_CLOSURE *cb_cls)
    329 {
    330   CURL *eh;
    331   char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
    332   struct TALER_ReserveSignatureP reserve_sig;
    333   struct TALER_FullPaytoHashP h_payto;
    334   json_t *close_obj;
    335 
    336   prch->cb = cb;
    337   prch->cb_cls = cb_cls;
    338   prch->ts = GNUNET_TIME_timestamp_get ();
    339   {
    340     char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
    341     char *end;
    342 
    343     end = GNUNET_STRINGS_data_to_string (
    344       &prch->reserve_pub,
    345       sizeof (prch->reserve_pub),
    346       pub_str,
    347       sizeof (pub_str));
    348     *end = '\0';
    349     GNUNET_snprintf (arg_str,
    350                      sizeof (arg_str),
    351                      "reserves/%s/close",
    352                      pub_str);
    353   }
    354   prch->url = TALER_url_join (prch->base_url,
    355                               arg_str,
    356                               NULL);
    357   if (NULL == prch->url)
    358     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    359   if (NULL != prch->target_payto_uri.full_payto)
    360     TALER_full_payto_hash (prch->target_payto_uri,
    361                            &h_payto);
    362   TALER_wallet_reserve_close_sign (prch->ts,
    363                                    (NULL != prch->target_payto_uri.full_payto)
    364                                    ? &h_payto
    365                                    : NULL,
    366                                    &prch->reserve_priv,
    367                                    &reserve_sig);
    368   close_obj = GNUNET_JSON_PACK (
    369     GNUNET_JSON_pack_allow_null (
    370       TALER_JSON_pack_full_payto ("payto_uri",
    371                                   prch->target_payto_uri)),
    372     GNUNET_JSON_pack_timestamp ("request_timestamp",
    373                                 prch->ts),
    374     GNUNET_JSON_pack_data_auto ("reserve_sig",
    375                                 &reserve_sig));
    376   eh = TALER_EXCHANGE_curl_easy_get_ (prch->url);
    377   if ( (NULL == eh) ||
    378        (GNUNET_OK !=
    379         TALER_curl_easy_post (&prch->post_ctx,
    380                               eh,
    381                               close_obj)) )
    382   {
    383     GNUNET_break (0);
    384     if (NULL != eh)
    385       curl_easy_cleanup (eh);
    386     json_decref (close_obj);
    387     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    388   }
    389   json_decref (close_obj);
    390   prch->job = GNUNET_CURL_job_add2 (prch->ctx,
    391                                     eh,
    392                                     prch->post_ctx.headers,
    393                                     &handle_reserves_close_finished,
    394                                     prch);
    395   if (NULL == prch->job)
    396     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    397   return TALER_EC_NONE;
    398 }
    399 
    400 
    401 void
    402 TALER_EXCHANGE_post_reserves_close_cancel (
    403   struct TALER_EXCHANGE_PostReservesCloseHandle *prch)
    404 {
    405   if (NULL != prch->job)
    406   {
    407     GNUNET_CURL_job_cancel (prch->job);
    408     prch->job = NULL;
    409   }
    410   TALER_curl_easy_post_finished (&prch->post_ctx);
    411   GNUNET_free (prch->url);
    412   GNUNET_free (prch->base_url);
    413   GNUNET_free (prch->target_payto_uri.full_payto);
    414   GNUNET_free (prch);
    415 }
    416 
    417 
    418 /* end of exchange_api_post-reserves-RESERVE_PUB-close.c */