exchange_api_get-purses-PURSE_PUB-merge.c (10809B)
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 "taler/platform.h" 23 #include <jansson.h> 24 #include <microhttpd.h> /* just for HTTP status 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_json_lib.h" 29 #include "taler/taler_exchange_service.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 GET /purses/$PURSE_PUB/merge Handle 37 */ 38 struct TALER_EXCHANGE_GetPursesHandle 39 { 40 41 /** 42 * Base URL of the exchange. 43 */ 44 char *base_url; 45 46 /** 47 * The url for this request. 48 */ 49 char *url; 50 51 /** 52 * The keys of the exchange this request handle will use. 53 */ 54 struct TALER_EXCHANGE_Keys *keys; 55 56 /** 57 * Handle for the request. 58 */ 59 struct GNUNET_CURL_Job *job; 60 61 /** 62 * Function to call with the result. 63 */ 64 TALER_EXCHANGE_GetPursesCallback cb; 65 66 /** 67 * Closure for @e cb. 68 */ 69 TALER_EXCHANGE_GET_PURSES_RESULT_CLOSURE *cb_cls; 70 71 /** 72 * CURL context to use. 73 */ 74 struct GNUNET_CURL_Context *ctx; 75 76 /** 77 * Public key of the purse being queried. 78 */ 79 struct TALER_PurseContractPublicKeyP purse_pub; 80 81 /** 82 * Options for the request. 83 */ 84 struct 85 { 86 /** 87 * How long to wait for a change to happen. 88 */ 89 struct GNUNET_TIME_Relative timeout; 90 91 /** 92 * True to wait for a merge event, false to wait for a deposit event. 93 */ 94 bool wait_for_merge; 95 } options; 96 97 }; 98 99 100 /** 101 * Function called when we're done processing the 102 * HTTP /purses/$PID GET request. 103 * 104 * @param cls the `struct TALER_EXCHANGE_GetPursesHandle` 105 * @param response_code HTTP response code, 0 on error 106 * @param response parsed JSON result, NULL on error 107 */ 108 static void 109 handle_purse_get_finished (void *cls, 110 long response_code, 111 const void *response) 112 { 113 struct TALER_EXCHANGE_GetPursesHandle *gph = cls; 114 const json_t *j = response; 115 struct TALER_EXCHANGE_GetPursesResponse dr = { 116 .hr.reply = j, 117 .hr.http_status = (unsigned int) response_code 118 }; 119 120 gph->job = NULL; 121 switch (response_code) 122 { 123 case 0: 124 dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 125 break; 126 case MHD_HTTP_OK: 127 { 128 bool no_merge = false; 129 bool no_deposit = false; 130 struct TALER_ExchangePublicKeyP exchange_pub; 131 struct TALER_ExchangeSignatureP exchange_sig; 132 struct GNUNET_JSON_Specification spec[] = { 133 GNUNET_JSON_spec_mark_optional ( 134 GNUNET_JSON_spec_timestamp ("merge_timestamp", 135 &dr.details.ok.merge_timestamp), 136 &no_merge), 137 GNUNET_JSON_spec_mark_optional ( 138 GNUNET_JSON_spec_timestamp ("deposit_timestamp", 139 &dr.details.ok.deposit_timestamp), 140 &no_deposit), 141 TALER_JSON_spec_amount_any ("balance", 142 &dr.details.ok.balance), 143 GNUNET_JSON_spec_timestamp ("purse_expiration", 144 &dr.details.ok.purse_expiration), 145 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 146 &exchange_pub), 147 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 148 &exchange_sig), 149 GNUNET_JSON_spec_end () 150 }; 151 152 if (GNUNET_OK != 153 GNUNET_JSON_parse (j, 154 spec, 155 NULL, NULL)) 156 { 157 GNUNET_break_op (0); 158 dr.hr.http_status = 0; 159 dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 160 break; 161 } 162 if (GNUNET_OK != 163 TALER_EXCHANGE_test_signing_key (gph->keys, 164 &exchange_pub)) 165 { 166 GNUNET_break_op (0); 167 dr.hr.http_status = 0; 168 dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE; 169 break; 170 } 171 if (GNUNET_OK != 172 TALER_exchange_online_purse_status_verify ( 173 dr.details.ok.merge_timestamp, 174 dr.details.ok.deposit_timestamp, 175 &dr.details.ok.balance, 176 &exchange_pub, 177 &exchange_sig)) 178 { 179 GNUNET_break_op (0); 180 dr.hr.http_status = 0; 181 dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE; 182 break; 183 } 184 gph->cb (gph->cb_cls, 185 &dr); 186 TALER_EXCHANGE_get_purses_cancel (gph); 187 return; 188 } 189 case MHD_HTTP_BAD_REQUEST: 190 dr.hr.ec = TALER_JSON_get_error_code (j); 191 dr.hr.hint = TALER_JSON_get_error_hint (j); 192 /* This should never happen, either us or the exchange is buggy 193 (or API version conflict); just pass JSON reply to the application */ 194 break; 195 case MHD_HTTP_FORBIDDEN: 196 dr.hr.ec = TALER_JSON_get_error_code (j); 197 dr.hr.hint = TALER_JSON_get_error_hint (j); 198 /* Nothing really to verify, exchange says one of the signatures is 199 invalid; as we checked them, this should never happen, we 200 should pass the JSON reply to the application */ 201 break; 202 case MHD_HTTP_NOT_FOUND: 203 dr.hr.ec = TALER_JSON_get_error_code (j); 204 dr.hr.hint = TALER_JSON_get_error_hint (j); 205 /* Exchange does not know about transaction; 206 we should pass the reply to the application */ 207 break; 208 case MHD_HTTP_GONE: 209 /* purse expired */ 210 dr.hr.ec = TALER_JSON_get_error_code (j); 211 dr.hr.hint = TALER_JSON_get_error_hint (j); 212 break; 213 case MHD_HTTP_INTERNAL_SERVER_ERROR: 214 dr.hr.ec = TALER_JSON_get_error_code (j); 215 dr.hr.hint = TALER_JSON_get_error_hint (j); 216 /* Server had an internal issue; we should retry, but this API 217 leaves this to the application */ 218 break; 219 default: 220 /* unexpected response code */ 221 dr.hr.ec = TALER_JSON_get_error_code (j); 222 dr.hr.hint = TALER_JSON_get_error_hint (j); 223 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 224 "Unexpected response code %u/%d for exchange GET purses\n", 225 (unsigned int) response_code, 226 (int) dr.hr.ec); 227 GNUNET_break_op (0); 228 break; 229 } 230 gph->cb (gph->cb_cls, 231 &dr); 232 TALER_EXCHANGE_get_purses_cancel (gph); 233 } 234 235 236 struct TALER_EXCHANGE_GetPursesHandle * 237 TALER_EXCHANGE_get_purses_create ( 238 struct GNUNET_CURL_Context *ctx, 239 const char *url, 240 struct TALER_EXCHANGE_Keys *keys, 241 const struct TALER_PurseContractPublicKeyP *purse_pub) 242 { 243 struct TALER_EXCHANGE_GetPursesHandle *gph; 244 245 gph = GNUNET_new (struct TALER_EXCHANGE_GetPursesHandle); 246 gph->ctx = ctx; 247 gph->base_url = GNUNET_strdup (url); 248 gph->keys = TALER_EXCHANGE_keys_incref (keys); 249 gph->purse_pub = *purse_pub; 250 return gph; 251 } 252 253 254 enum GNUNET_GenericReturnValue 255 TALER_EXCHANGE_get_purses_set_options_ ( 256 struct TALER_EXCHANGE_GetPursesHandle *gph, 257 unsigned int num_options, 258 const struct TALER_EXCHANGE_GetPursesOptionValue *options) 259 { 260 for (unsigned int i = 0; i < num_options; i++) 261 { 262 switch (options[i].option) 263 { 264 case TALER_EXCHANGE_GET_PURSES_OPTION_END: 265 return GNUNET_OK; 266 case TALER_EXCHANGE_GET_PURSES_OPTION_TIMEOUT: 267 gph->options.timeout = options[i].details.timeout; 268 break; 269 case TALER_EXCHANGE_GET_PURSES_OPTION_WAIT_FOR_MERGE: 270 gph->options.wait_for_merge = options[i].details.wait_for_merge; 271 break; 272 default: 273 GNUNET_break (0); 274 return GNUNET_SYSERR; 275 } 276 } 277 return GNUNET_OK; 278 } 279 280 281 enum TALER_ErrorCode 282 TALER_EXCHANGE_get_purses_start ( 283 struct TALER_EXCHANGE_GetPursesHandle *gph, 284 TALER_EXCHANGE_GetPursesCallback cb, 285 TALER_EXCHANGE_GET_PURSES_RESULT_CLOSURE *cb_cls) 286 { 287 char arg_str[sizeof (gph->purse_pub) * 2 + 64]; 288 CURL *eh; 289 unsigned int tms 290 = (unsigned int) gph->options.timeout.rel_value_us 291 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; 292 293 if (NULL != gph->job) 294 { 295 GNUNET_break (0); 296 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 297 } 298 gph->cb = cb; 299 gph->cb_cls = cb_cls; 300 { 301 char cpub_str[sizeof (gph->purse_pub) * 2]; 302 char *end; 303 char timeout_str[32]; 304 305 end = GNUNET_STRINGS_data_to_string (&gph->purse_pub, 306 sizeof (gph->purse_pub), 307 cpub_str, 308 sizeof (cpub_str)); 309 *end = '\0'; 310 GNUNET_snprintf (timeout_str, 311 sizeof (timeout_str), 312 "%u", 313 tms); 314 GNUNET_snprintf (arg_str, 315 sizeof (arg_str), 316 "purses/%s/%s", 317 cpub_str, 318 gph->options.wait_for_merge 319 ? "merge" 320 : "deposit"); 321 gph->url = TALER_url_join (gph->base_url, 322 arg_str, 323 "timeout_ms", 324 (0 == tms) 325 ? NULL 326 : timeout_str, 327 NULL); 328 } 329 if (NULL == gph->url) 330 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 331 eh = TALER_EXCHANGE_curl_easy_get_ (gph->url); 332 if (NULL == eh) 333 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 334 if (0 != tms) 335 { 336 GNUNET_break (CURLE_OK == 337 curl_easy_setopt (eh, 338 CURLOPT_TIMEOUT_MS, 339 (long) (tms + 100L))); 340 } 341 gph->job = GNUNET_CURL_job_add (gph->ctx, 342 eh, 343 &handle_purse_get_finished, 344 gph); 345 if (NULL == gph->job) 346 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 347 return TALER_EC_NONE; 348 } 349 350 351 void 352 TALER_EXCHANGE_get_purses_cancel ( 353 struct TALER_EXCHANGE_GetPursesHandle *gph) 354 { 355 if (NULL != gph->job) 356 { 357 GNUNET_CURL_job_cancel (gph->job); 358 gph->job = NULL; 359 } 360 GNUNET_free (gph->url); 361 GNUNET_free (gph->base_url); 362 TALER_EXCHANGE_keys_decref (gph->keys); 363 GNUNET_free (gph); 364 } 365 366 367 /* end of exchange_api_get-purses-PURSE_PUB-merge.c */