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