lae_credit.c (9936B)
1 /* 2 This file is part of Anastasis 3 Copyright (C) 2017--2021 Anastasis SARL 4 5 Anastasis 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 Anastasis 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 Anastasis; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file libanastasiseufin/lae_credit.c 21 * @brief Implementation of the /history requests of the 22 * Taler revenue API (as served by libeufin-bank) 23 * @author Christian Grothoff 24 * @author Marcello Stanisci 25 */ 26 #include "platform.h" 27 #include "lae_common.h" 28 #include <microhttpd.h> /* just for HTTP status codes */ 29 30 31 /** 32 * @brief A /history Handle 33 */ 34 struct ANASTASIS_EUFIN_CreditHistoryHandle 35 { 36 37 /** 38 * The url for this request. 39 */ 40 char *request_url; 41 42 /** 43 * Handle for the request. 44 */ 45 struct GNUNET_CURL_Job *job; 46 47 /** 48 * Function to call with the result. 49 */ 50 ANASTASIS_EUFIN_CreditHistoryCallback hcb; 51 52 /** 53 * Closure for @a cb. 54 */ 55 void *hcb_cls; 56 }; 57 58 59 /** 60 * Parse history given in JSON format and invoke the callback on each item. 61 * 62 * @param hh handle to the account history request 63 * @param history JSON array with the history 64 * @return #GNUNET_OK if history was valid and @a rhistory and @a balance 65 * were set, 66 * #GNUNET_SYSERR if there was a protocol violation in @a history 67 */ 68 static enum GNUNET_GenericReturnValue 69 parse_account_history (struct ANASTASIS_EUFIN_CreditHistoryHandle *hh, 70 const json_t *history) 71 { 72 json_t *history_array; 73 const char *credit_account_uri; 74 75 if (NULL == (history_array = json_object_get (history, 76 "incoming_transactions"))) 77 { 78 GNUNET_break_op (0); 79 return GNUNET_SYSERR; 80 } 81 if (! json_is_array (history_array)) 82 { 83 GNUNET_break_op (0); 84 return GNUNET_SYSERR; 85 } 86 /* The credited account is the account we asked about, so the revenue API 87 states it once for the whole response instead of per transaction. */ 88 credit_account_uri = json_string_value (json_object_get (history, 89 "credit_account")); 90 if (NULL == credit_account_uri) 91 { 92 GNUNET_break_op (0); 93 return GNUNET_SYSERR; 94 } 95 for (size_t i = 0; i<json_array_size (history_array); i++) 96 { 97 struct ANASTASIS_EUFIN_CreditDetails td; 98 uint64_t row_id; 99 struct GNUNET_JSON_Specification hist_spec[] = { 100 TALER_JSON_spec_amount_any ("amount", 101 &td.amount), 102 GNUNET_JSON_spec_timestamp ("date", 103 &td.execution_date), 104 GNUNET_JSON_spec_uint64 ("row_id", 105 &row_id), 106 GNUNET_JSON_spec_string ("subject", 107 &td.wire_subject), 108 GNUNET_JSON_spec_string ("debit_account", 109 &td.debit_account_uri), 110 GNUNET_JSON_spec_end () 111 }; 112 json_t *transaction = json_array_get (history_array, 113 i); 114 115 td.credit_account_uri = credit_account_uri; 116 if (GNUNET_OK != 117 GNUNET_JSON_parse (transaction, 118 hist_spec, 119 NULL, NULL)) 120 { 121 GNUNET_break_op (0); 122 return GNUNET_SYSERR; 123 } 124 if (GNUNET_OK != 125 hh->hcb (hh->hcb_cls, 126 MHD_HTTP_OK, 127 TALER_EC_NONE, 128 row_id, 129 &td)) 130 { 131 hh->hcb = NULL; 132 GNUNET_JSON_parse_free (hist_spec); 133 return GNUNET_OK; 134 } 135 GNUNET_JSON_parse_free (hist_spec); 136 } 137 return GNUNET_OK; 138 } 139 140 141 /** 142 * Function called when we're done processing the 143 * HTTP /history request. 144 * 145 * @param cls the `struct ANASTASIS_EUFIN_CreditHistoryHandle` 146 * @param response_code HTTP response code, 0 on error 147 * @param response parsed JSON result, NULL on error 148 */ 149 static void 150 handle_credit_history_finished (void *cls, 151 long response_code, 152 const void *response) 153 { 154 struct ANASTASIS_EUFIN_CreditHistoryHandle *hh = cls; 155 const json_t *j = response; 156 enum TALER_ErrorCode ec; 157 158 hh->job = NULL; 159 switch (response_code) 160 { 161 case 0: 162 ec = TALER_EC_GENERIC_INVALID_RESPONSE; 163 break; 164 case MHD_HTTP_OK: 165 if (GNUNET_OK != 166 parse_account_history (hh, 167 j)) 168 { 169 GNUNET_break_op (0); 170 response_code = 0; 171 ec = TALER_EC_GENERIC_INVALID_RESPONSE; 172 break; 173 } 174 response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */ 175 ec = TALER_EC_NONE; 176 break; 177 case MHD_HTTP_NO_CONTENT: 178 ec = TALER_EC_NONE; 179 break; 180 case MHD_HTTP_BAD_REQUEST: 181 /* This should never happen, either us or the bank is buggy 182 (or API version conflict); just pass JSON reply to the application */ 183 GNUNET_break_op (0); 184 ec = TALER_JSON_get_error_code (j); 185 break; 186 case MHD_HTTP_UNAUTHORIZED: 187 /* Nothing really to verify, bank says the HTTP Authentication 188 failed. May happen if HTTP authentication is used and the 189 user supplied a wrong username/password combination. */ 190 ec = TALER_JSON_get_error_code (j); 191 break; 192 case MHD_HTTP_NOT_FOUND: 193 /* Nothing really to verify: the bank is either unaware 194 of the endpoint (not a bank), or of the account. 195 We should pass the JSON (?) reply to the application */ 196 ec = TALER_JSON_get_error_code (j); 197 break; 198 case MHD_HTTP_INTERNAL_SERVER_ERROR: 199 /* Server had an internal issue; we should retry, but this API 200 leaves this to the application */ 201 ec = TALER_JSON_get_error_code (j); 202 break; 203 default: 204 /* unexpected response code */ 205 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 206 "Unexpected response code %u\n", 207 (unsigned int) response_code); 208 ec = TALER_JSON_get_error_code (j); 209 break; 210 } 211 if (NULL != hh->hcb) 212 hh->hcb (hh->hcb_cls, 213 response_code, 214 ec, 215 0LLU, 216 NULL); 217 ANASTASIS_EUFIN_credit_history_cancel (hh); 218 } 219 220 221 struct ANASTASIS_EUFIN_CreditHistoryHandle * 222 ANASTASIS_EUFIN_credit_history ( 223 struct GNUNET_CURL_Context *ctx, 224 const struct ANASTASIS_EUFIN_AuthenticationData *auth, 225 uint64_t start_row, 226 int64_t num_results, 227 struct GNUNET_TIME_Relative timeout, 228 ANASTASIS_EUFIN_CreditHistoryCallback hres_cb, 229 void *hres_cb_cls) 230 { 231 char url[128]; 232 struct ANASTASIS_EUFIN_CreditHistoryHandle *hh; 233 CURL *eh; 234 unsigned long long tms; 235 236 if (0 == num_results) 237 { 238 GNUNET_break (0); 239 return NULL; 240 } 241 242 tms = (unsigned long long) (timeout.rel_value_us 243 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 244 if ( ( (UINT64_MAX == start_row) && 245 (0 > num_results) ) || 246 ( (0 == start_row) && 247 (0 < num_results) ) ) 248 { 249 if ( (0 < num_results) && 250 (! GNUNET_TIME_relative_is_zero (timeout)) ) 251 GNUNET_snprintf (url, 252 sizeof (url), 253 "history?limit=%lld&timeout_ms=%llu", 254 (long long) num_results, 255 tms); 256 else 257 GNUNET_snprintf (url, 258 sizeof (url), 259 "history?limit=%lld", 260 (long long) num_results); 261 } 262 else 263 { 264 if ( (0 < num_results) && 265 (! GNUNET_TIME_relative_is_zero (timeout)) ) 266 GNUNET_snprintf (url, 267 sizeof (url), 268 "history?limit=%lld&offset=%llu&timeout_ms=%llu", 269 (long long) num_results, 270 (unsigned long long) start_row, 271 tms); 272 else 273 GNUNET_snprintf (url, 274 sizeof (url), 275 "history?limit=%lld&offset=%llu", 276 (long long) num_results, 277 (unsigned long long) start_row); 278 } 279 hh = GNUNET_new (struct ANASTASIS_EUFIN_CreditHistoryHandle); 280 hh->hcb = hres_cb; 281 hh->hcb_cls = hres_cb_cls; 282 hh->request_url = TALER_url_join (auth->wire_gateway_url, 283 url, 284 NULL); 285 if (NULL == hh->request_url) 286 { 287 GNUNET_free (hh); 288 GNUNET_break (0); 289 return NULL; 290 } 291 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 292 "Requesting credit history at `%s'\n", 293 hh->request_url); 294 eh = curl_easy_init (); 295 if ( (NULL == eh) || 296 (GNUNET_OK != 297 ANASTASIS_EUFIN_setup_auth_ (eh, 298 auth)) || 299 (CURLE_OK != 300 curl_easy_setopt (eh, 301 CURLOPT_URL, 302 hh->request_url)) ) 303 { 304 GNUNET_break (0); 305 ANASTASIS_EUFIN_credit_history_cancel (hh); 306 if (NULL != eh) 307 curl_easy_cleanup (eh); 308 return NULL; 309 } 310 if (0 != tms) 311 { 312 GNUNET_break (CURLE_OK == 313 curl_easy_setopt (eh, 314 CURLOPT_TIMEOUT_MS, 315 (long) tms)); 316 } 317 hh->job = GNUNET_CURL_job_add2 (ctx, 318 eh, 319 NULL, 320 &handle_credit_history_finished, 321 hh); 322 return hh; 323 } 324 325 326 void 327 ANASTASIS_EUFIN_credit_history_cancel ( 328 struct ANASTASIS_EUFIN_CreditHistoryHandle *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 lae_credit.c */