exchange_api_get-transfers-WTID.c (12999B)
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_MerchantPublicKeyP merchant_pub; 105 struct TALER_EXCHANGE_GetTransfersResponse tgr = { 106 .hr.reply = json, 107 .hr.http_status = MHD_HTTP_OK 108 }; 109 struct TALER_EXCHANGE_TransferData *td 110 = &tgr.details.ok.td; 111 struct GNUNET_JSON_Specification spec[] = { 112 TALER_JSON_spec_amount_any ("total", 113 &td->total_amount), 114 TALER_JSON_spec_amount_any ("wire_fee", 115 &td->wire_fee), 116 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 117 &merchant_pub), 118 GNUNET_JSON_spec_fixed_auto ("h_payto", 119 &td->h_payto), 120 GNUNET_JSON_spec_timestamp ("execution_time", 121 &td->execution_time), 122 GNUNET_JSON_spec_array_const ("deposits", 123 &details_j), 124 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 125 &td->exchange_sig), 126 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 127 &td->exchange_pub), 128 GNUNET_JSON_spec_end () 129 }; 130 131 if (GNUNET_OK != 132 GNUNET_JSON_parse (json, 133 spec, 134 NULL, NULL)) 135 { 136 GNUNET_break_op (0); 137 return GNUNET_SYSERR; 138 } 139 if (GNUNET_OK != 140 TALER_amount_set_zero (td->total_amount.currency, 141 &total_expected)) 142 { 143 GNUNET_break_op (0); 144 return GNUNET_SYSERR; 145 } 146 if (GNUNET_OK != 147 TALER_EXCHANGE_test_signing_key ( 148 gth->keys, 149 &td->exchange_pub)) 150 { 151 GNUNET_break_op (0); 152 return GNUNET_SYSERR; 153 } 154 td->details_length = json_array_size (details_j); 155 { 156 struct GNUNET_HashContext *hash_context; 157 struct TALER_TrackTransferDetails *details; 158 159 details = GNUNET_new_array (td->details_length, 160 struct TALER_TrackTransferDetails); 161 td->details = details; 162 hash_context = GNUNET_CRYPTO_hash_context_start (); 163 for (unsigned int i = 0; i < td->details_length; i++) 164 { 165 struct TALER_TrackTransferDetails *detail = &details[i]; 166 struct json_t *detail_j = json_array_get (details_j, i); 167 struct GNUNET_JSON_Specification spec_detail[] = { 168 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 169 &detail->h_contract_terms), 170 GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub), 171 TALER_JSON_spec_amount ("deposit_value", 172 total_expected.currency, 173 &detail->coin_value), 174 TALER_JSON_spec_amount ("deposit_fee", 175 total_expected.currency, 176 &detail->coin_fee), 177 GNUNET_JSON_spec_mark_optional ( 178 TALER_JSON_spec_amount ("refund_total", 179 total_expected.currency, 180 &detail->refund_total), 181 NULL), 182 GNUNET_JSON_spec_end () 183 }; 184 185 GNUNET_assert (GNUNET_OK == 186 TALER_amount_set_zero (td->total_amount.currency, 187 &detail->refund_total)); 188 if ( (GNUNET_OK != 189 GNUNET_JSON_parse (detail_j, 190 spec_detail, 191 NULL, NULL)) || 192 (0 > 193 TALER_amount_add (&total_expected, 194 &total_expected, 195 &detail->coin_value)) || 196 (0 > 197 TALER_amount_subtract (&total_expected, 198 &total_expected, 199 &detail->coin_fee)) ) 200 { 201 GNUNET_break_op (0); 202 GNUNET_CRYPTO_hash_context_abort (hash_context); 203 GNUNET_free (details); 204 return GNUNET_SYSERR; 205 } 206 /* build up big hash for signature checking later */ 207 TALER_exchange_online_wire_deposit_append ( 208 hash_context, 209 &detail->h_contract_terms, 210 td->execution_time, 211 &detail->coin_pub, 212 &detail->coin_value, 213 &detail->coin_fee); 214 } 215 /* Check signature */ 216 GNUNET_CRYPTO_hash_context_finish (hash_context, 217 &td->h_details); 218 if (GNUNET_OK != 219 TALER_exchange_online_wire_deposit_verify ( 220 &td->total_amount, 221 &td->wire_fee, 222 &merchant_pub, 223 &td->h_payto, 224 &td->h_details, 225 &td->exchange_pub, 226 &td->exchange_sig)) 227 { 228 GNUNET_break_op (0); 229 GNUNET_free (details); 230 return GNUNET_SYSERR; 231 } 232 if (0 > 233 TALER_amount_subtract (&total_expected, 234 &total_expected, 235 &td->wire_fee)) 236 { 237 GNUNET_break_op (0); 238 GNUNET_free (details); 239 return GNUNET_SYSERR; 240 } 241 if (0 != 242 TALER_amount_cmp (&total_expected, 243 &td->total_amount)) 244 { 245 GNUNET_break_op (0); 246 GNUNET_free (details); 247 return GNUNET_SYSERR; 248 } 249 gth->cb (gth->cb_cls, 250 &tgr); 251 gth->cb = NULL; 252 GNUNET_free (details); 253 } 254 return GNUNET_OK; 255 } 256 257 258 /** 259 * Function called when we're done processing the 260 * HTTP GET /transfers/$WTID request. 261 * 262 * @param cls the `struct TALER_EXCHANGE_GetTransfersHandle` 263 * @param response_code HTTP response code, 0 on error 264 * @param response parsed JSON result, NULL on error 265 */ 266 static void 267 handle_transfers_get_finished (void *cls, 268 long response_code, 269 const void *response) 270 { 271 struct TALER_EXCHANGE_GetTransfersHandle *gth = cls; 272 const json_t *j = response; 273 struct TALER_EXCHANGE_GetTransfersResponse tgr = { 274 .hr.reply = j, 275 .hr.http_status = (unsigned int) response_code 276 }; 277 278 gth->job = NULL; 279 switch (response_code) 280 { 281 case 0: 282 tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 283 break; 284 case MHD_HTTP_OK: 285 if (GNUNET_OK == 286 check_transfers_get_response_ok (gth, 287 j)) 288 { 289 GNUNET_assert (NULL == gth->cb); 290 TALER_EXCHANGE_get_transfers_cancel (gth); 291 return; 292 } 293 GNUNET_break_op (0); 294 tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 295 tgr.hr.http_status = 0; 296 break; 297 case MHD_HTTP_BAD_REQUEST: 298 /* This should never happen, either us or the exchange is buggy 299 (or API version conflict); just pass JSON reply to the application */ 300 tgr.hr.ec = TALER_JSON_get_error_code (j); 301 tgr.hr.hint = TALER_JSON_get_error_hint (j); 302 break; 303 case MHD_HTTP_FORBIDDEN: 304 /* Nothing really to verify, exchange says one of the signatures is 305 invalid; as we checked them, this should never happen, we 306 should pass the JSON reply to the application */ 307 tgr.hr.ec = TALER_JSON_get_error_code (j); 308 tgr.hr.hint = TALER_JSON_get_error_hint (j); 309 break; 310 case MHD_HTTP_NOT_FOUND: 311 /* Exchange does not know about transaction; 312 we should pass the reply to the application */ 313 tgr.hr.ec = TALER_JSON_get_error_code (j); 314 tgr.hr.hint = TALER_JSON_get_error_hint (j); 315 break; 316 case MHD_HTTP_INTERNAL_SERVER_ERROR: 317 /* Server had an internal issue; we should retry, but this API 318 leaves this to the application */ 319 tgr.hr.ec = TALER_JSON_get_error_code (j); 320 tgr.hr.hint = TALER_JSON_get_error_hint (j); 321 break; 322 default: 323 /* unexpected response code */ 324 GNUNET_break_op (0); 325 tgr.hr.ec = TALER_JSON_get_error_code (j); 326 tgr.hr.hint = TALER_JSON_get_error_hint (j); 327 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 328 "Unexpected response code %u/%d for transfers get\n", 329 (unsigned int) response_code, 330 (int) tgr.hr.ec); 331 break; 332 } 333 if (NULL != gth->cb) 334 gth->cb (gth->cb_cls, 335 &tgr); 336 TALER_EXCHANGE_get_transfers_cancel (gth); 337 } 338 339 340 struct TALER_EXCHANGE_GetTransfersHandle * 341 TALER_EXCHANGE_get_transfers_create ( 342 struct GNUNET_CURL_Context *ctx, 343 const char *url, 344 struct TALER_EXCHANGE_Keys *keys, 345 const struct TALER_WireTransferIdentifierRawP *wtid) 346 { 347 struct TALER_EXCHANGE_GetTransfersHandle *gth; 348 349 gth = GNUNET_new (struct TALER_EXCHANGE_GetTransfersHandle); 350 gth->ctx = ctx; 351 gth->base_url = GNUNET_strdup (url); 352 gth->keys = TALER_EXCHANGE_keys_incref (keys); 353 gth->wtid = *wtid; 354 return gth; 355 } 356 357 358 enum TALER_ErrorCode 359 TALER_EXCHANGE_get_transfers_start ( 360 struct TALER_EXCHANGE_GetTransfersHandle *gth, 361 TALER_EXCHANGE_GetTransfersCallback cb, 362 TALER_EXCHANGE_GET_TRANSFERS_RESULT_CLOSURE *cb_cls) 363 { 364 char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32]; 365 CURL *eh; 366 367 if (NULL != gth->job) 368 { 369 GNUNET_break (0); 370 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 371 } 372 gth->cb = cb; 373 gth->cb_cls = cb_cls; 374 { 375 char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2]; 376 char *end; 377 378 end = GNUNET_STRINGS_data_to_string (>h->wtid, 379 sizeof (gth->wtid), 380 wtid_str, 381 sizeof (wtid_str)); 382 *end = '\0'; 383 GNUNET_snprintf (arg_str, 384 sizeof (arg_str), 385 "transfers/%s", 386 wtid_str); 387 } 388 gth->url = TALER_url_join (gth->base_url, 389 arg_str, 390 NULL); 391 if (NULL == gth->url) 392 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 393 eh = TALER_EXCHANGE_curl_easy_get_ (gth->url); 394 if (NULL == eh) 395 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 396 gth->job = GNUNET_CURL_job_add_with_ct_json (gth->ctx, 397 eh, 398 &handle_transfers_get_finished, 399 gth); 400 if (NULL == gth->job) 401 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 402 return TALER_EC_NONE; 403 } 404 405 406 void 407 TALER_EXCHANGE_get_transfers_cancel ( 408 struct TALER_EXCHANGE_GetTransfersHandle *gth) 409 { 410 if (NULL != gth->job) 411 { 412 GNUNET_CURL_job_cancel (gth->job); 413 gth->job = NULL; 414 } 415 GNUNET_free (gth->url); 416 GNUNET_free (gth->base_url); 417 TALER_EXCHANGE_keys_decref (gth->keys); 418 GNUNET_free (gth); 419 } 420 421 422 /* end of exchange_api_get-transfers-WTID.c */