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-transfers-WTID.c
19 : * @brief Implementation of the GET /transfers/$WTID 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_curl_lib.h>
26 : #include "taler/taler_json_lib.h"
27 : #include "exchange_api_handle.h"
28 : #include "taler/taler_signatures.h"
29 : #include "exchange_api_curl_defaults.h"
30 :
31 :
32 : /**
33 : * @brief A GET /transfers/$WTID Handle
34 : */
35 : struct TALER_EXCHANGE_GetTransfersHandle
36 : {
37 :
38 : /**
39 : * Base URL of the exchange.
40 : */
41 : char *base_url;
42 :
43 : /**
44 : * The url for this request.
45 : */
46 : char *url;
47 :
48 : /**
49 : * Handle for the request.
50 : */
51 : struct GNUNET_CURL_Job *job;
52 :
53 : /**
54 : * Function to call with the result.
55 : */
56 : TALER_EXCHANGE_GetTransfersCallback cb;
57 :
58 : /**
59 : * Closure for @e cb.
60 : */
61 : TALER_EXCHANGE_GET_TRANSFERS_RESULT_CLOSURE *cb_cls;
62 :
63 : /**
64 : * CURL context to use.
65 : */
66 : struct GNUNET_CURL_Context *ctx;
67 :
68 : /**
69 : * The keys of the exchange this request handle will use.
70 : */
71 : struct TALER_EXCHANGE_Keys *keys;
72 :
73 : /**
74 : * Wire transfer identifier to look up.
75 : */
76 : struct TALER_WireTransferIdentifierRawP wtid;
77 :
78 : };
79 :
80 :
81 : /**
82 : * We got a #MHD_HTTP_OK response for the /transfers/$WTID request.
83 : * Check that the response is well-formed and if it is, call the
84 : * callback. If not, return an error code.
85 : *
86 : * This code is very similar to
87 : * merchant_api_track_transfer.c::check_transfers_get_response_ok.
88 : * Any changes should likely be reflected there as well.
89 : *
90 : * @param gth handle to the operation
91 : * @param json response we got
92 : * @return #GNUNET_OK if we are done and all is well,
93 : * #GNUNET_SYSERR if the response was bogus
94 : */
95 : static enum GNUNET_GenericReturnValue
96 4 : check_transfers_get_response_ok (
97 : struct TALER_EXCHANGE_GetTransfersHandle *gth,
98 : const json_t *json)
99 : {
100 : const json_t *details_j;
101 : struct TALER_Amount total_expected;
102 4 : struct TALER_EXCHANGE_GetTransfersResponse tgr = {
103 : .hr.reply = json,
104 : .hr.http_status = MHD_HTTP_OK
105 : };
106 4 : struct TALER_EXCHANGE_TransferData *td
107 : = &tgr.details.ok.td;
108 : struct GNUNET_JSON_Specification spec[] = {
109 4 : TALER_JSON_spec_amount_any ("total",
110 : &td->total_amount),
111 4 : TALER_JSON_spec_amount_any ("wire_fee",
112 : &td->wire_fee),
113 4 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
114 : &td->merchant_pub),
115 4 : GNUNET_JSON_spec_fixed_auto ("h_payto",
116 : &td->h_payto),
117 : /* Optional: only exchanges running protocol v39 or later return
118 : this, and even those only for transfers they recorded it for.
119 : Aliases into @a json, so it needs no cleanup. */
120 4 : GNUNET_JSON_spec_mark_optional (
121 : TALER_JSON_spec_full_payto_uri ("exchange_payto_uri",
122 : &td->exchange_payto_uri),
123 : NULL),
124 4 : GNUNET_JSON_spec_timestamp ("execution_time",
125 : &td->execution_time),
126 4 : GNUNET_JSON_spec_array_const ("deposits",
127 : &details_j),
128 4 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
129 : &td->exchange_sig),
130 4 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
131 : &td->exchange_pub),
132 4 : GNUNET_JSON_spec_end ()
133 : };
134 :
135 4 : if (GNUNET_OK !=
136 4 : GNUNET_JSON_parse (json,
137 : spec,
138 : NULL, NULL))
139 : {
140 0 : GNUNET_break_op (0);
141 0 : return GNUNET_SYSERR;
142 : }
143 4 : if (GNUNET_OK !=
144 4 : TALER_amount_set_zero (td->total_amount.currency,
145 : &total_expected))
146 : {
147 0 : GNUNET_break_op (0);
148 0 : return GNUNET_SYSERR;
149 : }
150 4 : if (GNUNET_OK !=
151 4 : TALER_EXCHANGE_test_signing_key (
152 4 : gth->keys,
153 4 : &td->exchange_pub))
154 : {
155 0 : GNUNET_break_op (0);
156 0 : return GNUNET_SYSERR;
157 : }
158 4 : td->details_length = json_array_size (details_j);
159 : {
160 : struct GNUNET_HashContext *hash_context;
161 : struct TALER_TrackTransferDetails *details;
162 :
163 4 : details = GNUNET_new_array (td->details_length,
164 : struct TALER_TrackTransferDetails);
165 4 : td->details = details;
166 4 : hash_context = GNUNET_CRYPTO_hash_context_start ();
167 21 : for (unsigned int i = 0; i < td->details_length; i++)
168 : {
169 17 : struct TALER_TrackTransferDetails *detail = &details[i];
170 17 : struct json_t *detail_j = json_array_get (details_j, i);
171 : struct GNUNET_JSON_Specification spec_detail[] = {
172 17 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
173 : &detail->h_contract_terms),
174 17 : GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub),
175 17 : TALER_JSON_spec_amount ("deposit_value",
176 : total_expected.currency,
177 : &detail->coin_value),
178 17 : TALER_JSON_spec_amount ("deposit_fee",
179 : total_expected.currency,
180 : &detail->coin_fee),
181 17 : GNUNET_JSON_spec_mark_optional (
182 : TALER_JSON_spec_amount ("refund_total",
183 : total_expected.currency,
184 : &detail->refund_total),
185 : NULL),
186 17 : GNUNET_JSON_spec_end ()
187 : };
188 :
189 17 : GNUNET_assert (GNUNET_OK ==
190 : TALER_amount_set_zero (td->total_amount.currency,
191 : &detail->refund_total));
192 17 : if ( (GNUNET_OK !=
193 17 : GNUNET_JSON_parse (detail_j,
194 : spec_detail,
195 17 : NULL, NULL)) ||
196 : (0 >
197 17 : TALER_amount_add (&total_expected,
198 : &total_expected,
199 34 : &detail->coin_value)) ||
200 : (0 >
201 17 : TALER_amount_subtract (&total_expected,
202 : &total_expected,
203 17 : &detail->coin_fee)) )
204 : {
205 0 : GNUNET_break_op (0);
206 0 : GNUNET_CRYPTO_hash_context_abort (hash_context);
207 0 : GNUNET_free (details);
208 0 : return GNUNET_SYSERR;
209 : }
210 : /* build up big hash for signature checking later */
211 17 : TALER_exchange_online_wire_deposit_append (
212 : hash_context,
213 17 : &detail->h_contract_terms,
214 : td->execution_time,
215 17 : &detail->coin_pub,
216 17 : &detail->coin_value,
217 17 : &detail->coin_fee);
218 : }
219 : /* Check signature */
220 4 : GNUNET_CRYPTO_hash_context_finish (hash_context,
221 : &td->h_details);
222 4 : if (GNUNET_OK !=
223 4 : TALER_exchange_online_wire_deposit_verify (
224 4 : &td->total_amount,
225 4 : &td->wire_fee,
226 4 : &td->merchant_pub,
227 4 : &td->h_payto,
228 4 : &td->h_details,
229 4 : &td->exchange_pub,
230 4 : &td->exchange_sig))
231 : {
232 0 : GNUNET_break_op (0);
233 0 : GNUNET_free (details);
234 0 : return GNUNET_SYSERR;
235 : }
236 4 : if (0 >
237 4 : TALER_amount_subtract (&total_expected,
238 : &total_expected,
239 4 : &td->wire_fee))
240 : {
241 0 : GNUNET_break_op (0);
242 0 : GNUNET_free (details);
243 0 : return GNUNET_SYSERR;
244 : }
245 4 : if (0 !=
246 4 : TALER_amount_cmp (&total_expected,
247 4 : &td->total_amount))
248 : {
249 0 : GNUNET_break_op (0);
250 0 : GNUNET_free (details);
251 0 : return GNUNET_SYSERR;
252 : }
253 4 : gth->cb (gth->cb_cls,
254 : &tgr);
255 4 : gth->cb = NULL;
256 4 : GNUNET_free (details);
257 : }
258 4 : return GNUNET_OK;
259 : }
260 :
261 :
262 : /**
263 : * Function called when we're done processing the
264 : * HTTP GET /transfers/$WTID request.
265 : *
266 : * @param cls the `struct TALER_EXCHANGE_GetTransfersHandle`
267 : * @param response_code HTTP response code, 0 on error
268 : * @param response parsed JSON result, NULL on error
269 : */
270 : static void
271 6 : handle_transfers_get_finished (void *cls,
272 : long response_code,
273 : const void *response)
274 : {
275 6 : struct TALER_EXCHANGE_GetTransfersHandle *gth = cls;
276 6 : const json_t *j = response;
277 6 : struct TALER_EXCHANGE_GetTransfersResponse tgr = {
278 : .hr.reply = j,
279 6 : .hr.http_status = (unsigned int) response_code
280 : };
281 :
282 6 : gth->job = NULL;
283 6 : switch (response_code)
284 : {
285 0 : case 0:
286 0 : tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
287 0 : break;
288 4 : case MHD_HTTP_OK:
289 4 : if (GNUNET_OK ==
290 4 : check_transfers_get_response_ok (gth,
291 : j))
292 : {
293 4 : GNUNET_assert (NULL == gth->cb);
294 4 : TALER_EXCHANGE_get_transfers_cancel (gth);
295 4 : return;
296 : }
297 0 : GNUNET_break_op (0);
298 0 : tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
299 0 : tgr.hr.http_status = 0;
300 0 : break;
301 0 : case MHD_HTTP_BAD_REQUEST:
302 : /* This should never happen, either us or the exchange is buggy
303 : (or API version conflict); just pass JSON reply to the application */
304 0 : tgr.hr.ec = TALER_JSON_get_error_code (j);
305 0 : tgr.hr.hint = TALER_JSON_get_error_hint (j);
306 0 : break;
307 0 : case MHD_HTTP_FORBIDDEN:
308 : /* Nothing really to verify, exchange says one of the signatures is
309 : invalid; as we checked them, this should never happen, we
310 : should pass the JSON reply to the application */
311 0 : tgr.hr.ec = TALER_JSON_get_error_code (j);
312 0 : tgr.hr.hint = TALER_JSON_get_error_hint (j);
313 0 : break;
314 2 : case MHD_HTTP_NOT_FOUND:
315 : /* Exchange does not know about transaction;
316 : we should pass the reply to the application */
317 2 : tgr.hr.ec = TALER_JSON_get_error_code (j);
318 2 : tgr.hr.hint = TALER_JSON_get_error_hint (j);
319 2 : break;
320 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
321 : /* Server had an internal issue; we should retry, but this API
322 : leaves this to the application */
323 0 : tgr.hr.ec = TALER_JSON_get_error_code (j);
324 0 : tgr.hr.hint = TALER_JSON_get_error_hint (j);
325 0 : break;
326 0 : default:
327 : /* unexpected response code */
328 0 : GNUNET_break_op (0);
329 0 : tgr.hr.ec = TALER_JSON_get_error_code (j);
330 0 : tgr.hr.hint = TALER_JSON_get_error_hint (j);
331 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
332 : "Unexpected response code %u/%d for transfers get\n",
333 : (unsigned int) response_code,
334 : (int) tgr.hr.ec);
335 0 : break;
336 : }
337 2 : if (NULL != gth->cb)
338 2 : gth->cb (gth->cb_cls,
339 : &tgr);
340 2 : TALER_EXCHANGE_get_transfers_cancel (gth);
341 : }
342 :
343 :
344 : struct TALER_EXCHANGE_GetTransfersHandle *
345 6 : TALER_EXCHANGE_get_transfers_create (
346 : struct GNUNET_CURL_Context *ctx,
347 : const char *url,
348 : struct TALER_EXCHANGE_Keys *keys,
349 : const struct TALER_WireTransferIdentifierRawP *wtid)
350 : {
351 : struct TALER_EXCHANGE_GetTransfersHandle *gth;
352 :
353 6 : gth = GNUNET_new (struct TALER_EXCHANGE_GetTransfersHandle);
354 6 : gth->ctx = ctx;
355 6 : gth->base_url = GNUNET_strdup (url);
356 6 : gth->keys = TALER_EXCHANGE_keys_incref (keys);
357 6 : gth->wtid = *wtid;
358 6 : return gth;
359 : }
360 :
361 :
362 : enum TALER_ErrorCode
363 6 : TALER_EXCHANGE_get_transfers_start (
364 : struct TALER_EXCHANGE_GetTransfersHandle *gth,
365 : TALER_EXCHANGE_GetTransfersCallback cb,
366 : TALER_EXCHANGE_GET_TRANSFERS_RESULT_CLOSURE *cb_cls)
367 : {
368 : char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32];
369 : CURL *eh;
370 :
371 6 : if (NULL != gth->job)
372 : {
373 0 : GNUNET_break (0);
374 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
375 : }
376 6 : gth->cb = cb;
377 6 : gth->cb_cls = cb_cls;
378 : {
379 : char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2];
380 : char *end;
381 :
382 6 : end = GNUNET_STRINGS_data_to_string (>h->wtid,
383 : sizeof (gth->wtid),
384 : wtid_str,
385 : sizeof (wtid_str));
386 6 : *end = '\0';
387 6 : GNUNET_snprintf (arg_str,
388 : sizeof (arg_str),
389 : "transfers/%s",
390 : wtid_str);
391 : }
392 6 : gth->url = TALER_url_join (gth->base_url,
393 : arg_str,
394 : NULL);
395 6 : if (NULL == gth->url)
396 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
397 6 : eh = TALER_EXCHANGE_curl_easy_get_ (gth->url);
398 6 : if (NULL == eh)
399 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
400 6 : gth->job = GNUNET_CURL_job_add_with_ct_json (gth->ctx,
401 : eh,
402 : &handle_transfers_get_finished,
403 : gth);
404 6 : if (NULL == gth->job)
405 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
406 6 : return TALER_EC_NONE;
407 : }
408 :
409 :
410 : void
411 6 : TALER_EXCHANGE_get_transfers_cancel (
412 : struct TALER_EXCHANGE_GetTransfersHandle *gth)
413 : {
414 6 : if (NULL != gth->job)
415 : {
416 0 : GNUNET_CURL_job_cancel (gth->job);
417 0 : gth->job = NULL;
418 : }
419 6 : GNUNET_free (gth->url);
420 6 : GNUNET_free (gth->base_url);
421 6 : TALER_EXCHANGE_keys_decref (gth->keys);
422 6 : GNUNET_free (gth);
423 6 : }
424 :
425 :
426 : /* end of exchange_api_get-transfers-WTID.c */
|