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