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