diff options
Diffstat (limited to 'src/authorization/libanastasiseufin/lae_credit.c')
-rw-r--r-- | src/authorization/libanastasiseufin/lae_credit.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/src/authorization/libanastasiseufin/lae_credit.c b/src/authorization/libanastasiseufin/lae_credit.c new file mode 100644 index 0000000..b97536b --- /dev/null +++ b/src/authorization/libanastasiseufin/lae_credit.c | |||
@@ -0,0 +1,333 @@ | |||
1 | /* | ||
2 | This file is part of Anastasis | ||
3 | Copyright (C) 2017--2021 Anastasis SARL | ||
4 | |||
5 | Anastasis 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 | Anastasis 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 Anastasis; see the file COPYING. If not, | ||
17 | see <http://www.gnu.org/licenses/> | ||
18 | */ | ||
19 | /** | ||
20 | * @file libanastasiseufin/lae_credit.c | ||
21 | * @brief Implementation of the /history/incoming | ||
22 | * requests of the libeufin's Anastasis facade | ||
23 | * @author Christian Grothoff | ||
24 | * @author Marcello Stanisci | ||
25 | */ | ||
26 | #include "platform.h" | ||
27 | #include "lae_common.h" | ||
28 | #include <microhttpd.h> /* just for HTTP status codes */ | ||
29 | |||
30 | |||
31 | /** | ||
32 | * @brief A /history/incoming Handle | ||
33 | */ | ||
34 | struct ANASTASIS_EUFIN_CreditHistoryHandle | ||
35 | { | ||
36 | |||
37 | /** | ||
38 | * The url for this request. | ||
39 | */ | ||
40 | char *request_url; | ||
41 | |||
42 | /** | ||
43 | * Handle for the request. | ||
44 | */ | ||
45 | struct GNUNET_CURL_Job *job; | ||
46 | |||
47 | /** | ||
48 | * Function to call with the result. | ||
49 | */ | ||
50 | ANASTASIS_EUFIN_CreditHistoryCallback hcb; | ||
51 | |||
52 | /** | ||
53 | * Closure for @a cb. | ||
54 | */ | ||
55 | void *hcb_cls; | ||
56 | }; | ||
57 | |||
58 | |||
59 | /** | ||
60 | * Parse history given in JSON format and invoke the callback on each item. | ||
61 | * | ||
62 | * @param hh handle to the account history request | ||
63 | * @param history JSON array with the history | ||
64 | * @return #GNUNET_OK if history was valid and @a rhistory and @a balance | ||
65 | * were set, | ||
66 | * #GNUNET_SYSERR if there was a protocol violation in @a history | ||
67 | */ | ||
68 | static int | ||
69 | parse_account_history (struct ANASTASIS_EUFIN_CreditHistoryHandle *hh, | ||
70 | const json_t *history) | ||
71 | { | ||
72 | json_t *history_array; | ||
73 | |||
74 | if (NULL == (history_array = json_object_get (history, | ||
75 | "incoming_transactions"))) | ||
76 | { | ||
77 | GNUNET_break_op (0); | ||
78 | return GNUNET_SYSERR; | ||
79 | } | ||
80 | if (! json_is_array (history_array)) | ||
81 | { | ||
82 | GNUNET_break_op (0); | ||
83 | return GNUNET_SYSERR; | ||
84 | } | ||
85 | for (unsigned int i = 0; i<json_array_size (history_array); i++) | ||
86 | { | ||
87 | struct ANASTASIS_EUFIN_CreditDetails td; | ||
88 | uint64_t row_id; | ||
89 | struct GNUNET_JSON_Specification hist_spec[] = { | ||
90 | TALER_JSON_spec_amount_any ("amount", | ||
91 | &td.amount), | ||
92 | TALER_JSON_spec_absolute_time ("date", | ||
93 | &td.execution_date), | ||
94 | GNUNET_JSON_spec_uint64 ("row_id", | ||
95 | &row_id), | ||
96 | GNUNET_JSON_spec_string ("subject", | ||
97 | &td.wire_subject), | ||
98 | GNUNET_JSON_spec_string ("debit_account", | ||
99 | &td.debit_account_uri), | ||
100 | GNUNET_JSON_spec_string ("credit_account", | ||
101 | &td.credit_account_uri), | ||
102 | GNUNET_JSON_spec_end () | ||
103 | }; | ||
104 | json_t *transaction = json_array_get (history_array, | ||
105 | i); | ||
106 | |||
107 | if (GNUNET_OK != | ||
108 | GNUNET_JSON_parse (transaction, | ||
109 | hist_spec, | ||
110 | NULL, NULL)) | ||
111 | { | ||
112 | GNUNET_break_op (0); | ||
113 | return GNUNET_SYSERR; | ||
114 | } | ||
115 | if (GNUNET_OK != | ||
116 | hh->hcb (hh->hcb_cls, | ||
117 | MHD_HTTP_OK, | ||
118 | TALER_EC_NONE, | ||
119 | row_id, | ||
120 | &td, | ||
121 | transaction)) | ||
122 | { | ||
123 | hh->hcb = NULL; | ||
124 | GNUNET_JSON_parse_free (hist_spec); | ||
125 | return GNUNET_OK; | ||
126 | } | ||
127 | GNUNET_JSON_parse_free (hist_spec); | ||
128 | } | ||
129 | return GNUNET_OK; | ||
130 | } | ||
131 | |||
132 | |||
133 | /** | ||
134 | * Function called when we're done processing the | ||
135 | * HTTP /history/incoming request. | ||
136 | * | ||
137 | * @param cls the `struct ANASTASIS_EUFIN_CreditHistoryHandle` | ||
138 | * @param response_code HTTP response code, 0 on error | ||
139 | * @param response parsed JSON result, NULL on error | ||
140 | */ | ||
141 | static void | ||
142 | handle_credit_history_finished (void *cls, | ||
143 | long response_code, | ||
144 | const void *response) | ||
145 | { | ||
146 | struct ANASTASIS_EUFIN_CreditHistoryHandle *hh = cls; | ||
147 | const json_t *j = response; | ||
148 | enum TALER_ErrorCode ec; | ||
149 | |||
150 | hh->job = NULL; | ||
151 | switch (response_code) | ||
152 | { | ||
153 | case 0: | ||
154 | ec = TALER_EC_GENERIC_INVALID_RESPONSE; | ||
155 | break; | ||
156 | case MHD_HTTP_OK: | ||
157 | if (GNUNET_OK != | ||
158 | parse_account_history (hh, | ||
159 | j)) | ||
160 | { | ||
161 | GNUNET_break_op (0); | ||
162 | response_code = 0; | ||
163 | ec = TALER_EC_GENERIC_INVALID_RESPONSE; | ||
164 | break; | ||
165 | } | ||
166 | response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */ | ||
167 | ec = TALER_EC_NONE; | ||
168 | break; | ||
169 | case MHD_HTTP_NO_CONTENT: | ||
170 | ec = TALER_EC_NONE; | ||
171 | break; | ||
172 | case MHD_HTTP_BAD_REQUEST: | ||
173 | /* This should never happen, either us or the bank is buggy | ||
174 | (or API version conflict); just pass JSON reply to the application */ | ||
175 | GNUNET_break_op (0); | ||
176 | ec = TALER_JSON_get_error_code (j); | ||
177 | break; | ||
178 | case MHD_HTTP_UNAUTHORIZED: | ||
179 | /* Nothing really to verify, bank says the HTTP Authentication | ||
180 | failed. May happen if HTTP authentication is used and the | ||
181 | user supplied a wrong username/password combination. */ | ||
182 | ec = TALER_JSON_get_error_code (j); | ||
183 | break; | ||
184 | case MHD_HTTP_NOT_FOUND: | ||
185 | /* Nothing really to verify: the bank is either unaware | ||
186 | of the endpoint (not a bank), or of the account. | ||
187 | We should pass the JSON (?) reply to the application */ | ||
188 | ec = TALER_JSON_get_error_code (j); | ||
189 | break; | ||
190 | case MHD_HTTP_INTERNAL_SERVER_ERROR: | ||
191 | /* Server had an internal issue; we should retry, but this API | ||
192 | leaves this to the application */ | ||
193 | ec = TALER_JSON_get_error_code (j); | ||
194 | break; | ||
195 | default: | ||
196 | /* unexpected response code */ | ||
197 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
198 | "Unexpected response code %u\n", | ||
199 | (unsigned int) response_code); | ||
200 | ec = TALER_JSON_get_error_code (j); | ||
201 | break; | ||
202 | } | ||
203 | if (NULL != hh->hcb) | ||
204 | hh->hcb (hh->hcb_cls, | ||
205 | response_code, | ||
206 | ec, | ||
207 | 0LLU, | ||
208 | NULL, | ||
209 | j); | ||
210 | ANASTASIS_EUFIN_credit_history_cancel (hh); | ||
211 | } | ||
212 | |||
213 | |||
214 | struct ANASTASIS_EUFIN_CreditHistoryHandle * | ||
215 | ANASTASIS_EUFIN_credit_history ( | ||
216 | struct GNUNET_CURL_Context *ctx, | ||
217 | const struct ANASTASIS_EUFIN_AuthenticationData *auth, | ||
218 | uint64_t start_row, | ||
219 | int64_t num_results, | ||
220 | struct GNUNET_TIME_Relative timeout, | ||
221 | ANASTASIS_EUFIN_CreditHistoryCallback hres_cb, | ||
222 | void *hres_cb_cls) | ||
223 | { | ||
224 | char url[128]; | ||
225 | struct ANASTASIS_EUFIN_CreditHistoryHandle *hh; | ||
226 | CURL *eh; | ||
227 | unsigned long long tms; | ||
228 | |||
229 | if (0 == num_results) | ||
230 | { | ||
231 | GNUNET_break (0); | ||
232 | return NULL; | ||
233 | } | ||
234 | |||
235 | tms = (unsigned long long) (timeout.rel_value_us | ||
236 | / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); | ||
237 | if ( ( (UINT64_MAX == start_row) && | ||
238 | (0 > num_results) ) || | ||
239 | ( (0 == start_row) && | ||
240 | (0 < num_results) ) ) | ||
241 | { | ||
242 | if ( (0 < num_results) && | ||
243 | (! GNUNET_TIME_relative_is_zero (timeout)) ) | ||
244 | GNUNET_snprintf (url, | ||
245 | sizeof (url), | ||
246 | "history/incoming?delta=%lld&long_poll_ms=%llu", | ||
247 | (long long) num_results, | ||
248 | tms); | ||
249 | else | ||
250 | GNUNET_snprintf (url, | ||
251 | sizeof (url), | ||
252 | "history/incoming?delta=%lld", | ||
253 | (long long) num_results); | ||
254 | } | ||
255 | else | ||
256 | { | ||
257 | if ( (0 < num_results) && | ||
258 | (! GNUNET_TIME_relative_is_zero (timeout)) ) | ||
259 | GNUNET_snprintf (url, | ||
260 | sizeof (url), | ||
261 | "history/incoming?delta=%lld&start=%llu&long_poll_ms=%llu", | ||
262 | (long long) num_results, | ||
263 | (unsigned long long) start_row, | ||
264 | tms); | ||
265 | else | ||
266 | GNUNET_snprintf (url, | ||
267 | sizeof (url), | ||
268 | "history/incoming?delta=%lld&start=%llu", | ||
269 | (long long) num_results, | ||
270 | (unsigned long long) start_row); | ||
271 | } | ||
272 | hh = GNUNET_new (struct ANASTASIS_EUFIN_CreditHistoryHandle); | ||
273 | hh->hcb = hres_cb; | ||
274 | hh->hcb_cls = hres_cb_cls; | ||
275 | hh->request_url = Anastasis_url_join (auth->wire_gateway_url, | ||
276 | url, | ||
277 | NULL); | ||
278 | if (NULL == hh->request_url) | ||
279 | { | ||
280 | GNUNET_free (hh); | ||
281 | GNUNET_break (0); | ||
282 | return NULL; | ||
283 | } | ||
284 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
285 | "Requesting credit history at `%s'\n", | ||
286 | hh->request_url); | ||
287 | eh = curl_easy_init (); | ||
288 | if ( (NULL == eh) || | ||
289 | (GNUNET_OK != | ||
290 | ANASTASIS_EUFIN_setup_auth_ (eh, | ||
291 | auth)) || | ||
292 | (CURLE_OK != | ||
293 | curl_easy_setopt (eh, | ||
294 | CURLOPT_URL, | ||
295 | hh->request_url)) ) | ||
296 | { | ||
297 | GNUNET_break (0); | ||
298 | ANASTASIS_EUFIN_credit_history_cancel (hh); | ||
299 | if (NULL != eh) | ||
300 | curl_easy_cleanup (eh); | ||
301 | return NULL; | ||
302 | } | ||
303 | if (0 != tms) | ||
304 | { | ||
305 | GNUNET_break (CURLE_OK == | ||
306 | curl_easy_setopt (eh, | ||
307 | CURLOPT_TIMEOUT_MS, | ||
308 | (long) tms)); | ||
309 | } | ||
310 | hh->job = GNUNET_CURL_job_add2 (ctx, | ||
311 | eh, | ||
312 | NULL, | ||
313 | &handle_credit_history_finished, | ||
314 | hh); | ||
315 | return hh; | ||
316 | } | ||
317 | |||
318 | |||
319 | void | ||
320 | ANASTASIS_EUFIN_credit_history_cancel ( | ||
321 | struct ANASTASIS_EUFIN_CreditHistoryHandle *hh) | ||
322 | { | ||
323 | if (NULL != hh->job) | ||
324 | { | ||
325 | GNUNET_CURL_job_cancel (hh->job); | ||
326 | hh->job = NULL; | ||
327 | } | ||
328 | GNUNET_free (hh->request_url); | ||
329 | GNUNET_free (hh); | ||
330 | } | ||
331 | |||
332 | |||
333 | /* end of lae_credit.c */ | ||