bank_api_credit.c (13412B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2017--2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or 6 modify it under the terms of the GNU General Public License 7 as published by the Free Software Foundation; either version 3, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file bank-lib/bank_api_credit.c 21 * @brief Implementation of the /history/incoming 22 * requests of the bank's HTTP API. 23 * @author Christian Grothoff 24 * @author Marcello Stanisci 25 */ 26 #include "bank_api_common.h" 27 #include <microhttpd.h> /* just for HTTP status codes */ 28 #include "taler/taler_signatures.h" 29 30 31 /** 32 * How much longer than the application-specified timeout 33 * do we wait (giving the server a chance to respond)? 34 */ 35 #define GRACE_PERIOD_MS 1000 36 37 38 /** 39 * @brief A /history/incoming Handle 40 */ 41 struct TALER_BANK_CreditHistoryHandle 42 { 43 44 /** 45 * The url for this request. 46 */ 47 char *request_url; 48 49 /** 50 * Handle for the request. 51 */ 52 struct GNUNET_CURL_Job *job; 53 54 /** 55 * Function to call with the result. 56 */ 57 TALER_BANK_CreditHistoryCallback hcb; 58 59 /** 60 * Closure for @a cb. 61 */ 62 void *hcb_cls; 63 }; 64 65 66 /** 67 * Parse history given in JSON format and invoke the callback on each item. 68 * 69 * @param hh handle to the account history request 70 * @param history JSON array with the history 71 * @return #GNUNET_OK if history was valid and @a rhistory and @a balance 72 * were set, 73 * #GNUNET_SYSERR if there was a protocol violation in @a history 74 */ 75 static enum GNUNET_GenericReturnValue 76 parse_account_history (struct TALER_BANK_CreditHistoryHandle *hh, 77 const json_t *history) 78 { 79 struct TALER_BANK_CreditHistoryResponse chr = { 80 .http_status = MHD_HTTP_OK, 81 .ec = TALER_EC_NONE, 82 .response = history 83 }; 84 const json_t *history_array; 85 struct GNUNET_JSON_Specification spec[] = { 86 GNUNET_JSON_spec_array_const ("incoming_transactions", 87 &history_array), 88 TALER_JSON_spec_full_payto_uri ("credit_account", 89 &chr.details.ok.credit_account_uri), 90 GNUNET_JSON_spec_end () 91 }; 92 93 if (GNUNET_OK != 94 GNUNET_JSON_parse (history, 95 spec, 96 NULL, 97 NULL)) 98 { 99 GNUNET_break_op (0); 100 return GNUNET_SYSERR; 101 } 102 { 103 size_t len = json_array_size (history_array); 104 struct TALER_BANK_CreditDetails *cd; 105 106 GNUNET_break_op (0 != len); 107 cd = GNUNET_new_array (GNUNET_NZL (len), 108 struct TALER_BANK_CreditDetails); 109 for (size_t i = 0; i<len; i++) 110 { 111 struct TALER_BANK_CreditDetails *td = &cd[i]; 112 const char *type; 113 bool no_credit_fee; 114 struct GNUNET_JSON_Specification hist_spec[] = { 115 GNUNET_JSON_spec_string ("type", 116 &type), 117 TALER_JSON_spec_amount_any ("amount", 118 &td->amount), 119 GNUNET_JSON_spec_mark_optional ( 120 TALER_JSON_spec_amount_any ("credit_fee", 121 &td->credit_fee), 122 &no_credit_fee), 123 GNUNET_JSON_spec_timestamp ("date", 124 &td->execution_date), 125 GNUNET_JSON_spec_uint64 ("row_id", 126 &td->serial_id), 127 TALER_JSON_spec_full_payto_uri ("debit_account", 128 &td->debit_account_uri), 129 GNUNET_JSON_spec_end () 130 }; 131 json_t *transaction = json_array_get (history_array, 132 i); 133 134 if (GNUNET_OK != 135 GNUNET_JSON_parse (transaction, 136 hist_spec, 137 NULL, 138 NULL)) 139 { 140 GNUNET_break_op (0); 141 goto fail; 142 } 143 if (no_credit_fee) 144 { 145 GNUNET_assert (GNUNET_OK == 146 TALER_amount_set_zero (td->amount.currency, 147 &td->credit_fee)); 148 } 149 else 150 { 151 if (GNUNET_YES != 152 TALER_amount_cmp_currency (&td->amount, 153 &td->credit_fee)) 154 { 155 GNUNET_break_op (0); 156 goto fail; 157 } 158 } 159 if (0 == strcasecmp ("RESERVE", 160 type)) 161 { 162 struct GNUNET_JSON_Specification reserve_spec[] = { 163 GNUNET_JSON_spec_fixed_auto ("reserve_pub", 164 &td->details.reserve.reserve_pub), 165 GNUNET_JSON_spec_end () 166 }; 167 168 if (GNUNET_OK != 169 GNUNET_JSON_parse (transaction, 170 reserve_spec, 171 NULL, 172 NULL)) 173 { 174 GNUNET_break_op (0); 175 goto fail; 176 } 177 td->type = TALER_BANK_CT_RESERVE; 178 } 179 else if (0 == strcasecmp ("KYCAUTH", 180 type)) 181 { 182 struct GNUNET_JSON_Specification kycauth_spec[] = { 183 GNUNET_JSON_spec_fixed_auto ("account_pub", 184 &td->details.kycauth.account_pub), 185 GNUNET_JSON_spec_end () 186 }; 187 188 if (GNUNET_OK != 189 GNUNET_JSON_parse (transaction, 190 kycauth_spec, 191 NULL, 192 NULL)) 193 { 194 GNUNET_break_op (0); 195 goto fail; 196 } 197 td->type = TALER_BANK_CT_KYCAUTH; 198 } 199 else if (0 == strcasecmp ("WAD", 200 type)) 201 { 202 struct GNUNET_JSON_Specification wad_spec[] = { 203 TALER_JSON_spec_web_url ("origin_exchange_url", 204 &td->details.wad.origin_exchange_url), 205 GNUNET_JSON_spec_fixed_auto ("wad_id", 206 &td->details.wad.wad_id), 207 GNUNET_JSON_spec_end () 208 }; 209 210 if (GNUNET_OK != 211 GNUNET_JSON_parse (transaction, 212 wad_spec, 213 NULL, 214 NULL)) 215 { 216 GNUNET_break_op (0); 217 goto fail; 218 } 219 td->type = TALER_BANK_CT_WAD; 220 } 221 else 222 { 223 GNUNET_break_op (0); 224 goto fail; 225 } 226 } 227 chr.details.ok.details_length = len; 228 chr.details.ok.details = cd; 229 hh->hcb (hh->hcb_cls, 230 &chr); 231 GNUNET_free (cd); 232 return GNUNET_OK; 233 fail: 234 GNUNET_free (cd); 235 return GNUNET_SYSERR; 236 } 237 } 238 239 240 /** 241 * Function called when we're done processing the 242 * HTTP /history/incoming request. 243 * 244 * @param cls the `struct TALER_BANK_CreditHistoryHandle` 245 * @param response_code HTTP response code, 0 on error 246 * @param response parsed JSON result, NULL on error 247 */ 248 static void 249 handle_credit_history_finished (void *cls, 250 long response_code, 251 const void *response) 252 { 253 struct TALER_BANK_CreditHistoryHandle *hh = cls; 254 struct TALER_BANK_CreditHistoryResponse chr = { 255 .http_status = response_code, 256 .response = response 257 }; 258 259 hh->job = NULL; 260 switch (response_code) 261 { 262 case 0: 263 chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 264 break; 265 case MHD_HTTP_OK: 266 if (GNUNET_OK != 267 parse_account_history (hh, 268 chr.response)) 269 { 270 GNUNET_break_op (0); 271 json_dumpf (chr.response, 272 stderr, 273 JSON_INDENT (2)); 274 chr.http_status = 0; 275 chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 276 break; 277 } 278 TALER_BANK_credit_history_cancel (hh); 279 return; 280 case MHD_HTTP_NO_CONTENT: 281 break; 282 case MHD_HTTP_BAD_REQUEST: 283 /* This should never happen, either us or the bank is buggy 284 (or API version conflict); just pass JSON reply to the application */ 285 GNUNET_break_op (0); 286 chr.ec = TALER_JSON_get_error_code (chr.response); 287 break; 288 case MHD_HTTP_UNAUTHORIZED: 289 /* Nothing really to verify, bank says the HTTP Authentication 290 failed. May happen if HTTP authentication is used and the 291 user supplied a wrong username/password combination. */ 292 chr.ec = TALER_JSON_get_error_code (chr.response); 293 break; 294 case MHD_HTTP_NOT_FOUND: 295 /* Nothing really to verify: the bank is either unaware 296 of the endpoint (not a bank), or of the account. 297 We should pass the JSON (?) reply to the application */ 298 chr.ec = TALER_JSON_get_error_code (chr.response); 299 break; 300 case MHD_HTTP_INTERNAL_SERVER_ERROR: 301 /* Server had an internal issue; we should retry, but this API 302 leaves this to the application */ 303 chr.ec = TALER_JSON_get_error_code (chr.response); 304 break; 305 default: 306 /* unexpected response code */ 307 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 308 "Unexpected response code %u\n", 309 (unsigned int) response_code); 310 chr.ec = TALER_JSON_get_error_code (chr.response); 311 break; 312 } 313 hh->hcb (hh->hcb_cls, 314 &chr); 315 TALER_BANK_credit_history_cancel (hh); 316 } 317 318 319 struct TALER_BANK_CreditHistoryHandle * 320 TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx, 321 const struct TALER_BANK_AuthenticationData *auth, 322 uint64_t start_row, 323 int64_t num_results, 324 struct GNUNET_TIME_Relative timeout, 325 TALER_BANK_CreditHistoryCallback hres_cb, 326 void *hres_cb_cls) 327 { 328 char url[128]; 329 struct TALER_BANK_CreditHistoryHandle *hh; 330 CURL *eh; 331 unsigned long long tms; 332 333 if (0 == num_results) 334 { 335 GNUNET_break (0); 336 return NULL; 337 } 338 339 tms = (unsigned long long) (timeout.rel_value_us 340 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 341 if ( ( (UINT64_MAX == start_row) && 342 (0 > num_results) ) || 343 ( (0 == start_row) && 344 (0 < num_results) ) ) 345 { 346 if ( (0 < num_results) && 347 (! GNUNET_TIME_relative_is_zero (timeout)) ) 348 /* 0 == start_row is implied, go with timeout into future */ 349 GNUNET_snprintf (url, 350 sizeof (url), 351 "history/incoming?limit=%lld&long_poll_ms=%llu", 352 (long long) num_results, 353 tms); 354 else 355 /* Going back from current transaction or have no timeout; 356 hence timeout makes no sense */ 357 GNUNET_snprintf (url, 358 sizeof (url), 359 "history/incoming?limit=%lld", 360 (long long) num_results); 361 } 362 else 363 { 364 if ( (0 < num_results) && 365 (! GNUNET_TIME_relative_is_zero (timeout)) ) 366 /* going forward from num_result */ 367 GNUNET_snprintf (url, 368 sizeof (url), 369 "history/incoming?limit=%lld&offset=%llu&long_poll_ms=%llu", 370 (long long) num_results, 371 (unsigned long long) start_row, 372 tms); 373 else 374 /* going backwards or have no timeout; 375 hence timeout makes no sense */ 376 GNUNET_snprintf (url, 377 sizeof (url), 378 "history/incoming?limit=%lld&offset=%llu", 379 (long long) num_results, 380 (unsigned long long) start_row); 381 } 382 hh = GNUNET_new (struct TALER_BANK_CreditHistoryHandle); 383 hh->hcb = hres_cb; 384 hh->hcb_cls = hres_cb_cls; 385 hh->request_url = TALER_url_join (auth->wire_gateway_url, 386 url, 387 NULL); 388 if (NULL == hh->request_url) 389 { 390 GNUNET_free (hh); 391 GNUNET_break (0); 392 return NULL; 393 } 394 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 395 "Requesting credit history at `%s'\n", 396 hh->request_url); 397 eh = curl_easy_init (); 398 if ( (NULL == eh) || 399 (GNUNET_OK != 400 TALER_BANK_setup_auth_ (eh, 401 auth)) || 402 (CURLE_OK != 403 curl_easy_setopt (eh, 404 CURLOPT_URL, 405 hh->request_url)) ) 406 { 407 GNUNET_break (0); 408 TALER_BANK_credit_history_cancel (hh); 409 if (NULL != eh) 410 curl_easy_cleanup (eh); 411 return NULL; 412 } 413 if (0 != tms) 414 { 415 GNUNET_break (CURLE_OK == 416 curl_easy_setopt (eh, 417 CURLOPT_TIMEOUT_MS, 418 (long) tms + GRACE_PERIOD_MS)); 419 } 420 hh->job = GNUNET_CURL_job_add2 (ctx, 421 eh, 422 NULL, 423 &handle_credit_history_finished, 424 hh); 425 return hh; 426 } 427 428 429 void 430 TALER_BANK_credit_history_cancel (struct TALER_BANK_CreditHistoryHandle *hh) 431 { 432 if (NULL != hh->job) 433 { 434 GNUNET_CURL_job_cancel (hh->job); 435 hh->job = NULL; 436 } 437 GNUNET_free (hh->request_url); 438 GNUNET_free (hh); 439 } 440 441 442 /* end of bank_api_credit.c */