bank_api_debit.c (10246B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2017--2023 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_debit.c 21 * @brief Implementation of the /history/outgoing 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/outgoing Handle 41 */ 42 struct TALER_BANK_DebitHistoryHandle 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_DebitHistoryCallback 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_DebitHistoryHandle *hh, 78 const json_t *history) 79 { 80 struct TALER_BANK_DebitHistoryResponse dhr = { 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 ("outgoing_transactions", 88 &history_array), 89 TALER_JSON_spec_full_payto_uri ("debit_account", 90 &dhr.details.ok.debit_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_DebitDetails dd[GNUNET_NZL (len)]; 106 107 GNUNET_break_op (0 != len); 108 for (unsigned int i = 0; i<len; i++) 109 { 110 struct TALER_BANK_DebitDetails *td = &dd[i]; 111 struct GNUNET_JSON_Specification hist_spec[] = { 112 TALER_JSON_spec_amount_any ("amount", 113 &td->amount), 114 GNUNET_JSON_spec_timestamp ("date", 115 &td->execution_date), 116 GNUNET_JSON_spec_uint64 ("row_id", 117 &td->serial_id), 118 GNUNET_JSON_spec_fixed_auto ("wtid", 119 &td->wtid), 120 TALER_JSON_spec_full_payto_uri ("credit_account", 121 &td->credit_account_uri), 122 TALER_JSON_spec_web_url ("exchange_base_url", 123 &td->exchange_base_url), 124 GNUNET_JSON_spec_end () 125 }; 126 json_t *transaction = json_array_get (history_array, 127 i); 128 129 if (GNUNET_OK != 130 GNUNET_JSON_parse (transaction, 131 hist_spec, 132 NULL, 133 NULL)) 134 { 135 GNUNET_break_op (0); 136 return GNUNET_SYSERR; 137 } 138 } 139 dhr.details.ok.details_length = len; 140 dhr.details.ok.details = dd; 141 hh->hcb (hh->hcb_cls, 142 &dhr); 143 } 144 return GNUNET_OK; 145 } 146 147 148 /** 149 * Function called when we're done processing the 150 * HTTP /history/outgoing request. 151 * 152 * @param cls the `struct TALER_BANK_DebitHistoryHandle` 153 * @param response_code HTTP response code, 0 on error 154 * @param response parsed JSON result, NULL on error 155 */ 156 static void 157 handle_debit_history_finished (void *cls, 158 long response_code, 159 const void *response) 160 { 161 struct TALER_BANK_DebitHistoryHandle *hh = cls; 162 struct TALER_BANK_DebitHistoryResponse dhr = { 163 .http_status = response_code, 164 .response = response 165 }; 166 167 hh->job = NULL; 168 switch (response_code) 169 { 170 case 0: 171 dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 172 break; 173 case MHD_HTTP_OK: 174 if (GNUNET_OK != 175 parse_account_history (hh, 176 dhr.response)) 177 { 178 GNUNET_break_op (0); 179 dhr.http_status = 0; 180 dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 181 break; 182 } 183 TALER_BANK_debit_history_cancel (hh); 184 return; 185 case MHD_HTTP_NO_CONTENT: 186 break; 187 case MHD_HTTP_BAD_REQUEST: 188 /* This should never happen, either us or the bank is buggy 189 (or API version conflict); just pass JSON reply to the application */ 190 GNUNET_break_op (0); 191 dhr.ec = TALER_JSON_get_error_code (dhr.response); 192 break; 193 case MHD_HTTP_UNAUTHORIZED: 194 /* Nothing really to verify, bank says the HTTP Authentication 195 failed. May happen if HTTP authentication is used and the 196 user supplied a wrong username/password combination. */ 197 dhr.ec = TALER_JSON_get_error_code (dhr.response); 198 break; 199 case MHD_HTTP_NOT_FOUND: 200 /* Nothing really to verify: the bank is either unaware 201 of the endpoint (not a bank), or of the account. 202 We should pass the JSON (?) reply to the application */ 203 dhr.ec = TALER_JSON_get_error_code (dhr.response); 204 break; 205 case MHD_HTTP_INTERNAL_SERVER_ERROR: 206 /* Server had an internal issue; we should retry, but this API 207 leaves this to the application */ 208 dhr.ec = TALER_JSON_get_error_code (dhr.response); 209 break; 210 default: 211 /* unexpected response code */ 212 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 213 "Unexpected response code %u\n", 214 (unsigned int) response_code); 215 dhr.ec = TALER_JSON_get_error_code (dhr.response); 216 break; 217 } 218 hh->hcb (hh->hcb_cls, 219 &dhr); 220 TALER_BANK_debit_history_cancel (hh); 221 } 222 223 224 struct TALER_BANK_DebitHistoryHandle * 225 TALER_BANK_debit_history (struct GNUNET_CURL_Context *ctx, 226 const struct TALER_BANK_AuthenticationData *auth, 227 uint64_t start_row, 228 int64_t num_results, 229 struct GNUNET_TIME_Relative timeout, 230 TALER_BANK_DebitHistoryCallback hres_cb, 231 void *hres_cb_cls) 232 { 233 char url[128]; 234 struct TALER_BANK_DebitHistoryHandle *hh; 235 CURL *eh; 236 unsigned long long tms; 237 238 if (0 == num_results) 239 { 240 GNUNET_break (0); 241 return NULL; 242 } 243 244 tms = (unsigned long long) (timeout.rel_value_us 245 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 246 if ( ( (UINT64_MAX == start_row) && 247 (0 > num_results) ) || 248 ( (0 == start_row) && 249 (0 < num_results) ) ) 250 { 251 if ( (0 < num_results) && 252 (! GNUNET_TIME_relative_is_zero (timeout)) ) 253 GNUNET_snprintf (url, 254 sizeof (url), 255 "history/outgoing?limit=%lld&long_poll_ms=%llu", 256 (long long) num_results, 257 tms); 258 else 259 GNUNET_snprintf (url, 260 sizeof (url), 261 "history/outgoing?limit=%lld", 262 (long long) num_results); 263 } 264 else 265 { 266 if ( (0 < num_results) && 267 (! GNUNET_TIME_relative_is_zero (timeout)) ) 268 GNUNET_snprintf (url, 269 sizeof (url), 270 "history/outgoing?limit=%lld&offset=%llu&long_poll_ms=%llu", 271 (long long) num_results, 272 (unsigned long long) start_row, 273 tms); 274 else 275 GNUNET_snprintf (url, 276 sizeof (url), 277 "history/outgoing?limit=%lld&offset=%llu", 278 (long long) num_results, 279 (unsigned long long) start_row); 280 } 281 hh = GNUNET_new (struct TALER_BANK_DebitHistoryHandle); 282 hh->hcb = hres_cb; 283 hh->hcb_cls = hres_cb_cls; 284 hh->request_url = TALER_url_join (auth->wire_gateway_url, 285 url, 286 NULL); 287 if (NULL == hh->request_url) 288 { 289 GNUNET_free (hh); 290 GNUNET_break (0); 291 return NULL; 292 } 293 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 294 "Requesting debit history at `%s'\n", 295 hh->request_url); 296 eh = curl_easy_init (); 297 if ( (NULL == eh) || 298 (GNUNET_OK != 299 TALER_BANK_setup_auth_ (eh, 300 auth)) || 301 (CURLE_OK != 302 curl_easy_setopt (eh, 303 CURLOPT_URL, 304 hh->request_url)) ) 305 { 306 GNUNET_break (0); 307 TALER_BANK_debit_history_cancel (hh); 308 if (NULL != eh) 309 curl_easy_cleanup (eh); 310 return NULL; 311 } 312 if (0 != tms) 313 { 314 GNUNET_break (CURLE_OK == 315 curl_easy_setopt (eh, 316 CURLOPT_TIMEOUT_MS, 317 (long) tms + GRACE_PERIOD_MS)); 318 } 319 hh->job = GNUNET_CURL_job_add2 (ctx, 320 eh, 321 NULL, 322 &handle_debit_history_finished, 323 hh); 324 return hh; 325 } 326 327 328 void 329 TALER_BANK_debit_history_cancel (struct TALER_BANK_DebitHistoryHandle *hh) 330 { 331 if (NULL != hh->job) 332 { 333 GNUNET_CURL_job_cancel (hh->job); 334 hh->job = NULL; 335 } 336 GNUNET_free (hh->request_url); 337 GNUNET_free (hh); 338 } 339 340 341 /* end of bank_api_debit.c */