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