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