cash2ecash

cash2ecash: cash acceptor that issues digital cash (experimental)
Log | Files | Refs | README | LICENSE

bank_api_get_config.c (8931B)


      1 /*
      2   This file is part of TALER cash2ecash
      3   Copyright (C) 2026 GNUnet e.V.
      4 
      5   This program is free software: you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation, either version 3 of the
      8   License, or (at your option) any later version.
      9 
     10   This program is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty of
     12   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14   GNU Affero General Public License for more details.
     15 
     16   You should have received a copy of the GNU Affero General Public License
     17   along with this program.  If not, see <https://www.gnu.org/licenses/>.
     18 */
     19 
     20 /**
     21  * @file bank_api_get_config.c
     22  * @brief Implementation of the GET /config request of the bank's HTTP API
     23  * @author Reto Tellenbach
     24  */
     25 
     26 #include <microhttpd.h>
     27 #include "bank_api_get_config.h"
     28 #include "taler/taler_json_lib.h"
     29 
     30 /**
     31  * Which revision of the Taler core-bank protocol is implemented
     32  * by this library?  Used to determine compatibility.
     33  */
     34 #define TALER_PROTOCOL_CURRENT 12
     35 
     36 /**
     37  * How many revisions back are we compatible to?
     38  */
     39 #define TALER_PROTOCOL_AGE 0
     40 
     41 
     42 /**
     43  * Log error related to CURL operations.
     44  *
     45  * @param type log level
     46  * @param function which function failed to run
     47  * @param code what was the curl error code
     48  */
     49 #define CURL_STRERROR(type, function, code)      \
     50         GNUNET_log (type, \
     51                     "Curl function `%s' has failed at `%s:%d' with error: %s", \
     52                     function, __FILE__, __LINE__, curl_easy_strerror (code));
     53 
     54 
     55 /**
     56  * Handle for the get config request.
     57  */
     58 struct TALER_BANK_GetConfigHandle
     59 {
     60       /**
     61    * The context of this handle
     62    */
     63   struct GNUNET_CURL_Context *ctx;
     64 
     65   /**
     66    * Function to call with the ,
     67    * NULL if this has already been done.
     68    */
     69   TALER_BANK_ConfigCallback config_cb;
     70 
     71   /**
     72    * Closure to pass to
     73    */
     74   void *config_cb_cls;
     75 
     76   /**
     77    * Data for the request to get the /config of a bank,
     78    * NULL once we are past stage #MHS_INIT.
     79    */
     80   struct GNUNET_CURL_Job *job;
     81 
     82   /**
     83    * The whole request line
     84    */
     85   char *job_url;
     86 };
     87 
     88 
     89 /**
     90  * Decode the JSON in @a resp_obj from the /config response
     91  *
     92  * @param[in] resp_obj JSON object to parse
     93  * @param[in,out] vi where to store the results we decoded
     94  * @param[out] vc where to store config compatibility data
     95  * @return #TALER_EC_NONE on success
     96  */
     97 static enum TALER_ErrorCode
     98 decode_config_json (const json_t *resp_obj,
     99                     struct TALER_BANK_ConfigInformation *vi,
    100                     enum TALER_BANK_VersionCompatibility *vc)
    101 {
    102   struct TALER_JSON_ProtocolVersion pv;
    103   const char *ver;
    104   bool bank_name_missing;
    105 
    106   struct GNUNET_JSON_Specification spec[] = {
    107     TALER_JSON_spec_version ("version",
    108                              &pv),
    109     GNUNET_JSON_spec_string ("version",
    110                              &ver),
    111     GNUNET_JSON_spec_string ("currency",
    112                                  &vi->currency),
    113     GNUNET_JSON_spec_mark_optional (
    114     GNUNET_JSON_spec_string ("bank_name",
    115                               &vi->bank_name),
    116                               &bank_name_missing),
    117     GNUNET_JSON_spec_end ()
    118   };
    119 
    120   if (JSON_OBJECT != json_typeof (resp_obj))
    121   {
    122     GNUNET_break_op (0);
    123     return TALER_EC_GENERIC_JSON_INVALID;
    124   }
    125   if (GNUNET_OK !=
    126       GNUNET_JSON_parse (resp_obj,
    127                          spec,
    128                          NULL, NULL))
    129   {
    130     GNUNET_break_op (0);
    131     return TALER_EC_GENERIC_JSON_INVALID;
    132   }
    133 
    134   /* Set default fot optional values */
    135   if (bank_name_missing)
    136   {
    137     vi->bank_name = GNUNET_strdup("Taler Bank");
    138   }
    139 
    140   /* Version comparison */
    141   vi->version = ver;
    142   *vc = TALER_BANK_VC_MATCH;
    143   if (TALER_PROTOCOL_CURRENT < pv.current)
    144   {
    145     *vc |= TALER_BANK_VC_NEWER;
    146     if (TALER_PROTOCOL_CURRENT < pv.current - pv.age)
    147       *vc |= TALER_BANK_VC_INCOMPATIBLE;
    148   }
    149   if (TALER_PROTOCOL_CURRENT > pv.current)
    150   {
    151     *vc |= TALER_BANK_VC_OLDER;
    152     if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > pv.current)
    153       *vc |= TALER_BANK_VC_INCOMPATIBLE;
    154   }
    155 
    156   struct GNUNET_JSON_Specification spec2[] = {
    157     GNUNET_JSON_spec_mark_optional (
    158     TALER_JSON_spec_amount ("min_wire_transfer_amount",
    159                                 vi->currency,
    160                                 &vi->min_wire_transfer_amount),
    161                                 NULL),
    162     GNUNET_JSON_spec_mark_optional (
    163     TALER_JSON_spec_amount ("max_wire_transfer_amount",
    164                              vi->currency,
    165                              &vi->max_wire_transfer_amount),
    166                              NULL),
    167     TALER_JSON_spec_currency_specification ("currency_specification",
    168                                             vi->currency,
    169                                             &vi->currency_specification),
    170     GNUNET_JSON_spec_mark_optional (
    171     TALER_JSON_spec_amount("wire_transfer_fees",
    172                             vi->currency,
    173                             &vi->wire_transfer_fees),
    174                             NULL),      
    175     GNUNET_JSON_spec_end ()
    176   };
    177 
    178   if (GNUNET_OK !=
    179       GNUNET_JSON_parse (resp_obj,
    180                          spec2,
    181                          NULL, NULL))
    182   {
    183     GNUNET_break_op (0);
    184     return TALER_EC_GENERIC_JSON_INVALID;
    185   }
    186 
    187   return TALER_EC_NONE;
    188 }
    189 
    190 
    191 /**
    192  * Callback used when http reply arived to a /config request.
    193  *
    194  * @param cls the `struct TALER_BANK_GetConfigHandle`
    195  * @param response_code HTTP response code or 0 on error
    196  * @param  gresp_obj JSON result, NULL on error, must be a `const json_t *`
    197  */
    198 static void
    199 response_cb(void *cls,
    200         long response_code,
    201         const void *gresp_obj)
    202 {
    203     struct TALER_BANK_GetConfigHandle *bank = cls;
    204     const json_t *resp_obj = gresp_obj;
    205     
    206     struct TALER_BANK_ConfigResponse cr = {
    207         .hr.response = resp_obj,
    208         .hr.http_status = (unsigned int)response_code
    209     };
    210     
    211     bank->job = NULL; //job was successfull, curl job cancel not needed anymore in cleanup
    212     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    213               "Received config from URL `%s' with status %ld.\n",
    214               bank->job_url,
    215               response_code);
    216     
    217     switch (response_code)
    218   {
    219   case 0:
    220     GNUNET_break_op (0);
    221     cr.hr.ec = TALER_EC_INVALID;
    222     break;
    223   case MHD_HTTP_OK:
    224     if (NULL == resp_obj)
    225     {
    226       GNUNET_break_op (0);
    227       cr.hr.http_status = 0;
    228       cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    229       break;
    230     }
    231     cr.hr.ec = decode_config_json (resp_obj,
    232                                    &cr.details.ok.configi,
    233                                    &cr.details.ok.version_compa);
    234     if (TALER_EC_NONE != cr.hr.ec)
    235     {
    236       GNUNET_break_op (0);
    237       cr.hr.http_status = 0;
    238       break;
    239     }
    240     break;
    241   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    242     cr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    243     cr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    244     break;
    245   default:
    246     cr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    247     cr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    248     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    249                 "Unexpected response code %u/%d\n",
    250                 (unsigned int) response_code,
    251                 (int) cr.hr.ec);
    252     break;
    253   }
    254 
    255     bank->config_cb (bank->config_cb_cls, &cr);
    256     TALER_BANK_get_config_cancel(bank);
    257 }
    258 
    259 
    260 struct TALER_BANK_GetConfigHandle *
    261 TALER_BANK_get_config ( struct GNUNET_CURL_Context *ctx,
    262                         const char *url,
    263                         TALER_BANK_ConfigCallback config_cb,
    264                         void *config_cb_cls)
    265 {
    266     struct TALER_BANK_GetConfigHandle *bank;
    267     CURL *eh;
    268 
    269     bank = GNUNET_new(struct TALER_BANK_GetConfigHandle);
    270     bank->config_cb = config_cb;
    271     bank->config_cb_cls = config_cb_cls;
    272     bank->ctx = ctx;
    273     bank->job_url = TALER_url_join(url,"config",NULL);
    274     if(NULL == bank->job_url)
    275     {
    276         GNUNET_break(0);
    277         GNUNET_free(bank);
    278         return NULL;
    279     }
    280 
    281     GNUNET_log( GNUNET_ERROR_TYPE_INFO,
    282                 "Requesting bank config with URL `%s'.\n",
    283                 bank->job_url);
    284     eh = TALER_BANK_curl_easy_get_(bank->job_url);
    285     if(NULL == eh)
    286     {
    287         GNUNET_break(0);
    288         TALER_BANK_get_config_cancel(bank);
    289         return NULL;
    290     }
    291     bank->job = GNUNET_CURL_job_add(bank->ctx,
    292                                     eh,
    293                                     &response_cb,
    294                                     bank);
    295     if(NULL == bank->job)
    296     {
    297         GNUNET_break(0);
    298         TALER_BANK_get_config_cancel(bank);
    299         return NULL;
    300     }
    301             
    302     return bank;
    303 }
    304 
    305 
    306 
    307 void
    308 TALER_BANK_get_config_cancel ( struct TALER_BANK_GetConfigHandle *bank)
    309 {
    310     if(NULL != bank->job)
    311     {
    312         GNUNET_CURL_job_cancel(bank->job);
    313         bank->job = NULL;
    314     }
    315     GNUNET_free(bank->job_url);
    316     GNUNET_free(bank);
    317 }   
    318