Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2026 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_get-reserves-RESERVE_PUB.c
19 : * @brief Implementation of the GET /reserves/$RESERVE_PUB requests
20 : * @author Christian Grothoff
21 : */
22 : #include <jansson.h>
23 : #include <microhttpd.h> /* just for HTTP status codes */
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_json_lib.h>
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler/taler_json_lib.h"
28 : #include "exchange_api_handle.h"
29 : #include "taler/taler_signatures.h"
30 : #include "exchange_api_curl_defaults.h"
31 :
32 :
33 : /**
34 : * @brief A GET /reserves/$RESERVE_PUB Handle
35 : */
36 : struct TALER_EXCHANGE_GetReservesHandle
37 : {
38 :
39 : /**
40 : * Base URL of the exchange.
41 : */
42 : char *base_url;
43 :
44 : /**
45 : * The url for this request.
46 : */
47 : char *url;
48 :
49 : /**
50 : * CURL context to use.
51 : */
52 : struct GNUNET_CURL_Context *ctx;
53 :
54 : /**
55 : * Handle for the request.
56 : */
57 : struct GNUNET_CURL_Job *job;
58 :
59 : /**
60 : * Function to call with the result.
61 : */
62 : TALER_EXCHANGE_GetReservesCallback cb;
63 :
64 : /**
65 : * Closure for @e cb.
66 : */
67 : TALER_EXCHANGE_GET_RESERVES_RESULT_CLOSURE *cb_cls;
68 :
69 : /**
70 : * Public key of the reserve we are querying.
71 : */
72 : struct TALER_ReservePublicKeyP reserve_pub;
73 :
74 : /**
75 : * Options for the request.
76 : */
77 : struct
78 : {
79 : /**
80 : * How long to wait for an answer (enables long polling).
81 : */
82 : struct GNUNET_TIME_Relative timeout;
83 : } options;
84 :
85 : };
86 :
87 :
88 : /**
89 : * We received an #MHD_HTTP_OK status code. Handle the JSON response.
90 : *
91 : * @param grh handle of the request
92 : * @param j JSON response
93 : * @return #GNUNET_OK on success
94 : */
95 : static enum GNUNET_GenericReturnValue
96 30 : handle_reserves_get_ok (struct TALER_EXCHANGE_GetReservesHandle *grh,
97 : const json_t *j)
98 : {
99 30 : struct TALER_EXCHANGE_GetReservesResponse rs = {
100 : .hr.reply = j,
101 : .hr.http_status = MHD_HTTP_OK
102 : };
103 : struct GNUNET_JSON_Specification spec[] = {
104 30 : TALER_JSON_spec_amount_any ("balance",
105 : &rs.details.ok.balance),
106 30 : GNUNET_JSON_spec_mark_optional (
107 : GNUNET_JSON_spec_string (
108 : "last_origin",
109 : (const char **) &rs.details.ok.last_origin.full_payto),
110 : NULL),
111 30 : GNUNET_JSON_spec_end ()
112 : };
113 :
114 30 : if (GNUNET_OK !=
115 30 : GNUNET_JSON_parse (j,
116 : spec,
117 : NULL,
118 : NULL))
119 : {
120 0 : GNUNET_break_op (0);
121 0 : return GNUNET_SYSERR;
122 : }
123 30 : grh->cb (grh->cb_cls,
124 : &rs);
125 30 : grh->cb = NULL;
126 30 : return GNUNET_OK;
127 : }
128 :
129 :
130 : /**
131 : * Function called when we're done processing the
132 : * HTTP GET /reserves/$RESERVE_PUB request.
133 : *
134 : * @param cls the `struct TALER_EXCHANGE_GetReservesHandle`
135 : * @param response_code HTTP response code, 0 on error
136 : * @param response parsed JSON result, NULL on error
137 : */
138 : static void
139 30 : handle_reserves_get_finished (void *cls,
140 : long response_code,
141 : const void *response)
142 : {
143 30 : struct TALER_EXCHANGE_GetReservesHandle *grh = cls;
144 30 : const json_t *j = response;
145 30 : struct TALER_EXCHANGE_GetReservesResponse rs = {
146 : .hr.reply = j,
147 30 : .hr.http_status = (unsigned int) response_code
148 : };
149 :
150 30 : grh->job = NULL;
151 30 : switch (response_code)
152 : {
153 0 : case 0:
154 0 : rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
155 0 : break;
156 30 : case MHD_HTTP_OK:
157 30 : if (GNUNET_OK !=
158 30 : handle_reserves_get_ok (grh,
159 : j))
160 : {
161 0 : rs.hr.http_status = 0;
162 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
163 : }
164 30 : break;
165 0 : case MHD_HTTP_BAD_REQUEST:
166 : /* This should never happen, either us or the exchange is buggy
167 : (or API version conflict); just pass JSON reply to the application */
168 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
169 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
170 0 : break;
171 0 : case MHD_HTTP_NOT_FOUND:
172 : /* Nothing really to verify, this should never
173 : happen, we should pass the JSON reply to the application */
174 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
175 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
176 0 : break;
177 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
178 : /* Server had an internal issue; we should retry, but this API
179 : leaves this to the application */
180 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
181 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
182 0 : break;
183 0 : default:
184 : /* unexpected response code */
185 0 : GNUNET_break_op (0);
186 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
187 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
188 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
189 : "Unexpected response code %u/%d for GET %s\n",
190 : (unsigned int) response_code,
191 : (int) rs.hr.ec,
192 : grh->url);
193 0 : break;
194 : }
195 30 : if (NULL != grh->cb)
196 : {
197 0 : grh->cb (grh->cb_cls,
198 : &rs);
199 0 : grh->cb = NULL;
200 : }
201 30 : TALER_EXCHANGE_get_reserves_cancel (grh);
202 30 : }
203 :
204 :
205 : struct TALER_EXCHANGE_GetReservesHandle *
206 30 : TALER_EXCHANGE_get_reserves_create (
207 : struct GNUNET_CURL_Context *ctx,
208 : const char *url,
209 : const struct TALER_ReservePublicKeyP *reserve_pub)
210 : {
211 : struct TALER_EXCHANGE_GetReservesHandle *grh;
212 :
213 30 : grh = GNUNET_new (struct TALER_EXCHANGE_GetReservesHandle);
214 30 : grh->ctx = ctx;
215 30 : grh->base_url = GNUNET_strdup (url);
216 30 : grh->reserve_pub = *reserve_pub;
217 30 : return grh;
218 : }
219 :
220 :
221 : enum GNUNET_GenericReturnValue
222 7 : TALER_EXCHANGE_get_reserves_set_options_ (
223 : struct TALER_EXCHANGE_GetReservesHandle *grh,
224 : unsigned int num_options,
225 : const struct TALER_EXCHANGE_GetReservesOptionValue *options)
226 : {
227 14 : for (unsigned int i = 0; i < num_options; i++)
228 : {
229 14 : switch (options[i].option)
230 : {
231 7 : case TALER_EXCHANGE_GET_RESERVES_OPTION_END:
232 7 : return GNUNET_OK;
233 7 : case TALER_EXCHANGE_GET_RESERVES_OPTION_TIMEOUT:
234 7 : grh->options.timeout = options[i].details.timeout;
235 7 : break;
236 0 : default:
237 0 : GNUNET_break (0);
238 0 : return GNUNET_SYSERR;
239 : }
240 : }
241 0 : return GNUNET_OK;
242 : }
243 :
244 :
245 : enum TALER_ErrorCode
246 30 : TALER_EXCHANGE_get_reserves_start (
247 : struct TALER_EXCHANGE_GetReservesHandle *grh,
248 : TALER_EXCHANGE_GetReservesCallback cb,
249 : TALER_EXCHANGE_GET_RESERVES_RESULT_CLOSURE *cb_cls)
250 : {
251 : char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16];
252 : CURL *eh;
253 30 : unsigned int tms
254 30 : = (unsigned int) grh->options.timeout.rel_value_us
255 30 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
256 :
257 30 : if (NULL != grh->job)
258 : {
259 0 : GNUNET_break (0);
260 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
261 : }
262 30 : grh->cb = cb;
263 30 : grh->cb_cls = cb_cls;
264 : {
265 : char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
266 : char *end;
267 : char timeout_str[32];
268 :
269 30 : end = GNUNET_STRINGS_data_to_string (
270 30 : &grh->reserve_pub,
271 : sizeof (grh->reserve_pub),
272 : pub_str,
273 : sizeof (pub_str));
274 30 : *end = '\0';
275 30 : GNUNET_snprintf (arg_str,
276 : sizeof (arg_str),
277 : "reserves/%s",
278 : pub_str);
279 30 : GNUNET_snprintf (timeout_str,
280 : sizeof (timeout_str),
281 : "%u",
282 : tms);
283 30 : grh->url = TALER_url_join (grh->base_url,
284 : arg_str,
285 : "timeout_ms",
286 : (0 == tms)
287 : ? NULL
288 : : timeout_str,
289 : NULL);
290 : }
291 30 : if (NULL == grh->url)
292 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
293 30 : eh = TALER_EXCHANGE_curl_easy_get_ (grh->url);
294 30 : if (NULL == eh)
295 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
296 30 : if (0 != tms)
297 : {
298 7 : GNUNET_break (CURLE_OK ==
299 : curl_easy_setopt (eh,
300 : CURLOPT_TIMEOUT_MS,
301 : (long) (tms + 100L)));
302 : }
303 30 : grh->job = GNUNET_CURL_job_add (grh->ctx,
304 : eh,
305 : &handle_reserves_get_finished,
306 : grh);
307 30 : if (NULL == grh->job)
308 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
309 30 : return TALER_EC_NONE;
310 : }
311 :
312 :
313 : void
314 30 : TALER_EXCHANGE_get_reserves_cancel (
315 : struct TALER_EXCHANGE_GetReservesHandle *grh)
316 : {
317 30 : if (NULL != grh->job)
318 : {
319 0 : GNUNET_CURL_job_cancel (grh->job);
320 0 : grh->job = NULL;
321 : }
322 30 : GNUNET_free (grh->url);
323 30 : GNUNET_free (grh->base_url);
324 30 : GNUNET_free (grh);
325 30 : }
326 :
327 :
328 : /* end of exchange_api_get-reserves-RESERVE_PUB.c */
|