Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file lib/exchange_api_reserves_history.c
19 : * @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <jansson.h>
24 : #include <microhttpd.h> /* just for HTTP history codes */
25 : #include <gnunet/gnunet_util_lib.h>
26 : #include <gnunet/gnunet_json_lib.h>
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include "taler_exchange_service.h"
29 : #include "taler_json_lib.h"
30 : #include "exchange_api_handle.h"
31 : #include "taler_signatures.h"
32 : #include "exchange_api_curl_defaults.h"
33 :
34 :
35 : /**
36 : * @brief A /reserves/$RID/history Handle
37 : */
38 : struct TALER_EXCHANGE_ReservesHistoryHandle
39 : {
40 :
41 : /**
42 : * The connection to exchange this request handle will use
43 : */
44 : struct TALER_EXCHANGE_Handle *exchange;
45 :
46 : /**
47 : * The url for this request.
48 : */
49 : char *url;
50 :
51 : /**
52 : * Handle for the request.
53 : */
54 : struct GNUNET_CURL_Job *job;
55 :
56 : /**
57 : * Context for #TEH_curl_easy_post(). Keeps the data that must
58 : * persist for Curl to make the upload.
59 : */
60 : struct TALER_CURL_PostContext post_ctx;
61 :
62 : /**
63 : * Function to call with the result.
64 : */
65 : TALER_EXCHANGE_ReservesHistoryCallback cb;
66 :
67 : /**
68 : * Closure for @a cb.
69 : */
70 : void *cb_cls;
71 :
72 : /**
73 : * Public key of the reserve we are querying.
74 : */
75 : struct TALER_ReservePublicKeyP reserve_pub;
76 :
77 : /**
78 : * Our signature.
79 : */
80 : struct TALER_ReserveSignatureP reserve_sig;
81 :
82 : /**
83 : * When did we make the request.
84 : */
85 : struct GNUNET_TIME_Timestamp ts;
86 :
87 : };
88 :
89 :
90 : /**
91 : * We received an #MHD_HTTP_OK history code. Handle the JSON
92 : * response.
93 : *
94 : * @param rsh handle of the request
95 : * @param j JSON response
96 : * @return #GNUNET_OK on success
97 : */
98 : static enum GNUNET_GenericReturnValue
99 0 : handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
100 : const json_t *j)
101 : {
102 : json_t *history;
103 : unsigned int len;
104 0 : struct TALER_EXCHANGE_ReserveHistory rs = {
105 : .hr.reply = j,
106 : .hr.http_status = MHD_HTTP_OK,
107 : .ts = rsh->ts,
108 0 : .reserve_sig = &rsh->reserve_sig
109 : };
110 : struct GNUNET_JSON_Specification spec[] = {
111 0 : TALER_JSON_spec_amount_any ("balance",
112 : &rs.details.ok.balance),
113 0 : GNUNET_JSON_spec_json ("history",
114 : &history),
115 0 : GNUNET_JSON_spec_end ()
116 : };
117 :
118 0 : if (GNUNET_OK !=
119 0 : GNUNET_JSON_parse (j,
120 : spec,
121 : NULL,
122 : NULL))
123 : {
124 0 : GNUNET_break_op (0);
125 0 : return GNUNET_SYSERR;
126 : }
127 0 : len = json_array_size (history);
128 : {
129 : struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
130 :
131 0 : rhistory = GNUNET_new_array (len,
132 : struct TALER_EXCHANGE_ReserveHistoryEntry);
133 0 : if (GNUNET_OK !=
134 0 : TALER_EXCHANGE_parse_reserve_history (rsh->exchange,
135 : history,
136 0 : &rsh->reserve_pub,
137 : rs.details.ok.balance.currency,
138 : &rs.details.ok.total_in,
139 : &rs.details.ok.total_out,
140 : len,
141 : rhistory))
142 : {
143 0 : GNUNET_break_op (0);
144 0 : TALER_EXCHANGE_free_reserve_history (rhistory,
145 : len);
146 0 : GNUNET_JSON_parse_free (spec);
147 0 : return GNUNET_SYSERR;
148 : }
149 0 : if (NULL != rsh->cb)
150 : {
151 0 : rs.details.ok.history = rhistory;
152 0 : rs.details.ok.history_len = len;
153 0 : rsh->cb (rsh->cb_cls,
154 : &rs);
155 0 : rsh->cb = NULL;
156 : }
157 0 : TALER_EXCHANGE_free_reserve_history (rhistory,
158 : len);
159 : }
160 0 : GNUNET_JSON_parse_free (spec);
161 0 : return GNUNET_OK;
162 : }
163 :
164 :
165 : /**
166 : * Function called when we're done processing the
167 : * HTTP /reserves/$RID/history request.
168 : *
169 : * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle`
170 : * @param response_code HTTP response code, 0 on error
171 : * @param response parsed JSON result, NULL on error
172 : */
173 : static void
174 0 : handle_reserves_history_finished (void *cls,
175 : long response_code,
176 : const void *response)
177 : {
178 0 : struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls;
179 0 : const json_t *j = response;
180 0 : struct TALER_EXCHANGE_ReserveHistory rs = {
181 : .hr.reply = j,
182 0 : .hr.http_status = (unsigned int) response_code
183 : };
184 :
185 0 : rsh->job = NULL;
186 0 : switch (response_code)
187 : {
188 0 : case 0:
189 0 : rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
190 0 : break;
191 0 : case MHD_HTTP_OK:
192 0 : if (GNUNET_OK !=
193 0 : handle_reserves_history_ok (rsh,
194 : j))
195 : {
196 0 : GNUNET_break_op (0);
197 0 : rs.hr.http_status = 0;
198 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
199 : }
200 0 : break;
201 0 : case MHD_HTTP_BAD_REQUEST:
202 : /* This should never happen, either us or the exchange is buggy
203 : (or API version conflict); just pass JSON reply to the application */
204 0 : GNUNET_break (0);
205 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
206 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
207 0 : break;
208 0 : case MHD_HTTP_FORBIDDEN:
209 : /* This should never happen, either us or the exchange is buggy
210 : (or API version conflict); just pass JSON reply to the application */
211 0 : GNUNET_break (0);
212 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
213 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
214 0 : break;
215 0 : case MHD_HTTP_NOT_FOUND:
216 : /* Nothing really to verify, this should never
217 : happen, we should pass the JSON reply to the application */
218 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
219 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
220 0 : break;
221 0 : case MHD_HTTP_CONFLICT:
222 : /* Insufficient balance to inquire for reserve history */
223 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
224 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
225 0 : break;
226 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
227 : /* Server had an internal issue; we should retry, but this API
228 : leaves this to the application */
229 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
230 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
231 0 : break;
232 0 : default:
233 : /* unexpected response code */
234 0 : GNUNET_break_op (0);
235 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
236 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
237 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
238 : "Unexpected response code %u/%d for reserves history\n",
239 : (unsigned int) response_code,
240 : (int) rs.hr.ec);
241 0 : break;
242 : }
243 0 : if (NULL != rsh->cb)
244 : {
245 0 : rsh->cb (rsh->cb_cls,
246 : &rs);
247 0 : rsh->cb = NULL;
248 : }
249 0 : TALER_EXCHANGE_reserves_history_cancel (rsh);
250 0 : }
251 :
252 :
253 : struct TALER_EXCHANGE_ReservesHistoryHandle *
254 0 : TALER_EXCHANGE_reserves_history (
255 : struct TALER_EXCHANGE_Handle *exchange,
256 : const struct TALER_ReservePrivateKeyP *reserve_priv,
257 : TALER_EXCHANGE_ReservesHistoryCallback cb,
258 : void *cb_cls)
259 : {
260 : struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
261 : struct GNUNET_CURL_Context *ctx;
262 : CURL *eh;
263 : char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
264 : const struct TALER_EXCHANGE_Keys *keys;
265 : const struct TALER_EXCHANGE_GlobalFee *gf;
266 :
267 0 : if (GNUNET_YES !=
268 0 : TEAH_handle_is_ready (exchange))
269 : {
270 0 : GNUNET_break (0);
271 0 : return NULL;
272 : }
273 0 : rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
274 0 : rsh->exchange = exchange;
275 0 : rsh->cb = cb;
276 0 : rsh->cb_cls = cb_cls;
277 0 : rsh->ts = GNUNET_TIME_timestamp_get ();
278 0 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
279 : &rsh->reserve_pub.eddsa_pub);
280 : {
281 : char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
282 : char *end;
283 :
284 0 : end = GNUNET_STRINGS_data_to_string (
285 0 : &rsh->reserve_pub,
286 : sizeof (rsh->reserve_pub),
287 : pub_str,
288 : sizeof (pub_str));
289 0 : *end = '\0';
290 0 : GNUNET_snprintf (arg_str,
291 : sizeof (arg_str),
292 : "/reserves/%s/history",
293 : pub_str);
294 : }
295 0 : rsh->url = TEAH_path_to_url (exchange,
296 : arg_str);
297 0 : if (NULL == rsh->url)
298 : {
299 0 : GNUNET_free (rsh);
300 0 : return NULL;
301 : }
302 0 : eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
303 0 : if (NULL == eh)
304 : {
305 0 : GNUNET_break (0);
306 0 : GNUNET_free (rsh->url);
307 0 : GNUNET_free (rsh);
308 0 : return NULL;
309 : }
310 0 : keys = TALER_EXCHANGE_get_keys (exchange);
311 0 : if (NULL == keys)
312 : {
313 0 : GNUNET_break (0);
314 0 : curl_easy_cleanup (eh);
315 0 : GNUNET_free (rsh->url);
316 0 : GNUNET_free (rsh);
317 0 : return NULL;
318 : }
319 0 : gf = TALER_EXCHANGE_get_global_fee (keys,
320 : rsh->ts);
321 0 : if (NULL == gf)
322 : {
323 0 : GNUNET_break_op (0);
324 0 : curl_easy_cleanup (eh);
325 0 : GNUNET_free (rsh->url);
326 0 : GNUNET_free (rsh);
327 0 : return NULL;
328 : }
329 0 : TALER_wallet_reserve_history_sign (rsh->ts,
330 : &gf->fees.history,
331 : reserve_priv,
332 : &rsh->reserve_sig);
333 : {
334 0 : json_t *history_obj = GNUNET_JSON_PACK (
335 : GNUNET_JSON_pack_timestamp ("request_timestamp",
336 : rsh->ts),
337 : GNUNET_JSON_pack_data_auto ("reserve_sig",
338 : &rsh->reserve_sig));
339 :
340 0 : if (GNUNET_OK !=
341 0 : TALER_curl_easy_post (&rsh->post_ctx,
342 : eh,
343 : history_obj))
344 : {
345 0 : GNUNET_break (0);
346 0 : curl_easy_cleanup (eh);
347 0 : json_decref (history_obj);
348 0 : GNUNET_free (rsh->url);
349 0 : GNUNET_free (rsh);
350 0 : return NULL;
351 : }
352 0 : json_decref (history_obj);
353 : }
354 0 : ctx = TEAH_handle_to_context (exchange);
355 0 : rsh->job = GNUNET_CURL_job_add2 (ctx,
356 : eh,
357 0 : rsh->post_ctx.headers,
358 : &handle_reserves_history_finished,
359 : rsh);
360 0 : return rsh;
361 : }
362 :
363 :
364 : void
365 0 : TALER_EXCHANGE_reserves_history_cancel (
366 : struct TALER_EXCHANGE_ReservesHistoryHandle *rsh)
367 : {
368 0 : if (NULL != rsh->job)
369 : {
370 0 : GNUNET_CURL_job_cancel (rsh->job);
371 0 : rsh->job = NULL;
372 : }
373 0 : TALER_curl_easy_post_finished (&rsh->post_ctx);
374 0 : GNUNET_free (rsh->url);
375 0 : GNUNET_free (rsh);
376 0 : }
377 :
378 :
379 : /* end of exchange_api_reserves_history.c */
|