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