exchange_api_get-transfers-WTID.c (12959B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-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-transfers-WTID.c 19 * @brief Implementation of the GET /transfers/$WTID 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_curl_lib.h> 27 #include "taler/taler_exchange_service.h" 28 #include "taler/taler_json_lib.h" 29 #include "exchange_api_handle.h" 30 #include "taler/taler_signatures.h" 31 #include "exchange_api_curl_defaults.h" 32 33 34 /** 35 * @brief A GET /transfers/$WTID Handle 36 */ 37 struct TALER_EXCHANGE_GetTransfersHandle 38 { 39 40 /** 41 * Base URL of the exchange. 42 */ 43 char *base_url; 44 45 /** 46 * The url for this request. 47 */ 48 char *url; 49 50 /** 51 * Handle for the request. 52 */ 53 struct GNUNET_CURL_Job *job; 54 55 /** 56 * Function to call with the result. 57 */ 58 TALER_EXCHANGE_GetTransfersCallback cb; 59 60 /** 61 * Closure for @e cb. 62 */ 63 TALER_EXCHANGE_GET_TRANSFERS_RESULT_CLOSURE *cb_cls; 64 65 /** 66 * CURL context to use. 67 */ 68 struct GNUNET_CURL_Context *ctx; 69 70 /** 71 * The keys of the exchange this request handle will use. 72 */ 73 struct TALER_EXCHANGE_Keys *keys; 74 75 /** 76 * Wire transfer identifier to look up. 77 */ 78 struct TALER_WireTransferIdentifierRawP wtid; 79 80 }; 81 82 83 /** 84 * We got a #MHD_HTTP_OK response for the /transfers/$WTID request. 85 * Check that the response is well-formed and if it is, call the 86 * callback. If not, return an error code. 87 * 88 * This code is very similar to 89 * merchant_api_track_transfer.c::check_transfers_get_response_ok. 90 * Any changes should likely be reflected there as well. 91 * 92 * @param gth handle to the operation 93 * @param json response we got 94 * @return #GNUNET_OK if we are done and all is well, 95 * #GNUNET_SYSERR if the response was bogus 96 */ 97 static enum GNUNET_GenericReturnValue 98 check_transfers_get_response_ok ( 99 struct TALER_EXCHANGE_GetTransfersHandle *gth, 100 const json_t *json) 101 { 102 const json_t *details_j; 103 struct TALER_Amount total_expected; 104 struct TALER_EXCHANGE_GetTransfersResponse tgr = { 105 .hr.reply = json, 106 .hr.http_status = MHD_HTTP_OK 107 }; 108 struct TALER_EXCHANGE_TransferData *td 109 = &tgr.details.ok.td; 110 struct GNUNET_JSON_Specification spec[] = { 111 TALER_JSON_spec_amount_any ("total", 112 &td->total_amount), 113 TALER_JSON_spec_amount_any ("wire_fee", 114 &td->wire_fee), 115 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 116 &td->merchant_pub), 117 GNUNET_JSON_spec_fixed_auto ("h_payto", 118 &td->h_payto), 119 GNUNET_JSON_spec_timestamp ("execution_time", 120 &td->execution_time), 121 GNUNET_JSON_spec_array_const ("deposits", 122 &details_j), 123 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 124 &td->exchange_sig), 125 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 126 &td->exchange_pub), 127 GNUNET_JSON_spec_end () 128 }; 129 130 if (GNUNET_OK != 131 GNUNET_JSON_parse (json, 132 spec, 133 NULL, NULL)) 134 { 135 GNUNET_break_op (0); 136 return GNUNET_SYSERR; 137 } 138 if (GNUNET_OK != 139 TALER_amount_set_zero (td->total_amount.currency, 140 &total_expected)) 141 { 142 GNUNET_break_op (0); 143 return GNUNET_SYSERR; 144 } 145 if (GNUNET_OK != 146 TALER_EXCHANGE_test_signing_key ( 147 gth->keys, 148 &td->exchange_pub)) 149 { 150 GNUNET_break_op (0); 151 return GNUNET_SYSERR; 152 } 153 td->details_length = json_array_size (details_j); 154 { 155 struct GNUNET_HashContext *hash_context; 156 struct TALER_TrackTransferDetails *details; 157 158 details = GNUNET_new_array (td->details_length, 159 struct TALER_TrackTransferDetails); 160 td->details = details; 161 hash_context = GNUNET_CRYPTO_hash_context_start (); 162 for (unsigned int i = 0; i < td->details_length; i++) 163 { 164 struct TALER_TrackTransferDetails *detail = &details[i]; 165 struct json_t *detail_j = json_array_get (details_j, i); 166 struct GNUNET_JSON_Specification spec_detail[] = { 167 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 168 &detail->h_contract_terms), 169 GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub), 170 TALER_JSON_spec_amount ("deposit_value", 171 total_expected.currency, 172 &detail->coin_value), 173 TALER_JSON_spec_amount ("deposit_fee", 174 total_expected.currency, 175 &detail->coin_fee), 176 GNUNET_JSON_spec_mark_optional ( 177 TALER_JSON_spec_amount ("refund_total", 178 total_expected.currency, 179 &detail->refund_total), 180 NULL), 181 GNUNET_JSON_spec_end () 182 }; 183 184 GNUNET_assert (GNUNET_OK == 185 TALER_amount_set_zero (td->total_amount.currency, 186 &detail->refund_total)); 187 if ( (GNUNET_OK != 188 GNUNET_JSON_parse (detail_j, 189 spec_detail, 190 NULL, NULL)) || 191 (0 > 192 TALER_amount_add (&total_expected, 193 &total_expected, 194 &detail->coin_value)) || 195 (0 > 196 TALER_amount_subtract (&total_expected, 197 &total_expected, 198 &detail->coin_fee)) ) 199 { 200 GNUNET_break_op (0); 201 GNUNET_CRYPTO_hash_context_abort (hash_context); 202 GNUNET_free (details); 203 return GNUNET_SYSERR; 204 } 205 /* build up big hash for signature checking later */ 206 TALER_exchange_online_wire_deposit_append ( 207 hash_context, 208 &detail->h_contract_terms, 209 td->execution_time, 210 &detail->coin_pub, 211 &detail->coin_value, 212 &detail->coin_fee); 213 } 214 /* Check signature */ 215 GNUNET_CRYPTO_hash_context_finish (hash_context, 216 &td->h_details); 217 if (GNUNET_OK != 218 TALER_exchange_online_wire_deposit_verify ( 219 &td->total_amount, 220 &td->wire_fee, 221 &td->merchant_pub, 222 &td->h_payto, 223 &td->h_details, 224 &td->exchange_pub, 225 &td->exchange_sig)) 226 { 227 GNUNET_break_op (0); 228 GNUNET_free (details); 229 return GNUNET_SYSERR; 230 } 231 if (0 > 232 TALER_amount_subtract (&total_expected, 233 &total_expected, 234 &td->wire_fee)) 235 { 236 GNUNET_break_op (0); 237 GNUNET_free (details); 238 return GNUNET_SYSERR; 239 } 240 if (0 != 241 TALER_amount_cmp (&total_expected, 242 &td->total_amount)) 243 { 244 GNUNET_break_op (0); 245 GNUNET_free (details); 246 return GNUNET_SYSERR; 247 } 248 gth->cb (gth->cb_cls, 249 &tgr); 250 gth->cb = NULL; 251 GNUNET_free (details); 252 } 253 return GNUNET_OK; 254 } 255 256 257 /** 258 * Function called when we're done processing the 259 * HTTP GET /transfers/$WTID request. 260 * 261 * @param cls the `struct TALER_EXCHANGE_GetTransfersHandle` 262 * @param response_code HTTP response code, 0 on error 263 * @param response parsed JSON result, NULL on error 264 */ 265 static void 266 handle_transfers_get_finished (void *cls, 267 long response_code, 268 const void *response) 269 { 270 struct TALER_EXCHANGE_GetTransfersHandle *gth = cls; 271 const json_t *j = response; 272 struct TALER_EXCHANGE_GetTransfersResponse tgr = { 273 .hr.reply = j, 274 .hr.http_status = (unsigned int) response_code 275 }; 276 277 gth->job = NULL; 278 switch (response_code) 279 { 280 case 0: 281 tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 282 break; 283 case MHD_HTTP_OK: 284 if (GNUNET_OK == 285 check_transfers_get_response_ok (gth, 286 j)) 287 { 288 GNUNET_assert (NULL == gth->cb); 289 TALER_EXCHANGE_get_transfers_cancel (gth); 290 return; 291 } 292 GNUNET_break_op (0); 293 tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 294 tgr.hr.http_status = 0; 295 break; 296 case MHD_HTTP_BAD_REQUEST: 297 /* This should never happen, either us or the exchange is buggy 298 (or API version conflict); just pass JSON reply to the application */ 299 tgr.hr.ec = TALER_JSON_get_error_code (j); 300 tgr.hr.hint = TALER_JSON_get_error_hint (j); 301 break; 302 case MHD_HTTP_FORBIDDEN: 303 /* Nothing really to verify, exchange says one of the signatures is 304 invalid; as we checked them, this should never happen, we 305 should pass the JSON reply to the application */ 306 tgr.hr.ec = TALER_JSON_get_error_code (j); 307 tgr.hr.hint = TALER_JSON_get_error_hint (j); 308 break; 309 case MHD_HTTP_NOT_FOUND: 310 /* Exchange does not know about transaction; 311 we should pass the reply to the application */ 312 tgr.hr.ec = TALER_JSON_get_error_code (j); 313 tgr.hr.hint = TALER_JSON_get_error_hint (j); 314 break; 315 case MHD_HTTP_INTERNAL_SERVER_ERROR: 316 /* Server had an internal issue; we should retry, but this API 317 leaves this to the application */ 318 tgr.hr.ec = TALER_JSON_get_error_code (j); 319 tgr.hr.hint = TALER_JSON_get_error_hint (j); 320 break; 321 default: 322 /* unexpected response code */ 323 GNUNET_break_op (0); 324 tgr.hr.ec = TALER_JSON_get_error_code (j); 325 tgr.hr.hint = TALER_JSON_get_error_hint (j); 326 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 327 "Unexpected response code %u/%d for transfers get\n", 328 (unsigned int) response_code, 329 (int) tgr.hr.ec); 330 break; 331 } 332 if (NULL != gth->cb) 333 gth->cb (gth->cb_cls, 334 &tgr); 335 TALER_EXCHANGE_get_transfers_cancel (gth); 336 } 337 338 339 struct TALER_EXCHANGE_GetTransfersHandle * 340 TALER_EXCHANGE_get_transfers_create ( 341 struct GNUNET_CURL_Context *ctx, 342 const char *url, 343 struct TALER_EXCHANGE_Keys *keys, 344 const struct TALER_WireTransferIdentifierRawP *wtid) 345 { 346 struct TALER_EXCHANGE_GetTransfersHandle *gth; 347 348 gth = GNUNET_new (struct TALER_EXCHANGE_GetTransfersHandle); 349 gth->ctx = ctx; 350 gth->base_url = GNUNET_strdup (url); 351 gth->keys = TALER_EXCHANGE_keys_incref (keys); 352 gth->wtid = *wtid; 353 return gth; 354 } 355 356 357 enum TALER_ErrorCode 358 TALER_EXCHANGE_get_transfers_start ( 359 struct TALER_EXCHANGE_GetTransfersHandle *gth, 360 TALER_EXCHANGE_GetTransfersCallback cb, 361 TALER_EXCHANGE_GET_TRANSFERS_RESULT_CLOSURE *cb_cls) 362 { 363 char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32]; 364 CURL *eh; 365 366 if (NULL != gth->job) 367 { 368 GNUNET_break (0); 369 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 370 } 371 gth->cb = cb; 372 gth->cb_cls = cb_cls; 373 { 374 char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2]; 375 char *end; 376 377 end = GNUNET_STRINGS_data_to_string (>h->wtid, 378 sizeof (gth->wtid), 379 wtid_str, 380 sizeof (wtid_str)); 381 *end = '\0'; 382 GNUNET_snprintf (arg_str, 383 sizeof (arg_str), 384 "transfers/%s", 385 wtid_str); 386 } 387 gth->url = TALER_url_join (gth->base_url, 388 arg_str, 389 NULL); 390 if (NULL == gth->url) 391 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 392 eh = TALER_EXCHANGE_curl_easy_get_ (gth->url); 393 if (NULL == eh) 394 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 395 gth->job = GNUNET_CURL_job_add_with_ct_json (gth->ctx, 396 eh, 397 &handle_transfers_get_finished, 398 gth); 399 if (NULL == gth->job) 400 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 401 return TALER_EC_NONE; 402 } 403 404 405 void 406 TALER_EXCHANGE_get_transfers_cancel ( 407 struct TALER_EXCHANGE_GetTransfersHandle *gth) 408 { 409 if (NULL != gth->job) 410 { 411 GNUNET_CURL_job_cancel (gth->job); 412 gth->job = NULL; 413 } 414 GNUNET_free (gth->url); 415 GNUNET_free (gth->base_url); 416 TALER_EXCHANGE_keys_decref (gth->keys); 417 GNUNET_free (gth); 418 } 419 420 421 /* end of exchange_api_get-transfers-WTID.c */