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