exchange_api_purses_get.c (9007B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022-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_purses_get.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 Contract Get Handle 37 */ 38 struct TALER_EXCHANGE_PurseGetHandle 39 { 40 41 /** 42 * The keys of the exchange this request handle will use 43 */ 44 struct TALER_EXCHANGE_Keys *keys; 45 46 /** 47 * The url for this request. 48 */ 49 char *url; 50 51 /** 52 * Handle for the request. 53 */ 54 struct GNUNET_CURL_Job *job; 55 56 /** 57 * Function to call with the result. 58 */ 59 TALER_EXCHANGE_PurseGetCallback cb; 60 61 /** 62 * Closure for @a cb. 63 */ 64 void *cb_cls; 65 66 }; 67 68 69 /** 70 * Function called when we're done processing the 71 * HTTP /purses/$PID GET request. 72 * 73 * @param cls the `struct TALER_EXCHANGE_PurseGetHandle` 74 * @param response_code HTTP response code, 0 on error 75 * @param response parsed JSON result, NULL on error 76 */ 77 static void 78 handle_purse_get_finished (void *cls, 79 long response_code, 80 const void *response) 81 { 82 struct TALER_EXCHANGE_PurseGetHandle *pgh = cls; 83 const json_t *j = response; 84 struct TALER_EXCHANGE_PurseGetResponse dr = { 85 .hr.reply = j, 86 .hr.http_status = (unsigned int) response_code 87 }; 88 89 pgh->job = NULL; 90 switch (response_code) 91 { 92 case 0: 93 dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 94 break; 95 case MHD_HTTP_OK: 96 { 97 bool no_merge = false; 98 bool no_deposit = false; 99 struct TALER_ExchangePublicKeyP exchange_pub; 100 struct TALER_ExchangeSignatureP exchange_sig; 101 struct GNUNET_JSON_Specification spec[] = { 102 GNUNET_JSON_spec_mark_optional ( 103 GNUNET_JSON_spec_timestamp ("merge_timestamp", 104 &dr.details.ok.merge_timestamp), 105 &no_merge), 106 GNUNET_JSON_spec_mark_optional ( 107 GNUNET_JSON_spec_timestamp ("deposit_timestamp", 108 &dr.details.ok.deposit_timestamp), 109 &no_deposit), 110 TALER_JSON_spec_amount_any ("balance", 111 &dr.details.ok.balance), 112 GNUNET_JSON_spec_timestamp ("purse_expiration", 113 &dr.details.ok.purse_expiration), 114 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 115 &exchange_pub), 116 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 117 &exchange_sig), 118 GNUNET_JSON_spec_end () 119 }; 120 121 if (GNUNET_OK != 122 GNUNET_JSON_parse (j, 123 spec, 124 NULL, NULL)) 125 { 126 GNUNET_break_op (0); 127 dr.hr.http_status = 0; 128 dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 129 break; 130 } 131 132 if (GNUNET_OK != 133 TALER_EXCHANGE_test_signing_key (pgh->keys, 134 &exchange_pub)) 135 { 136 GNUNET_break_op (0); 137 dr.hr.http_status = 0; 138 dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE; 139 break; 140 } 141 if (GNUNET_OK != 142 TALER_exchange_online_purse_status_verify ( 143 dr.details.ok.merge_timestamp, 144 dr.details.ok.deposit_timestamp, 145 &dr.details.ok.balance, 146 &exchange_pub, 147 &exchange_sig)) 148 { 149 GNUNET_break_op (0); 150 dr.hr.http_status = 0; 151 dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE; 152 break; 153 } 154 pgh->cb (pgh->cb_cls, 155 &dr); 156 TALER_EXCHANGE_purse_get_cancel (pgh); 157 return; 158 } 159 case MHD_HTTP_BAD_REQUEST: 160 dr.hr.ec = TALER_JSON_get_error_code (j); 161 dr.hr.hint = TALER_JSON_get_error_hint (j); 162 /* This should never happen, either us or the exchange is buggy 163 (or API version conflict); just pass JSON reply to the application */ 164 break; 165 case MHD_HTTP_FORBIDDEN: 166 dr.hr.ec = TALER_JSON_get_error_code (j); 167 dr.hr.hint = TALER_JSON_get_error_hint (j); 168 /* Nothing really to verify, exchange says one of the signatures is 169 invalid; as we checked them, this should never happen, we 170 should pass the JSON reply to the application */ 171 break; 172 case MHD_HTTP_NOT_FOUND: 173 dr.hr.ec = TALER_JSON_get_error_code (j); 174 dr.hr.hint = TALER_JSON_get_error_hint (j); 175 /* Exchange does not know about transaction; 176 we should pass the reply to the application */ 177 break; 178 case MHD_HTTP_GONE: 179 /* purse expired */ 180 dr.hr.ec = TALER_JSON_get_error_code (j); 181 dr.hr.hint = TALER_JSON_get_error_hint (j); 182 break; 183 case MHD_HTTP_INTERNAL_SERVER_ERROR: 184 dr.hr.ec = TALER_JSON_get_error_code (j); 185 dr.hr.hint = TALER_JSON_get_error_hint (j); 186 /* Server had an internal issue; we should retry, but this API 187 leaves this to the application */ 188 break; 189 default: 190 /* unexpected response code */ 191 dr.hr.ec = TALER_JSON_get_error_code (j); 192 dr.hr.hint = TALER_JSON_get_error_hint (j); 193 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 194 "Unexpected response code %u/%d for exchange GET purses\n", 195 (unsigned int) response_code, 196 (int) dr.hr.ec); 197 GNUNET_break_op (0); 198 break; 199 } 200 pgh->cb (pgh->cb_cls, 201 &dr); 202 TALER_EXCHANGE_purse_get_cancel (pgh); 203 } 204 205 206 struct TALER_EXCHANGE_PurseGetHandle * 207 TALER_EXCHANGE_purse_get ( 208 struct GNUNET_CURL_Context *ctx, 209 const char *url, 210 struct TALER_EXCHANGE_Keys *keys, 211 const struct TALER_PurseContractPublicKeyP *purse_pub, 212 struct GNUNET_TIME_Relative timeout, 213 bool wait_for_merge, 214 TALER_EXCHANGE_PurseGetCallback cb, 215 void *cb_cls) 216 { 217 struct TALER_EXCHANGE_PurseGetHandle *pgh; 218 CURL *eh; 219 char arg_str[sizeof (*purse_pub) * 2 + 64]; 220 unsigned int tms 221 = (unsigned int) timeout.rel_value_us 222 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; 223 224 pgh = GNUNET_new (struct TALER_EXCHANGE_PurseGetHandle); 225 pgh->cb = cb; 226 pgh->cb_cls = cb_cls; 227 { 228 char cpub_str[sizeof (*purse_pub) * 2]; 229 char *end; 230 char timeout_str[32]; 231 232 end = GNUNET_STRINGS_data_to_string (purse_pub, 233 sizeof (*purse_pub), 234 cpub_str, 235 sizeof (cpub_str)); 236 *end = '\0'; 237 GNUNET_snprintf (timeout_str, 238 sizeof (timeout_str), 239 "%u", 240 tms); 241 GNUNET_snprintf (arg_str, 242 sizeof (arg_str), 243 "purses/%s/%s", 244 cpub_str, 245 wait_for_merge 246 ? "merge" 247 : "deposit"); 248 pgh->url = TALER_url_join (url, 249 arg_str, 250 "timeout_ms", 251 (0 == tms) 252 ? NULL 253 : timeout_str, 254 NULL); 255 } 256 if (NULL == pgh->url) 257 { 258 GNUNET_free (pgh); 259 return NULL; 260 } 261 eh = TALER_EXCHANGE_curl_easy_get_ (pgh->url); 262 if (NULL == eh) 263 { 264 GNUNET_break (0); 265 GNUNET_free (pgh->url); 266 GNUNET_free (pgh); 267 return NULL; 268 } 269 if (0 != tms) 270 { 271 GNUNET_break (CURLE_OK == 272 curl_easy_setopt (eh, 273 CURLOPT_TIMEOUT_MS, 274 (long) (tms + 100L))); 275 } 276 pgh->job = GNUNET_CURL_job_add (ctx, 277 eh, 278 &handle_purse_get_finished, 279 pgh); 280 pgh->keys = TALER_EXCHANGE_keys_incref (keys); 281 return pgh; 282 } 283 284 285 void 286 TALER_EXCHANGE_purse_get_cancel ( 287 struct TALER_EXCHANGE_PurseGetHandle *pgh) 288 { 289 if (NULL != pgh->job) 290 { 291 GNUNET_CURL_job_cancel (pgh->job); 292 pgh->job = NULL; 293 } 294 GNUNET_free (pgh->url); 295 TALER_EXCHANGE_keys_decref (pgh->keys); 296 GNUNET_free (pgh); 297 } 298 299 300 /* end of exchange_api_purses_get.c */