Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022-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-purses-PURSE_PUB-merge.c
19 : * @brief Implementation of the /purses/ GET request
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 /purses/$PURSE_PUB/merge Handle
35 : */
36 : struct TALER_EXCHANGE_GetPursesHandle
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 : * The keys of the exchange this request handle will use.
51 : */
52 : struct TALER_EXCHANGE_Keys *keys;
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_GetPursesCallback cb;
63 :
64 : /**
65 : * Closure for @e cb.
66 : */
67 : TALER_EXCHANGE_GET_PURSES_RESULT_CLOSURE *cb_cls;
68 :
69 : /**
70 : * CURL context to use.
71 : */
72 : struct GNUNET_CURL_Context *ctx;
73 :
74 : /**
75 : * Public key of the purse being queried.
76 : */
77 : struct TALER_PurseContractPublicKeyP purse_pub;
78 :
79 : /**
80 : * Options for the request.
81 : */
82 : struct
83 : {
84 : /**
85 : * How long to wait for a change to happen.
86 : */
87 : struct GNUNET_TIME_Relative timeout;
88 :
89 : /**
90 : * True to wait for a merge event, false to wait for a deposit event.
91 : */
92 : bool wait_for_merge;
93 : } options;
94 :
95 : };
96 :
97 :
98 : /**
99 : * Function called when we're done processing the
100 : * HTTP /purses/$PID GET request.
101 : *
102 : * @param cls the `struct TALER_EXCHANGE_GetPursesHandle`
103 : * @param response_code HTTP response code, 0 on error
104 : * @param response parsed JSON result, NULL on error
105 : */
106 : static void
107 10 : handle_purse_get_finished (void *cls,
108 : long response_code,
109 : const void *response)
110 : {
111 10 : struct TALER_EXCHANGE_GetPursesHandle *gph = cls;
112 10 : const json_t *j = response;
113 10 : struct TALER_EXCHANGE_GetPursesResponse dr = {
114 : .hr.reply = j,
115 10 : .hr.http_status = (unsigned int) response_code
116 : };
117 :
118 10 : gph->job = NULL;
119 10 : switch (response_code)
120 : {
121 0 : case 0:
122 0 : dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
123 0 : break;
124 6 : case MHD_HTTP_OK:
125 : {
126 6 : bool no_merge = false;
127 6 : bool no_deposit = false;
128 : struct TALER_ExchangePublicKeyP exchange_pub;
129 : struct TALER_ExchangeSignatureP exchange_sig;
130 : struct GNUNET_JSON_Specification spec[] = {
131 6 : GNUNET_JSON_spec_mark_optional (
132 : GNUNET_JSON_spec_timestamp ("merge_timestamp",
133 : &dr.details.ok.merge_timestamp),
134 : &no_merge),
135 6 : GNUNET_JSON_spec_mark_optional (
136 : GNUNET_JSON_spec_timestamp ("deposit_timestamp",
137 : &dr.details.ok.deposit_timestamp),
138 : &no_deposit),
139 6 : TALER_JSON_spec_amount_any ("balance",
140 : &dr.details.ok.balance),
141 6 : GNUNET_JSON_spec_timestamp ("purse_expiration",
142 : &dr.details.ok.purse_expiration),
143 6 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
144 : &exchange_pub),
145 6 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
146 : &exchange_sig),
147 6 : GNUNET_JSON_spec_end ()
148 : };
149 :
150 6 : if (GNUNET_OK !=
151 6 : GNUNET_JSON_parse (j,
152 : spec,
153 : NULL, NULL))
154 : {
155 0 : GNUNET_break_op (0);
156 0 : dr.hr.http_status = 0;
157 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
158 0 : break;
159 : }
160 6 : if (GNUNET_OK !=
161 6 : TALER_EXCHANGE_test_signing_key (gph->keys,
162 : &exchange_pub))
163 : {
164 0 : GNUNET_break_op (0);
165 0 : dr.hr.http_status = 0;
166 0 : dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE;
167 0 : break;
168 : }
169 6 : if (GNUNET_OK !=
170 6 : TALER_exchange_online_purse_status_verify (
171 : dr.details.ok.merge_timestamp,
172 : dr.details.ok.deposit_timestamp,
173 : &dr.details.ok.balance,
174 : &exchange_pub,
175 : &exchange_sig))
176 : {
177 0 : GNUNET_break_op (0);
178 0 : dr.hr.http_status = 0;
179 0 : dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE;
180 0 : break;
181 : }
182 6 : gph->cb (gph->cb_cls,
183 : &dr);
184 6 : TALER_EXCHANGE_get_purses_cancel (gph);
185 6 : return;
186 : }
187 0 : case MHD_HTTP_BAD_REQUEST:
188 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
189 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
190 : /* This should never happen, either us or the exchange is buggy
191 : (or API version conflict); just pass JSON reply to the application */
192 0 : break;
193 0 : case MHD_HTTP_FORBIDDEN:
194 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
195 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
196 : /* Nothing really to verify, exchange says one of the signatures is
197 : invalid; as we checked them, this should never happen, we
198 : should pass the JSON reply to the application */
199 0 : break;
200 0 : case MHD_HTTP_NOT_FOUND:
201 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
202 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
203 : /* Exchange does not know about transaction;
204 : we should pass the reply to the application */
205 0 : break;
206 4 : case MHD_HTTP_GONE:
207 : /* purse expired */
208 4 : dr.hr.ec = TALER_JSON_get_error_code (j);
209 4 : dr.hr.hint = TALER_JSON_get_error_hint (j);
210 4 : break;
211 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
212 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
213 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
214 : /* Server had an internal issue; we should retry, but this API
215 : leaves this to the application */
216 0 : break;
217 0 : default:
218 : /* unexpected response code */
219 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
220 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
221 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
222 : "Unexpected response code %u/%d for exchange GET purses\n",
223 : (unsigned int) response_code,
224 : (int) dr.hr.ec);
225 0 : GNUNET_break_op (0);
226 0 : break;
227 : }
228 4 : gph->cb (gph->cb_cls,
229 : &dr);
230 4 : TALER_EXCHANGE_get_purses_cancel (gph);
231 : }
232 :
233 :
234 : struct TALER_EXCHANGE_GetPursesHandle *
235 10 : TALER_EXCHANGE_get_purses_create (
236 : struct GNUNET_CURL_Context *ctx,
237 : const char *url,
238 : struct TALER_EXCHANGE_Keys *keys,
239 : const struct TALER_PurseContractPublicKeyP *purse_pub)
240 : {
241 : struct TALER_EXCHANGE_GetPursesHandle *gph;
242 :
243 10 : gph = GNUNET_new (struct TALER_EXCHANGE_GetPursesHandle);
244 10 : gph->ctx = ctx;
245 10 : gph->base_url = GNUNET_strdup (url);
246 10 : gph->keys = TALER_EXCHANGE_keys_incref (keys);
247 10 : gph->purse_pub = *purse_pub;
248 10 : return gph;
249 : }
250 :
251 :
252 : enum GNUNET_GenericReturnValue
253 10 : TALER_EXCHANGE_get_purses_set_options_ (
254 : struct TALER_EXCHANGE_GetPursesHandle *gph,
255 : unsigned int num_options,
256 : const struct TALER_EXCHANGE_GetPursesOptionValue *options)
257 : {
258 30 : for (unsigned int i = 0; i < num_options; i++)
259 : {
260 30 : switch (options[i].option)
261 : {
262 10 : case TALER_EXCHANGE_GET_PURSES_OPTION_END:
263 10 : return GNUNET_OK;
264 10 : case TALER_EXCHANGE_GET_PURSES_OPTION_TIMEOUT:
265 10 : gph->options.timeout = options[i].details.timeout;
266 10 : break;
267 10 : case TALER_EXCHANGE_GET_PURSES_OPTION_WAIT_FOR_MERGE:
268 10 : gph->options.wait_for_merge = options[i].details.wait_for_merge;
269 10 : break;
270 0 : default:
271 0 : GNUNET_break (0);
272 0 : return GNUNET_SYSERR;
273 : }
274 : }
275 0 : return GNUNET_OK;
276 : }
277 :
278 :
279 : enum TALER_ErrorCode
280 10 : TALER_EXCHANGE_get_purses_start (
281 : struct TALER_EXCHANGE_GetPursesHandle *gph,
282 : TALER_EXCHANGE_GetPursesCallback cb,
283 : TALER_EXCHANGE_GET_PURSES_RESULT_CLOSURE *cb_cls)
284 : {
285 : char arg_str[sizeof (gph->purse_pub) * 2 + 64];
286 : CURL *eh;
287 10 : unsigned int tms
288 10 : = (unsigned int) gph->options.timeout.rel_value_us
289 10 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
290 :
291 10 : if (NULL != gph->job)
292 : {
293 0 : GNUNET_break (0);
294 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
295 : }
296 10 : gph->cb = cb;
297 10 : gph->cb_cls = cb_cls;
298 : {
299 : char cpub_str[sizeof (gph->purse_pub) * 2];
300 : char *end;
301 : char timeout_str[32];
302 :
303 10 : end = GNUNET_STRINGS_data_to_string (&gph->purse_pub,
304 : sizeof (gph->purse_pub),
305 : cpub_str,
306 : sizeof (cpub_str));
307 10 : *end = '\0';
308 10 : GNUNET_snprintf (timeout_str,
309 : sizeof (timeout_str),
310 : "%u",
311 : tms);
312 10 : GNUNET_snprintf (arg_str,
313 : sizeof (arg_str),
314 : "purses/%s/%s",
315 : cpub_str,
316 10 : gph->options.wait_for_merge
317 : ? "merge"
318 : : "deposit");
319 10 : gph->url = TALER_url_join (gph->base_url,
320 : arg_str,
321 : "timeout_ms",
322 : (0 == tms)
323 : ? NULL
324 : : timeout_str,
325 : NULL);
326 : }
327 10 : if (NULL == gph->url)
328 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
329 10 : eh = TALER_EXCHANGE_curl_easy_get_ (gph->url);
330 10 : if (NULL == eh)
331 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
332 10 : if (0 != tms)
333 : {
334 10 : GNUNET_break (CURLE_OK ==
335 : curl_easy_setopt (eh,
336 : CURLOPT_TIMEOUT_MS,
337 : (long) (tms + 100L)));
338 : }
339 10 : gph->job = GNUNET_CURL_job_add (gph->ctx,
340 : eh,
341 : &handle_purse_get_finished,
342 : gph);
343 10 : if (NULL == gph->job)
344 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
345 10 : return TALER_EC_NONE;
346 : }
347 :
348 :
349 : void
350 10 : TALER_EXCHANGE_get_purses_cancel (
351 : struct TALER_EXCHANGE_GetPursesHandle *gph)
352 : {
353 10 : if (NULL != gph->job)
354 : {
355 0 : GNUNET_CURL_job_cancel (gph->job);
356 0 : gph->job = NULL;
357 : }
358 10 : GNUNET_free (gph->url);
359 10 : GNUNET_free (gph->base_url);
360 10 : TALER_EXCHANGE_keys_decref (gph->keys);
361 10 : GNUNET_free (gph);
362 10 : }
363 :
364 :
365 : /* end of exchange_api_get-purses-PURSE_PUB-merge.c */
|