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 */