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