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