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