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_status.c
19 : * @brief Implementation of the POST /reserves/$RESERVE_PUB/status requests
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <jansson.h>
24 : #include <microhttpd.h> /* just for HTTP status 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/status Handle
37 : */
38 : struct TALER_EXCHANGE_ReservesStatusHandle
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_ReservesStatusCallback cb;
66 :
67 : /**
68 : * Public key of the reserve we are querying.
69 : */
70 : struct TALER_ReservePublicKeyP reserve_pub;
71 :
72 : /**
73 : * Closure for @a cb.
74 : */
75 : void *cb_cls;
76 :
77 : };
78 :
79 :
80 : /**
81 : * We received an #MHD_HTTP_OK status code. Handle the JSON
82 : * response.
83 : *
84 : * @param rsh handle of the request
85 : * @param j JSON response
86 : * @return #GNUNET_OK on success
87 : */
88 : static enum GNUNET_GenericReturnValue
89 0 : handle_reserves_status_ok (struct TALER_EXCHANGE_ReservesStatusHandle *rsh,
90 : const json_t *j)
91 : {
92 : json_t *history;
93 : unsigned int len;
94 0 : struct TALER_EXCHANGE_ReserveStatus rs = {
95 : .hr.reply = j,
96 : .hr.http_status = MHD_HTTP_OK
97 : };
98 : struct GNUNET_JSON_Specification spec[] = {
99 0 : TALER_JSON_spec_amount_any ("balance",
100 : &rs.details.ok.balance),
101 0 : GNUNET_JSON_spec_json ("history",
102 : &history),
103 0 : GNUNET_JSON_spec_end ()
104 : };
105 :
106 0 : if (GNUNET_OK !=
107 0 : GNUNET_JSON_parse (j,
108 : spec,
109 : NULL,
110 : NULL))
111 : {
112 0 : GNUNET_break_op (0);
113 0 : return GNUNET_SYSERR;
114 : }
115 0 : len = json_array_size (history);
116 : {
117 : struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
118 :
119 0 : rhistory = GNUNET_new_array (len,
120 : struct TALER_EXCHANGE_ReserveHistoryEntry);
121 0 : if (GNUNET_OK !=
122 0 : TALER_EXCHANGE_parse_reserve_history (rsh->exchange,
123 : history,
124 0 : &rsh->reserve_pub,
125 : rs.details.ok.balance.currency,
126 : &rs.details.ok.total_in,
127 : &rs.details.ok.total_out,
128 : len,
129 : rhistory))
130 : {
131 0 : GNUNET_break_op (0);
132 0 : TALER_EXCHANGE_free_reserve_history (rhistory,
133 : len);
134 0 : GNUNET_JSON_parse_free (spec);
135 0 : return GNUNET_SYSERR;
136 : }
137 0 : if (NULL != rsh->cb)
138 : {
139 0 : rs.details.ok.history = rhistory;
140 0 : rs.details.ok.history_len = len;
141 0 : rsh->cb (rsh->cb_cls,
142 : &rs);
143 0 : rsh->cb = NULL;
144 : }
145 0 : TALER_EXCHANGE_free_reserve_history (rhistory,
146 : len);
147 : }
148 0 : GNUNET_JSON_parse_free (spec);
149 0 : return GNUNET_OK;
150 : }
151 :
152 :
153 : /**
154 : * Function called when we're done processing the
155 : * HTTP /reserves/$RID/status request.
156 : *
157 : * @param cls the `struct TALER_EXCHANGE_ReservesStatusHandle`
158 : * @param response_code HTTP response code, 0 on error
159 : * @param response parsed JSON result, NULL on error
160 : */
161 : static void
162 0 : handle_reserves_status_finished (void *cls,
163 : long response_code,
164 : const void *response)
165 : {
166 0 : struct TALER_EXCHANGE_ReservesStatusHandle *rsh = cls;
167 0 : const json_t *j = response;
168 0 : struct TALER_EXCHANGE_ReserveStatus rs = {
169 : .hr.reply = j,
170 0 : .hr.http_status = (unsigned int) response_code
171 : };
172 :
173 0 : rsh->job = NULL;
174 0 : switch (response_code)
175 : {
176 0 : case 0:
177 0 : rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
178 0 : break;
179 0 : case MHD_HTTP_OK:
180 0 : if (GNUNET_OK !=
181 0 : handle_reserves_status_ok (rsh,
182 : j))
183 : {
184 0 : rs.hr.http_status = 0;
185 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
186 : }
187 0 : break;
188 0 : case MHD_HTTP_BAD_REQUEST:
189 : /* This should never happen, either us or the exchange is buggy
190 : (or API version conflict); just pass JSON reply to the application */
191 0 : GNUNET_break (0);
192 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
193 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
194 0 : break;
195 0 : case MHD_HTTP_FORBIDDEN:
196 : /* This should never happen, either us or the exchange is buggy
197 : (or API version conflict); just pass JSON reply to the application */
198 0 : GNUNET_break (0);
199 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
200 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
201 0 : break;
202 0 : case MHD_HTTP_NOT_FOUND:
203 : /* Nothing really to verify, this should never
204 : happen, we should pass the JSON reply to the application */
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_INTERNAL_SERVER_ERROR:
209 : /* Server had an internal issue; we should retry, but this API
210 : leaves this to the application */
211 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
212 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
213 0 : break;
214 0 : default:
215 : /* unexpected response code */
216 0 : GNUNET_break_op (0);
217 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
218 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
219 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
220 : "Unexpected response code %u/%d for reserves status\n",
221 : (unsigned int) response_code,
222 : (int) rs.hr.ec);
223 0 : break;
224 : }
225 0 : if (NULL != rsh->cb)
226 : {
227 0 : rsh->cb (rsh->cb_cls,
228 : &rs);
229 0 : rsh->cb = NULL;
230 : }
231 0 : TALER_EXCHANGE_reserves_status_cancel (rsh);
232 0 : }
233 :
234 :
235 : struct TALER_EXCHANGE_ReservesStatusHandle *
236 0 : TALER_EXCHANGE_reserves_status (
237 : struct TALER_EXCHANGE_Handle *exchange,
238 : const struct TALER_ReservePrivateKeyP *reserve_priv,
239 : TALER_EXCHANGE_ReservesStatusCallback cb,
240 : void *cb_cls)
241 : {
242 : struct TALER_EXCHANGE_ReservesStatusHandle *rsh;
243 : struct GNUNET_CURL_Context *ctx;
244 : CURL *eh;
245 : char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
246 : struct TALER_ReserveSignatureP reserve_sig;
247 : struct GNUNET_TIME_Timestamp ts
248 0 : = GNUNET_TIME_timestamp_get ();
249 :
250 0 : if (GNUNET_YES !=
251 0 : TEAH_handle_is_ready (exchange))
252 : {
253 0 : GNUNET_break (0);
254 0 : return NULL;
255 : }
256 0 : rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesStatusHandle);
257 0 : rsh->exchange = exchange;
258 0 : rsh->cb = cb;
259 0 : rsh->cb_cls = cb_cls;
260 0 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
261 : &rsh->reserve_pub.eddsa_pub);
262 : {
263 : char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
264 : char *end;
265 :
266 0 : end = GNUNET_STRINGS_data_to_string (
267 0 : &rsh->reserve_pub,
268 : sizeof (rsh->reserve_pub),
269 : pub_str,
270 : sizeof (pub_str));
271 0 : *end = '\0';
272 0 : GNUNET_snprintf (arg_str,
273 : sizeof (arg_str),
274 : "/reserves/%s/status",
275 : pub_str);
276 : }
277 0 : rsh->url = TEAH_path_to_url (exchange,
278 : arg_str);
279 0 : if (NULL == rsh->url)
280 : {
281 0 : GNUNET_free (rsh);
282 0 : return NULL;
283 : }
284 0 : eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
285 0 : if (NULL == eh)
286 : {
287 0 : GNUNET_break (0);
288 0 : GNUNET_free (rsh->url);
289 0 : GNUNET_free (rsh);
290 0 : return NULL;
291 : }
292 0 : TALER_wallet_reserve_status_sign (ts,
293 : reserve_priv,
294 : &reserve_sig);
295 : {
296 0 : json_t *status_obj = GNUNET_JSON_PACK (
297 : GNUNET_JSON_pack_timestamp ("request_timestamp",
298 : ts),
299 : GNUNET_JSON_pack_data_auto ("reserve_sig",
300 : &reserve_sig));
301 :
302 0 : if (GNUNET_OK !=
303 0 : TALER_curl_easy_post (&rsh->post_ctx,
304 : eh,
305 : status_obj))
306 : {
307 0 : GNUNET_break (0);
308 0 : curl_easy_cleanup (eh);
309 0 : json_decref (status_obj);
310 0 : GNUNET_free (rsh->url);
311 0 : GNUNET_free (rsh);
312 0 : return NULL;
313 : }
314 0 : json_decref (status_obj);
315 : }
316 0 : ctx = TEAH_handle_to_context (exchange);
317 0 : rsh->job = GNUNET_CURL_job_add2 (ctx,
318 : eh,
319 0 : rsh->post_ctx.headers,
320 : &handle_reserves_status_finished,
321 : rsh);
322 0 : return rsh;
323 : }
324 :
325 :
326 : void
327 0 : TALER_EXCHANGE_reserves_status_cancel (
328 : struct TALER_EXCHANGE_ReservesStatusHandle *rsh)
329 : {
330 0 : if (NULL != rsh->job)
331 : {
332 0 : GNUNET_CURL_job_cancel (rsh->job);
333 0 : rsh->job = NULL;
334 : }
335 0 : TALER_curl_easy_post_finished (&rsh->post_ctx);
336 0 : GNUNET_free (rsh->url);
337 0 : GNUNET_free (rsh);
338 0 : }
339 :
340 :
341 : /* end of exchange_api_reserves_status.c */
|