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