exchange

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

exchange_api_reserves_close.c (10473B)


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