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 4 : GNUNET_JSON_spec_timestamp ("execution_time",
118 : &td->execution_time),
119 4 : GNUNET_JSON_spec_array_const ("deposits",
120 : &details_j),
121 4 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
122 : &td->exchange_sig),
123 4 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
124 : &td->exchange_pub),
125 4 : GNUNET_JSON_spec_end ()
126 : };
127 :
128 4 : if (GNUNET_OK !=
129 4 : GNUNET_JSON_parse (json,
130 : spec,
131 : NULL, NULL))
132 : {
133 0 : GNUNET_break_op (0);
134 0 : return GNUNET_SYSERR;
135 : }
136 4 : if (GNUNET_OK !=
137 4 : TALER_amount_set_zero (td->total_amount.currency,
138 : &total_expected))
139 : {
140 0 : GNUNET_break_op (0);
141 0 : return GNUNET_SYSERR;
142 : }
143 4 : if (GNUNET_OK !=
144 4 : TALER_EXCHANGE_test_signing_key (
145 4 : gth->keys,
146 4 : &td->exchange_pub))
147 : {
148 0 : GNUNET_break_op (0);
149 0 : return GNUNET_SYSERR;
150 : }
151 4 : td->details_length = json_array_size (details_j);
152 : {
153 : struct GNUNET_HashContext *hash_context;
154 : struct TALER_TrackTransferDetails *details;
155 :
156 4 : details = GNUNET_new_array (td->details_length,
157 : struct TALER_TrackTransferDetails);
158 4 : td->details = details;
159 4 : hash_context = GNUNET_CRYPTO_hash_context_start ();
160 21 : for (unsigned int i = 0; i < td->details_length; i++)
161 : {
162 17 : struct TALER_TrackTransferDetails *detail = &details[i];
163 17 : struct json_t *detail_j = json_array_get (details_j, i);
164 : struct GNUNET_JSON_Specification spec_detail[] = {
165 17 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
166 : &detail->h_contract_terms),
167 17 : GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub),
168 17 : TALER_JSON_spec_amount ("deposit_value",
169 : total_expected.currency,
170 : &detail->coin_value),
171 17 : TALER_JSON_spec_amount ("deposit_fee",
172 : total_expected.currency,
173 : &detail->coin_fee),
174 17 : GNUNET_JSON_spec_mark_optional (
175 : TALER_JSON_spec_amount ("refund_total",
176 : total_expected.currency,
177 : &detail->refund_total),
178 : NULL),
179 17 : GNUNET_JSON_spec_end ()
180 : };
181 :
182 17 : GNUNET_assert (GNUNET_OK ==
183 : TALER_amount_set_zero (td->total_amount.currency,
184 : &detail->refund_total));
185 17 : if ( (GNUNET_OK !=
186 17 : GNUNET_JSON_parse (detail_j,
187 : spec_detail,
188 17 : NULL, NULL)) ||
189 : (0 >
190 17 : TALER_amount_add (&total_expected,
191 : &total_expected,
192 34 : &detail->coin_value)) ||
193 : (0 >
194 17 : TALER_amount_subtract (&total_expected,
195 : &total_expected,
196 17 : &detail->coin_fee)) )
197 : {
198 0 : GNUNET_break_op (0);
199 0 : GNUNET_CRYPTO_hash_context_abort (hash_context);
200 0 : GNUNET_free (details);
201 0 : return GNUNET_SYSERR;
202 : }
203 : /* build up big hash for signature checking later */
204 17 : TALER_exchange_online_wire_deposit_append (
205 : hash_context,
206 17 : &detail->h_contract_terms,
207 : td->execution_time,
208 17 : &detail->coin_pub,
209 17 : &detail->coin_value,
210 17 : &detail->coin_fee);
211 : }
212 : /* Check signature */
213 4 : GNUNET_CRYPTO_hash_context_finish (hash_context,
214 : &td->h_details);
215 4 : if (GNUNET_OK !=
216 4 : TALER_exchange_online_wire_deposit_verify (
217 4 : &td->total_amount,
218 4 : &td->wire_fee,
219 4 : &td->merchant_pub,
220 4 : &td->h_payto,
221 4 : &td->h_details,
222 4 : &td->exchange_pub,
223 4 : &td->exchange_sig))
224 : {
225 0 : GNUNET_break_op (0);
226 0 : GNUNET_free (details);
227 0 : return GNUNET_SYSERR;
228 : }
229 4 : if (0 >
230 4 : TALER_amount_subtract (&total_expected,
231 : &total_expected,
232 4 : &td->wire_fee))
233 : {
234 0 : GNUNET_break_op (0);
235 0 : GNUNET_free (details);
236 0 : return GNUNET_SYSERR;
237 : }
238 4 : if (0 !=
239 4 : TALER_amount_cmp (&total_expected,
240 4 : &td->total_amount))
241 : {
242 0 : GNUNET_break_op (0);
243 0 : GNUNET_free (details);
244 0 : return GNUNET_SYSERR;
245 : }
246 4 : gth->cb (gth->cb_cls,
247 : &tgr);
248 4 : gth->cb = NULL;
249 4 : GNUNET_free (details);
250 : }
251 4 : return GNUNET_OK;
252 : }
253 :
254 :
255 : /**
256 : * Function called when we're done processing the
257 : * HTTP GET /transfers/$WTID request.
258 : *
259 : * @param cls the `struct TALER_EXCHANGE_GetTransfersHandle`
260 : * @param response_code HTTP response code, 0 on error
261 : * @param response parsed JSON result, NULL on error
262 : */
263 : static void
264 6 : handle_transfers_get_finished (void *cls,
265 : long response_code,
266 : const void *response)
267 : {
268 6 : struct TALER_EXCHANGE_GetTransfersHandle *gth = cls;
269 6 : const json_t *j = response;
270 6 : struct TALER_EXCHANGE_GetTransfersResponse tgr = {
271 : .hr.reply = j,
272 6 : .hr.http_status = (unsigned int) response_code
273 : };
274 :
275 6 : gth->job = NULL;
276 6 : switch (response_code)
277 : {
278 0 : case 0:
279 0 : tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
280 0 : break;
281 4 : case MHD_HTTP_OK:
282 4 : if (GNUNET_OK ==
283 4 : check_transfers_get_response_ok (gth,
284 : j))
285 : {
286 4 : GNUNET_assert (NULL == gth->cb);
287 4 : TALER_EXCHANGE_get_transfers_cancel (gth);
288 4 : return;
289 : }
290 0 : GNUNET_break_op (0);
291 0 : tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
292 0 : tgr.hr.http_status = 0;
293 0 : break;
294 0 : case MHD_HTTP_BAD_REQUEST:
295 : /* This should never happen, either us or the exchange is buggy
296 : (or API version conflict); just pass JSON reply to the application */
297 0 : tgr.hr.ec = TALER_JSON_get_error_code (j);
298 0 : tgr.hr.hint = TALER_JSON_get_error_hint (j);
299 0 : break;
300 0 : case MHD_HTTP_FORBIDDEN:
301 : /* Nothing really to verify, exchange says one of the signatures is
302 : invalid; as we checked them, this should never happen, we
303 : should pass the 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 2 : case MHD_HTTP_NOT_FOUND:
308 : /* Exchange does not know about transaction;
309 : we should pass the reply to the application */
310 2 : tgr.hr.ec = TALER_JSON_get_error_code (j);
311 2 : tgr.hr.hint = TALER_JSON_get_error_hint (j);
312 2 : break;
313 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
314 : /* Server had an internal issue; we should retry, but this API
315 : leaves this to the application */
316 0 : tgr.hr.ec = TALER_JSON_get_error_code (j);
317 0 : tgr.hr.hint = TALER_JSON_get_error_hint (j);
318 0 : break;
319 0 : default:
320 : /* unexpected response code */
321 0 : GNUNET_break_op (0);
322 0 : tgr.hr.ec = TALER_JSON_get_error_code (j);
323 0 : tgr.hr.hint = TALER_JSON_get_error_hint (j);
324 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
325 : "Unexpected response code %u/%d for transfers get\n",
326 : (unsigned int) response_code,
327 : (int) tgr.hr.ec);
328 0 : break;
329 : }
330 2 : if (NULL != gth->cb)
331 2 : gth->cb (gth->cb_cls,
332 : &tgr);
333 2 : TALER_EXCHANGE_get_transfers_cancel (gth);
334 : }
335 :
336 :
337 : struct TALER_EXCHANGE_GetTransfersHandle *
338 6 : TALER_EXCHANGE_get_transfers_create (
339 : struct GNUNET_CURL_Context *ctx,
340 : const char *url,
341 : struct TALER_EXCHANGE_Keys *keys,
342 : const struct TALER_WireTransferIdentifierRawP *wtid)
343 : {
344 : struct TALER_EXCHANGE_GetTransfersHandle *gth;
345 :
346 6 : gth = GNUNET_new (struct TALER_EXCHANGE_GetTransfersHandle);
347 6 : gth->ctx = ctx;
348 6 : gth->base_url = GNUNET_strdup (url);
349 6 : gth->keys = TALER_EXCHANGE_keys_incref (keys);
350 6 : gth->wtid = *wtid;
351 6 : return gth;
352 : }
353 :
354 :
355 : enum TALER_ErrorCode
356 6 : TALER_EXCHANGE_get_transfers_start (
357 : struct TALER_EXCHANGE_GetTransfersHandle *gth,
358 : TALER_EXCHANGE_GetTransfersCallback cb,
359 : TALER_EXCHANGE_GET_TRANSFERS_RESULT_CLOSURE *cb_cls)
360 : {
361 : char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32];
362 : CURL *eh;
363 :
364 6 : if (NULL != gth->job)
365 : {
366 0 : GNUNET_break (0);
367 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
368 : }
369 6 : gth->cb = cb;
370 6 : gth->cb_cls = cb_cls;
371 : {
372 : char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2];
373 : char *end;
374 :
375 6 : end = GNUNET_STRINGS_data_to_string (>h->wtid,
376 : sizeof (gth->wtid),
377 : wtid_str,
378 : sizeof (wtid_str));
379 6 : *end = '\0';
380 6 : GNUNET_snprintf (arg_str,
381 : sizeof (arg_str),
382 : "transfers/%s",
383 : wtid_str);
384 : }
385 6 : gth->url = TALER_url_join (gth->base_url,
386 : arg_str,
387 : NULL);
388 6 : if (NULL == gth->url)
389 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
390 6 : eh = TALER_EXCHANGE_curl_easy_get_ (gth->url);
391 6 : if (NULL == eh)
392 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
393 6 : gth->job = GNUNET_CURL_job_add_with_ct_json (gth->ctx,
394 : eh,
395 : &handle_transfers_get_finished,
396 : gth);
397 6 : if (NULL == gth->job)
398 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
399 6 : return TALER_EC_NONE;
400 : }
401 :
402 :
403 : void
404 6 : TALER_EXCHANGE_get_transfers_cancel (
405 : struct TALER_EXCHANGE_GetTransfersHandle *gth)
406 : {
407 6 : if (NULL != gth->job)
408 : {
409 0 : GNUNET_CURL_job_cancel (gth->job);
410 0 : gth->job = NULL;
411 : }
412 6 : GNUNET_free (gth->url);
413 6 : GNUNET_free (gth->base_url);
414 6 : TALER_EXCHANGE_keys_decref (gth->keys);
415 6 : GNUNET_free (gth);
416 6 : }
417 :
418 :
419 : /* end of exchange_api_get-transfers-WTID.c */
|