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