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