Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2023 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_reserves_history.c
19 : * @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests
20 : * @author Christian Grothoff
21 : */
22 : #include "taler/platform.h"
23 : #include <jansson.h>
24 : #include <microhttpd.h> /* just for HTTP history codes */
25 : #include <gnunet/gnunet_util_lib.h>
26 : #include <gnunet/gnunet_json_lib.h>
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include "taler/taler_exchange_service.h"
29 : #include "taler/taler_json_lib.h"
30 : #include "exchange_api_handle.h"
31 : #include "taler/taler_signatures.h"
32 : #include "exchange_api_curl_defaults.h"
33 :
34 :
35 : /**
36 : * @brief A /reserves/$RID/history Handle
37 : */
38 : struct TALER_EXCHANGE_ReservesHistoryHandle
39 : {
40 :
41 : /**
42 : * The keys of the exchange this request handle will use
43 : */
44 : struct TALER_EXCHANGE_Keys *keys;
45 :
46 : /**
47 : * The url for this request.
48 : */
49 : char *url;
50 :
51 : /**
52 : * Handle for the request.
53 : */
54 : struct GNUNET_CURL_Job *job;
55 :
56 : /**
57 : * Context for #TEH_curl_easy_post(). Keeps the data that must
58 : * persist for Curl to make the upload.
59 : */
60 : struct TALER_CURL_PostContext post_ctx;
61 :
62 : /**
63 : * Function to call with the result.
64 : */
65 : TALER_EXCHANGE_ReservesHistoryCallback cb;
66 :
67 : /**
68 : * Public key of the reserve we are querying.
69 : */
70 : struct TALER_ReservePublicKeyP reserve_pub;
71 :
72 : /**
73 : * Closure for @a cb.
74 : */
75 : void *cb_cls;
76 :
77 : /**
78 : * Where to store the etag (if any).
79 : */
80 : uint64_t etag;
81 :
82 : };
83 :
84 :
85 : /**
86 : * Context for history entry helpers.
87 : */
88 : struct HistoryParseContext
89 : {
90 :
91 : /**
92 : * Keys of the exchange we use.
93 : */
94 : const struct TALER_EXCHANGE_Keys *keys;
95 :
96 : /**
97 : * Our reserve public key.
98 : */
99 : const struct TALER_ReservePublicKeyP *reserve_pub;
100 :
101 : /**
102 : * Array of UUIDs.
103 : */
104 : struct GNUNET_HashCode *uuids;
105 :
106 : /**
107 : * Where to sum up total inbound amounts.
108 : */
109 : struct TALER_Amount *total_in;
110 :
111 : /**
112 : * Where to sum up total outbound amounts.
113 : */
114 : struct TALER_Amount *total_out;
115 :
116 : /**
117 : * Number of entries already used in @e uuids.
118 : */
119 : unsigned int uuid_off;
120 : };
121 :
122 :
123 : /**
124 : * Type of a function called to parse a reserve history
125 : * entry @a rh.
126 : *
127 : * @param[in,out] rh where to write the result
128 : * @param[in,out] uc UUID context for duplicate detection
129 : * @param transaction the transaction to parse
130 : * @return #GNUNET_OK on success
131 : */
132 : typedef enum GNUNET_GenericReturnValue
133 : (*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
134 : struct HistoryParseContext *uc,
135 : const json_t *transaction);
136 :
137 :
138 : /**
139 : * Parse "credit" reserve history entry.
140 : *
141 : * @param[in,out] rh entry to parse
142 : * @param uc our context
143 : * @param transaction the transaction to parse
144 : * @return #GNUNET_OK on success
145 : */
146 : static enum GNUNET_GenericReturnValue
147 8 : parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
148 : struct HistoryParseContext *uc,
149 : const json_t *transaction)
150 : {
151 : struct TALER_FullPayto wire_uri;
152 : uint64_t wire_reference;
153 : struct GNUNET_TIME_Timestamp timestamp;
154 : struct GNUNET_JSON_Specification withdraw_spec[] = {
155 8 : GNUNET_JSON_spec_uint64 ("wire_reference",
156 : &wire_reference),
157 8 : GNUNET_JSON_spec_timestamp ("timestamp",
158 : ×tamp),
159 8 : TALER_JSON_spec_full_payto_uri ("sender_account_url",
160 : &wire_uri),
161 8 : GNUNET_JSON_spec_end ()
162 : };
163 :
164 8 : rh->type = TALER_EXCHANGE_RTT_CREDIT;
165 8 : if (0 >
166 8 : TALER_amount_add (uc->total_in,
167 8 : uc->total_in,
168 8 : &rh->amount))
169 : {
170 : /* overflow in history already!? inconceivable! Bad exchange! */
171 0 : GNUNET_break_op (0);
172 0 : return GNUNET_SYSERR;
173 : }
174 8 : if (GNUNET_OK !=
175 8 : GNUNET_JSON_parse (transaction,
176 : withdraw_spec,
177 : NULL, NULL))
178 : {
179 0 : GNUNET_break_op (0);
180 0 : return GNUNET_SYSERR;
181 : }
182 : rh->details.in_details.sender_url.full_payto
183 8 : = GNUNET_strdup (wire_uri.full_payto);
184 8 : rh->details.in_details.wire_reference = wire_reference;
185 8 : rh->details.in_details.timestamp = timestamp;
186 8 : return GNUNET_OK;
187 : }
188 :
189 :
190 : /**
191 : * Parse "withdraw" reserve history entry.
192 : *
193 : * @param[in,out] rh entry to parse
194 : * @param uc our context
195 : * @param transaction the transaction to parse
196 : * @return #GNUNET_OK on success
197 : */
198 : static enum GNUNET_GenericReturnValue
199 7 : parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
200 : struct HistoryParseContext *uc,
201 : const json_t *transaction)
202 : {
203 : uint16_t num_coins;
204 : struct TALER_Amount withdraw_fee;
205 : struct TALER_Amount withdraw_amount;
206 : struct TALER_Amount amount_without_fee;
207 7 : uint8_t max_age = 0;
208 7 : uint8_t noreveal_index = 0;
209 : struct TALER_HashBlindedPlanchetsP planchets_h;
210 : struct TALER_HashBlindedPlanchetsP selected_h;
211 : struct TALER_ReserveSignatureP reserve_sig;
212 : struct TALER_BlindingMasterSeedP blinding_seed;
213 : struct TALER_DenominationHashP *denom_pub_hashes;
214 : size_t num_denom_pub_hashes;
215 : bool no_max_age;
216 : bool no_noreveal_index;
217 : bool no_blinding_seed;
218 : bool no_selected_h;
219 : struct GNUNET_JSON_Specification withdraw_spec[] = {
220 7 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
221 : &reserve_sig),
222 7 : GNUNET_JSON_spec_uint16 ("num_coins",
223 : &num_coins),
224 7 : GNUNET_JSON_spec_fixed_auto ("planchets_h",
225 : &planchets_h),
226 7 : TALER_JSON_spec_amount_any ("amount",
227 : &withdraw_amount),
228 7 : TALER_JSON_spec_amount_any ("withdraw_fee",
229 : &withdraw_fee),
230 7 : TALER_JSON_spec_array_of_denom_pub_h ("denom_pub_hashes",
231 : &num_denom_pub_hashes,
232 : &denom_pub_hashes),
233 7 : GNUNET_JSON_spec_mark_optional (
234 : GNUNET_JSON_spec_fixed_auto ("selected_h",
235 : &selected_h),
236 : &no_selected_h),
237 7 : GNUNET_JSON_spec_mark_optional (
238 : GNUNET_JSON_spec_uint8 ("max_age",
239 : &max_age),
240 : &no_max_age),
241 7 : GNUNET_JSON_spec_mark_optional (
242 : GNUNET_JSON_spec_fixed_auto ("blinding_seed",
243 : &blinding_seed),
244 : &no_blinding_seed),
245 7 : GNUNET_JSON_spec_mark_optional (
246 : GNUNET_JSON_spec_uint8 ("noreveal_index",
247 : &noreveal_index),
248 : &no_noreveal_index),
249 7 : GNUNET_JSON_spec_end ()
250 : };
251 :
252 7 : rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL;
253 7 : if (GNUNET_OK !=
254 7 : GNUNET_JSON_parse (transaction,
255 : withdraw_spec,
256 : NULL, NULL))
257 : {
258 0 : GNUNET_break_op (0);
259 0 : return GNUNET_SYSERR;
260 : }
261 :
262 7 : if ((no_max_age != no_noreveal_index) ||
263 7 : (no_max_age != no_selected_h))
264 : {
265 0 : GNUNET_break_op (0);
266 0 : GNUNET_JSON_parse_free (withdraw_spec);
267 0 : return GNUNET_SYSERR;
268 : }
269 7 : rh->details.withdraw.age_restricted = ! no_max_age;
270 :
271 7 : if (num_coins != num_denom_pub_hashes)
272 : {
273 0 : GNUNET_break_op (0);
274 0 : GNUNET_JSON_parse_free (withdraw_spec);
275 0 : return GNUNET_SYSERR;
276 : }
277 :
278 : /* Check that the signature is a valid withdraw request */
279 7 : if (0>TALER_amount_subtract (
280 : &amount_without_fee,
281 : &withdraw_amount,
282 : &withdraw_fee))
283 : {
284 0 : GNUNET_break_op (0);
285 0 : GNUNET_JSON_parse_free (withdraw_spec);
286 0 : return GNUNET_SYSERR;
287 : }
288 :
289 7 : if (GNUNET_OK !=
290 7 : TALER_wallet_withdraw_verify (
291 : &amount_without_fee,
292 : &withdraw_fee,
293 : &planchets_h,
294 : no_blinding_seed ? NULL : &blinding_seed,
295 0 : no_max_age ? NULL : &uc->keys->age_mask,
296 : no_max_age ? 0 : max_age,
297 : uc->reserve_pub,
298 : &reserve_sig))
299 : {
300 0 : GNUNET_break_op (0);
301 0 : GNUNET_JSON_parse_free (withdraw_spec);
302 0 : return GNUNET_SYSERR;
303 : }
304 :
305 7 : rh->details.withdraw.num_coins = num_coins;
306 7 : rh->details.withdraw.age_restricted = ! no_max_age;
307 7 : rh->details.withdraw.max_age = max_age;
308 7 : rh->details.withdraw.planchets_h = planchets_h;
309 7 : rh->details.withdraw.selected_h = selected_h;
310 7 : rh->details.withdraw.noreveal_index = noreveal_index;
311 7 : rh->details.withdraw.no_blinding_seed = no_blinding_seed;
312 7 : if (! no_blinding_seed)
313 3 : rh->details.withdraw.blinding_seed = blinding_seed;
314 :
315 : /* check that withdraw fee matches expectations! */
316 : {
317 : const struct TALER_EXCHANGE_Keys *key_state;
318 : struct TALER_Amount fee_acc;
319 : struct TALER_Amount amount_acc;
320 :
321 7 : GNUNET_assert (GNUNET_OK ==
322 : TALER_amount_set_zero (withdraw_amount.currency,
323 : &fee_acc));
324 7 : GNUNET_assert (GNUNET_OK ==
325 : TALER_amount_set_zero (withdraw_amount.currency,
326 : &amount_acc));
327 :
328 7 : key_state = uc->keys;
329 :
330 : /* accumulate the withdraw fees */
331 16 : for (size_t i=0; i < num_coins; i++)
332 : {
333 : const struct TALER_EXCHANGE_DenomPublicKey *dki;
334 :
335 9 : dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
336 9 : &denom_pub_hashes[i]);
337 9 : if (NULL == dki)
338 : {
339 0 : GNUNET_break_op (0);
340 0 : GNUNET_JSON_parse_free (withdraw_spec);
341 0 : return GNUNET_SYSERR;
342 : }
343 9 : TALER_amount_add (&fee_acc,
344 : &fee_acc,
345 : &dki->fees.withdraw);
346 9 : TALER_amount_add (&amount_acc,
347 : &amount_acc,
348 : &dki->value);
349 : }
350 :
351 7 : if ( (GNUNET_YES !=
352 7 : TALER_amount_cmp_currency (&fee_acc,
353 7 : &withdraw_fee)) ||
354 : (0 !=
355 7 : TALER_amount_cmp (&amount_acc,
356 : &amount_without_fee)) )
357 : {
358 0 : GNUNET_break_op (0);
359 0 : GNUNET_JSON_parse_free (withdraw_spec);
360 0 : return GNUNET_SYSERR;
361 : }
362 7 : rh->details.withdraw.fee = withdraw_fee;
363 : }
364 :
365 : #pragma message "is out_authorization_sig still needed? Not set anywhere"
366 : rh->details.withdraw.out_authorization_sig
367 7 : = json_object_get (transaction,
368 : "signature");
369 : /* Check check that the same withdraw transaction
370 : isn't listed twice by the exchange. We use the
371 : "uuid" array to remember the hashes of all
372 : signatures, and compare the hashes to find
373 : duplicates. */
374 7 : GNUNET_CRYPTO_hash (&reserve_sig,
375 : sizeof (reserve_sig),
376 7 : &uc->uuids[uc->uuid_off]);
377 7 : for (unsigned int i = 0; i<uc->uuid_off; i++)
378 : {
379 0 : if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
380 : &uc->uuids[i]))
381 : {
382 0 : GNUNET_break_op (0);
383 0 : GNUNET_JSON_parse_free (withdraw_spec);
384 0 : return GNUNET_SYSERR;
385 : }
386 : }
387 7 : uc->uuid_off++;
388 :
389 7 : if (0 >
390 7 : TALER_amount_add (uc->total_out,
391 7 : uc->total_out,
392 7 : &rh->amount))
393 : {
394 : /* overflow in history already!? inconceivable! Bad exchange! */
395 0 : GNUNET_break_op (0);
396 0 : GNUNET_JSON_parse_free (withdraw_spec);
397 0 : return GNUNET_SYSERR;
398 : }
399 7 : GNUNET_JSON_parse_free (withdraw_spec);
400 7 : return GNUNET_OK;
401 : }
402 :
403 :
404 : /**
405 : * Parse "recoup" reserve history entry.
406 : *
407 : * @param[in,out] rh entry to parse
408 : * @param uc our context
409 : * @param transaction the transaction to parse
410 : * @return #GNUNET_OK on success
411 : */
412 : static enum GNUNET_GenericReturnValue
413 0 : parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
414 : struct HistoryParseContext *uc,
415 : const json_t *transaction)
416 : {
417 : const struct TALER_EXCHANGE_Keys *key_state;
418 : struct GNUNET_JSON_Specification recoup_spec[] = {
419 0 : GNUNET_JSON_spec_fixed_auto ("coin_pub",
420 : &rh->details.recoup_details.coin_pub),
421 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
422 : &rh->details.recoup_details.exchange_sig),
423 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
424 : &rh->details.recoup_details.exchange_pub),
425 0 : GNUNET_JSON_spec_timestamp ("timestamp",
426 : &rh->details.recoup_details.timestamp),
427 0 : GNUNET_JSON_spec_end ()
428 : };
429 :
430 0 : rh->type = TALER_EXCHANGE_RTT_RECOUP;
431 0 : if (GNUNET_OK !=
432 0 : GNUNET_JSON_parse (transaction,
433 : recoup_spec,
434 : NULL, NULL))
435 : {
436 0 : GNUNET_break_op (0);
437 0 : return GNUNET_SYSERR;
438 : }
439 0 : key_state = uc->keys;
440 0 : if (GNUNET_OK !=
441 0 : TALER_EXCHANGE_test_signing_key (key_state,
442 0 : &rh->details.
443 : recoup_details.exchange_pub))
444 : {
445 0 : GNUNET_break_op (0);
446 0 : return GNUNET_SYSERR;
447 : }
448 0 : if (GNUNET_OK !=
449 0 : TALER_exchange_online_confirm_recoup_verify (
450 : rh->details.recoup_details.timestamp,
451 0 : &rh->amount,
452 0 : &rh->details.recoup_details.coin_pub,
453 : uc->reserve_pub,
454 0 : &rh->details.recoup_details.exchange_pub,
455 0 : &rh->details.recoup_details.exchange_sig))
456 : {
457 0 : GNUNET_break_op (0);
458 0 : return GNUNET_SYSERR;
459 : }
460 0 : if (0 >
461 0 : TALER_amount_add (uc->total_in,
462 0 : uc->total_in,
463 0 : &rh->amount))
464 : {
465 : /* overflow in history already!? inconceivable! Bad exchange! */
466 0 : GNUNET_break_op (0);
467 0 : return GNUNET_SYSERR;
468 : }
469 0 : return GNUNET_OK;
470 : }
471 :
472 :
473 : /**
474 : * Parse "closing" reserve history entry.
475 : *
476 : * @param[in,out] rh entry to parse
477 : * @param uc our context
478 : * @param transaction the transaction to parse
479 : * @return #GNUNET_OK on success
480 : */
481 : static enum GNUNET_GenericReturnValue
482 0 : parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
483 : struct HistoryParseContext *uc,
484 : const json_t *transaction)
485 : {
486 : const struct TALER_EXCHANGE_Keys *key_state;
487 : struct TALER_FullPayto receiver_uri;
488 : struct GNUNET_JSON_Specification closing_spec[] = {
489 0 : TALER_JSON_spec_full_payto_uri (
490 : "receiver_account_details",
491 : &receiver_uri),
492 0 : GNUNET_JSON_spec_fixed_auto ("wtid",
493 : &rh->details.close_details.wtid),
494 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
495 : &rh->details.close_details.exchange_sig),
496 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
497 : &rh->details.close_details.exchange_pub),
498 0 : TALER_JSON_spec_amount_any ("closing_fee",
499 : &rh->details.close_details.fee),
500 0 : GNUNET_JSON_spec_timestamp ("timestamp",
501 : &rh->details.close_details.timestamp),
502 0 : GNUNET_JSON_spec_end ()
503 : };
504 :
505 0 : rh->type = TALER_EXCHANGE_RTT_CLOSING;
506 0 : if (GNUNET_OK !=
507 0 : GNUNET_JSON_parse (transaction,
508 : closing_spec,
509 : NULL, NULL))
510 : {
511 0 : GNUNET_break_op (0);
512 0 : return GNUNET_SYSERR;
513 : }
514 0 : key_state = uc->keys;
515 0 : if (GNUNET_OK !=
516 0 : TALER_EXCHANGE_test_signing_key (
517 : key_state,
518 0 : &rh->details.close_details.exchange_pub))
519 : {
520 0 : GNUNET_break_op (0);
521 0 : return GNUNET_SYSERR;
522 : }
523 0 : if (GNUNET_OK !=
524 0 : TALER_exchange_online_reserve_closed_verify (
525 : rh->details.close_details.timestamp,
526 0 : &rh->amount,
527 0 : &rh->details.close_details.fee,
528 : receiver_uri,
529 0 : &rh->details.close_details.wtid,
530 : uc->reserve_pub,
531 0 : &rh->details.close_details.exchange_pub,
532 0 : &rh->details.close_details.exchange_sig))
533 : {
534 0 : GNUNET_break_op (0);
535 0 : return GNUNET_SYSERR;
536 : }
537 0 : if (0 >
538 0 : TALER_amount_add (uc->total_out,
539 0 : uc->total_out,
540 0 : &rh->amount))
541 : {
542 : /* overflow in history already!? inconceivable! Bad exchange! */
543 0 : GNUNET_break_op (0);
544 0 : return GNUNET_SYSERR;
545 : }
546 : rh->details.close_details.receiver_account_details.full_payto
547 0 : = GNUNET_strdup (receiver_uri.full_payto);
548 0 : return GNUNET_OK;
549 : }
550 :
551 :
552 : /**
553 : * Parse "merge" reserve history entry.
554 : *
555 : * @param[in,out] rh entry to parse
556 : * @param uc our context
557 : * @param transaction the transaction to parse
558 : * @return #GNUNET_OK on success
559 : */
560 : static enum GNUNET_GenericReturnValue
561 8 : parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
562 : struct HistoryParseContext *uc,
563 : const json_t *transaction)
564 : {
565 : uint32_t flags32;
566 : struct GNUNET_JSON_Specification merge_spec[] = {
567 8 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
568 : &rh->details.merge_details.h_contract_terms),
569 8 : GNUNET_JSON_spec_fixed_auto ("merge_pub",
570 : &rh->details.merge_details.merge_pub),
571 8 : GNUNET_JSON_spec_fixed_auto ("purse_pub",
572 : &rh->details.merge_details.purse_pub),
573 8 : GNUNET_JSON_spec_uint32 ("min_age",
574 : &rh->details.merge_details.min_age),
575 8 : GNUNET_JSON_spec_uint32 ("flags",
576 : &flags32),
577 8 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
578 : &rh->details.merge_details.reserve_sig),
579 8 : TALER_JSON_spec_amount_any ("purse_fee",
580 : &rh->details.merge_details.purse_fee),
581 8 : GNUNET_JSON_spec_timestamp ("merge_timestamp",
582 : &rh->details.merge_details.merge_timestamp),
583 8 : GNUNET_JSON_spec_timestamp ("purse_expiration",
584 : &rh->details.merge_details.purse_expiration),
585 8 : GNUNET_JSON_spec_bool ("merged",
586 : &rh->details.merge_details.merged),
587 8 : GNUNET_JSON_spec_end ()
588 : };
589 :
590 8 : rh->type = TALER_EXCHANGE_RTT_MERGE;
591 8 : if (GNUNET_OK !=
592 8 : GNUNET_JSON_parse (transaction,
593 : merge_spec,
594 : NULL, NULL))
595 : {
596 0 : GNUNET_break_op (0);
597 0 : return GNUNET_SYSERR;
598 : }
599 8 : rh->details.merge_details.flags =
600 : (enum TALER_WalletAccountMergeFlags) flags32;
601 8 : if (GNUNET_OK !=
602 8 : TALER_wallet_account_merge_verify (
603 : rh->details.merge_details.merge_timestamp,
604 8 : &rh->details.merge_details.purse_pub,
605 : rh->details.merge_details.purse_expiration,
606 8 : &rh->details.merge_details.h_contract_terms,
607 8 : &rh->amount,
608 8 : &rh->details.merge_details.purse_fee,
609 : rh->details.merge_details.min_age,
610 : rh->details.merge_details.flags,
611 : uc->reserve_pub,
612 8 : &rh->details.merge_details.reserve_sig))
613 : {
614 0 : GNUNET_break_op (0);
615 0 : return GNUNET_SYSERR;
616 : }
617 8 : if (rh->details.merge_details.merged)
618 : {
619 8 : if (0 >
620 8 : TALER_amount_add (uc->total_in,
621 8 : uc->total_in,
622 8 : &rh->amount))
623 : {
624 : /* overflow in history already!? inconceivable! Bad exchange! */
625 0 : GNUNET_break_op (0);
626 0 : return GNUNET_SYSERR;
627 : }
628 : }
629 : else
630 : {
631 0 : if (0 >
632 0 : TALER_amount_add (uc->total_out,
633 0 : uc->total_out,
634 0 : &rh->details.merge_details.purse_fee))
635 : {
636 : /* overflow in history already!? inconceivable! Bad exchange! */
637 0 : GNUNET_break_op (0);
638 0 : return GNUNET_SYSERR;
639 : }
640 : }
641 8 : return GNUNET_OK;
642 : }
643 :
644 :
645 : /**
646 : * Parse "open" reserve open entry.
647 : *
648 : * @param[in,out] rh entry to parse
649 : * @param uc our context
650 : * @param transaction the transaction to parse
651 : * @return #GNUNET_OK on success
652 : */
653 : static enum GNUNET_GenericReturnValue
654 0 : parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
655 : struct HistoryParseContext *uc,
656 : const json_t *transaction)
657 : {
658 : struct GNUNET_JSON_Specification open_spec[] = {
659 0 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
660 : &rh->details.open_request.reserve_sig),
661 0 : TALER_JSON_spec_amount_any ("open_payment",
662 : &rh->details.open_request.reserve_payment),
663 0 : GNUNET_JSON_spec_uint32 ("requested_min_purses",
664 : &rh->details.open_request.purse_limit),
665 0 : GNUNET_JSON_spec_timestamp ("request_timestamp",
666 : &rh->details.open_request.request_timestamp),
667 0 : GNUNET_JSON_spec_timestamp ("requested_expiration",
668 : &rh->details.open_request.reserve_expiration),
669 0 : GNUNET_JSON_spec_end ()
670 : };
671 :
672 0 : rh->type = TALER_EXCHANGE_RTT_OPEN;
673 0 : if (GNUNET_OK !=
674 0 : GNUNET_JSON_parse (transaction,
675 : open_spec,
676 : NULL, NULL))
677 : {
678 0 : GNUNET_break_op (0);
679 0 : return GNUNET_SYSERR;
680 : }
681 0 : if (GNUNET_OK !=
682 0 : TALER_wallet_reserve_open_verify (
683 0 : &rh->amount,
684 : rh->details.open_request.request_timestamp,
685 : rh->details.open_request.reserve_expiration,
686 : rh->details.open_request.purse_limit,
687 : uc->reserve_pub,
688 0 : &rh->details.open_request.reserve_sig))
689 : {
690 0 : GNUNET_break_op (0);
691 0 : return GNUNET_SYSERR;
692 : }
693 0 : if (0 >
694 0 : TALER_amount_add (uc->total_out,
695 0 : uc->total_out,
696 0 : &rh->amount))
697 : {
698 : /* overflow in history already!? inconceivable! Bad exchange! */
699 0 : GNUNET_break_op (0);
700 0 : return GNUNET_SYSERR;
701 : }
702 0 : return GNUNET_OK;
703 : }
704 :
705 :
706 : /**
707 : * Parse "close" reserve close entry.
708 : *
709 : * @param[in,out] rh entry to parse
710 : * @param uc our context
711 : * @param transaction the transaction to parse
712 : * @return #GNUNET_OK on success
713 : */
714 : static enum GNUNET_GenericReturnValue
715 0 : parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
716 : struct HistoryParseContext *uc,
717 : const json_t *transaction)
718 : {
719 : struct GNUNET_JSON_Specification close_spec[] = {
720 0 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
721 : &rh->details.close_request.reserve_sig),
722 0 : GNUNET_JSON_spec_mark_optional (
723 0 : GNUNET_JSON_spec_fixed_auto ("h_payto",
724 : &rh->details.close_request.
725 : target_account_h_payto),
726 : NULL),
727 0 : GNUNET_JSON_spec_timestamp ("request_timestamp",
728 : &rh->details.close_request.request_timestamp),
729 0 : GNUNET_JSON_spec_end ()
730 : };
731 :
732 0 : rh->type = TALER_EXCHANGE_RTT_CLOSE;
733 0 : if (GNUNET_OK !=
734 0 : GNUNET_JSON_parse (transaction,
735 : close_spec,
736 : NULL, NULL))
737 : {
738 0 : GNUNET_break_op (0);
739 0 : return GNUNET_SYSERR;
740 : }
741 : /* force amount to invalid */
742 0 : memset (&rh->amount,
743 : 0,
744 : sizeof (rh->amount));
745 0 : if (GNUNET_OK !=
746 0 : TALER_wallet_reserve_close_verify (
747 : rh->details.close_request.request_timestamp,
748 0 : &rh->details.close_request.target_account_h_payto,
749 : uc->reserve_pub,
750 0 : &rh->details.close_request.reserve_sig))
751 : {
752 0 : GNUNET_break_op (0);
753 0 : return GNUNET_SYSERR;
754 : }
755 0 : return GNUNET_OK;
756 : }
757 :
758 :
759 : static void
760 8 : free_reserve_history (
761 : unsigned int len,
762 : struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
763 8 : {
764 31 : for (unsigned int i = 0; i<len; i++)
765 : {
766 23 : struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i];
767 :
768 23 : switch (rhi->type)
769 : {
770 8 : case TALER_EXCHANGE_RTT_CREDIT:
771 8 : GNUNET_free (rhi->details.in_details.sender_url.full_payto);
772 8 : break;
773 7 : case TALER_EXCHANGE_RTT_WITHDRAWAL:
774 7 : break;
775 0 : case TALER_EXCHANGE_RTT_RECOUP:
776 0 : break;
777 0 : case TALER_EXCHANGE_RTT_CLOSING:
778 0 : break;
779 8 : case TALER_EXCHANGE_RTT_MERGE:
780 8 : break;
781 0 : case TALER_EXCHANGE_RTT_OPEN:
782 0 : break;
783 0 : case TALER_EXCHANGE_RTT_CLOSE:
784 0 : GNUNET_free (rhi->details.close_details
785 : .receiver_account_details.full_payto);
786 0 : break;
787 : }
788 : }
789 8 : GNUNET_free (rhistory);
790 8 : }
791 :
792 :
793 : /**
794 : * Parse history given in JSON format and return it in binary
795 : * format.
796 : *
797 : * @param keys exchange keys
798 : * @param history JSON array with the history
799 : * @param reserve_pub public key of the reserve to inspect
800 : * @param currency currency we expect the balance to be in
801 : * @param[out] total_in set to value of credits to reserve
802 : * @param[out] total_out set to value of debits from reserve
803 : * @param history_length number of entries in @a history
804 : * @param[out] rhistory array of length @a history_length, set to the
805 : * parsed history entries
806 : * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
807 : * were set,
808 : * #GNUNET_SYSERR if there was a protocol violation in @a history
809 : */
810 : static enum GNUNET_GenericReturnValue
811 8 : parse_reserve_history (
812 : const struct TALER_EXCHANGE_Keys *keys,
813 : const json_t *history,
814 : const struct TALER_ReservePublicKeyP *reserve_pub,
815 : const char *currency,
816 : struct TALER_Amount *total_in,
817 : struct TALER_Amount *total_out,
818 : unsigned int history_length,
819 : struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
820 8 : {
821 : const struct
822 : {
823 : const char *type;
824 : ParseHelper helper;
825 8 : } map[] = {
826 : { "CREDIT", &parse_credit },
827 : { "WITHDRAW", &parse_withdraw },
828 : { "RECOUP", &parse_recoup },
829 : { "MERGE", &parse_merge },
830 : { "CLOSING", &parse_closing },
831 : { "OPEN", &parse_open },
832 : { "CLOSE", &parse_close },
833 : { NULL, NULL }
834 : };
835 8 : struct GNUNET_HashCode uuid[history_length];
836 8 : struct HistoryParseContext uc = {
837 : .keys = keys,
838 : .reserve_pub = reserve_pub,
839 : .uuids = uuid,
840 : .total_in = total_in,
841 : .total_out = total_out
842 : };
843 :
844 8 : GNUNET_assert (GNUNET_OK ==
845 : TALER_amount_set_zero (currency,
846 : total_in));
847 8 : GNUNET_assert (GNUNET_OK ==
848 : TALER_amount_set_zero (currency,
849 : total_out));
850 31 : for (unsigned int off = 0; off<history_length; off++)
851 : {
852 23 : struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
853 : json_t *transaction;
854 : struct TALER_Amount amount;
855 : const char *type;
856 : struct GNUNET_JSON_Specification hist_spec[] = {
857 23 : GNUNET_JSON_spec_string ("type",
858 : &type),
859 23 : TALER_JSON_spec_amount_any ("amount",
860 : &amount),
861 : /* 'wire' and 'signature' are optional depending on 'type'! */
862 23 : GNUNET_JSON_spec_end ()
863 : };
864 23 : bool found = false;
865 :
866 23 : transaction = json_array_get (history,
867 : off);
868 23 : if (GNUNET_OK !=
869 23 : GNUNET_JSON_parse (transaction,
870 : hist_spec,
871 : NULL, NULL))
872 : {
873 0 : GNUNET_break_op (0);
874 0 : json_dumpf (transaction,
875 : stderr,
876 : JSON_INDENT (2));
877 0 : return GNUNET_SYSERR;
878 : }
879 23 : rh->amount = amount;
880 23 : if (GNUNET_YES !=
881 23 : TALER_amount_cmp_currency (&amount,
882 : total_in))
883 : {
884 0 : GNUNET_break_op (0);
885 0 : return GNUNET_SYSERR;
886 : }
887 54 : for (unsigned int i = 0; NULL != map[i].type; i++)
888 : {
889 54 : if (0 == strcasecmp (map[i].type,
890 : type))
891 : {
892 23 : found = true;
893 23 : if (GNUNET_OK !=
894 23 : map[i].helper (rh,
895 : &uc,
896 : transaction))
897 : {
898 0 : GNUNET_break_op (0);
899 0 : return GNUNET_SYSERR;
900 : }
901 23 : break;
902 : }
903 : }
904 23 : if (! found)
905 : {
906 : /* unexpected 'type', protocol incompatibility, complain! */
907 0 : GNUNET_break_op (0);
908 0 : return GNUNET_SYSERR;
909 : }
910 : }
911 8 : return GNUNET_OK;
912 : }
913 :
914 :
915 : /**
916 : * Handle HTTP header received by curl.
917 : *
918 : * @param buffer one line of HTTP header data
919 : * @param size size of an item
920 : * @param nitems number of items passed
921 : * @param userdata our `struct TALER_EXCHANGE_ReservesHistoryHandle *`
922 : * @return `size * nitems`
923 : */
924 : static size_t
925 88 : handle_header (char *buffer,
926 : size_t size,
927 : size_t nitems,
928 : void *userdata)
929 : {
930 88 : struct TALER_EXCHANGE_ReservesHistoryHandle *rhh = userdata;
931 88 : size_t total = size * nitems;
932 : char *ndup;
933 : const char *hdr_type;
934 : char *hdr_val;
935 : char *sp;
936 :
937 88 : ndup = GNUNET_strndup (buffer,
938 : total);
939 88 : hdr_type = strtok_r (ndup,
940 : ":",
941 : &sp);
942 88 : if (NULL == hdr_type)
943 : {
944 0 : GNUNET_free (ndup);
945 0 : return total;
946 : }
947 88 : hdr_val = strtok_r (NULL,
948 : "\n\r",
949 : &sp);
950 88 : if (NULL == hdr_val)
951 : {
952 16 : GNUNET_free (ndup);
953 16 : return total;
954 : }
955 72 : if (' ' == *hdr_val)
956 72 : hdr_val++;
957 72 : if (0 == strcasecmp (hdr_type,
958 : MHD_HTTP_HEADER_ETAG))
959 : {
960 : unsigned long long tval;
961 : char dummy;
962 :
963 8 : if (1 !=
964 8 : sscanf (hdr_val,
965 : "\"%llu\"%c",
966 : &tval,
967 : &dummy))
968 : {
969 0 : GNUNET_break_op (0);
970 0 : GNUNET_free (ndup);
971 0 : return 0;
972 : }
973 8 : rhh->etag = (uint64_t) tval;
974 : }
975 72 : GNUNET_free (ndup);
976 72 : return total;
977 : }
978 :
979 :
980 : /**
981 : * We received an #MHD_HTTP_OK history code. Handle the JSON
982 : * response.
983 : *
984 : * @param rsh handle of the request
985 : * @param j JSON response
986 : * @return #GNUNET_OK on success
987 : */
988 : static enum GNUNET_GenericReturnValue
989 8 : handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
990 : const json_t *j)
991 : {
992 : const json_t *history;
993 : unsigned int len;
994 8 : struct TALER_EXCHANGE_ReserveHistory rs = {
995 : .hr.reply = j,
996 : .hr.http_status = MHD_HTTP_OK,
997 8 : .details.ok.etag = rsh->etag
998 : };
999 : struct GNUNET_JSON_Specification spec[] = {
1000 8 : TALER_JSON_spec_amount_any ("balance",
1001 : &rs.details.ok.balance),
1002 8 : GNUNET_JSON_spec_array_const ("history",
1003 : &history),
1004 8 : GNUNET_JSON_spec_end ()
1005 : };
1006 :
1007 8 : if (GNUNET_OK !=
1008 8 : GNUNET_JSON_parse (j,
1009 : spec,
1010 : NULL,
1011 : NULL))
1012 : {
1013 0 : GNUNET_break_op (0);
1014 0 : return GNUNET_SYSERR;
1015 : }
1016 8 : len = json_array_size (history);
1017 : {
1018 : struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
1019 :
1020 8 : rhistory = GNUNET_new_array (len,
1021 : struct TALER_EXCHANGE_ReserveHistoryEntry);
1022 8 : if (GNUNET_OK !=
1023 8 : parse_reserve_history (rsh->keys,
1024 : history,
1025 8 : &rsh->reserve_pub,
1026 : rs.details.ok.balance.currency,
1027 : &rs.details.ok.total_in,
1028 : &rs.details.ok.total_out,
1029 : len,
1030 : rhistory))
1031 : {
1032 0 : GNUNET_break_op (0);
1033 0 : free_reserve_history (len,
1034 : rhistory);
1035 0 : GNUNET_JSON_parse_free (spec);
1036 0 : return GNUNET_SYSERR;
1037 : }
1038 8 : if (NULL != rsh->cb)
1039 : {
1040 8 : rs.details.ok.history = rhistory;
1041 8 : rs.details.ok.history_len = len;
1042 8 : rsh->cb (rsh->cb_cls,
1043 : &rs);
1044 8 : rsh->cb = NULL;
1045 : }
1046 8 : free_reserve_history (len,
1047 : rhistory);
1048 : }
1049 8 : return GNUNET_OK;
1050 : }
1051 :
1052 :
1053 : /**
1054 : * Function called when we're done processing the
1055 : * HTTP /reserves/$RID/history request.
1056 : *
1057 : * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle`
1058 : * @param response_code HTTP response code, 0 on error
1059 : * @param response parsed JSON result, NULL on error
1060 : */
1061 : static void
1062 8 : handle_reserves_history_finished (void *cls,
1063 : long response_code,
1064 : const void *response)
1065 : {
1066 8 : struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls;
1067 8 : const json_t *j = response;
1068 8 : struct TALER_EXCHANGE_ReserveHistory rs = {
1069 : .hr.reply = j,
1070 8 : .hr.http_status = (unsigned int) response_code
1071 : };
1072 :
1073 8 : rsh->job = NULL;
1074 8 : switch (response_code)
1075 : {
1076 0 : case 0:
1077 0 : rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
1078 0 : break;
1079 8 : case MHD_HTTP_OK:
1080 8 : if (GNUNET_OK !=
1081 8 : handle_reserves_history_ok (rsh,
1082 : j))
1083 : {
1084 0 : rs.hr.http_status = 0;
1085 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
1086 : }
1087 8 : break;
1088 0 : case MHD_HTTP_BAD_REQUEST:
1089 : /* This should never happen, either us or the exchange is buggy
1090 : (or API version conflict); just pass JSON reply to the application */
1091 0 : GNUNET_break (0);
1092 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1093 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1094 0 : break;
1095 0 : case MHD_HTTP_FORBIDDEN:
1096 : /* This should never happen, either us or the exchange is buggy
1097 : (or API version conflict); just pass JSON reply to the application */
1098 0 : GNUNET_break (0);
1099 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1100 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1101 0 : break;
1102 0 : case MHD_HTTP_NOT_FOUND:
1103 : /* Nothing really to verify, this should never
1104 : happen, we should pass the JSON reply to the application */
1105 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1106 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1107 0 : break;
1108 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
1109 : /* Server had an internal issue; we should retry, but this API
1110 : leaves this to the application */
1111 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1112 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1113 0 : break;
1114 0 : default:
1115 : /* unexpected response code */
1116 0 : GNUNET_break_op (0);
1117 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1118 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1119 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1120 : "Unexpected response code %u/%d for reserves history\n",
1121 : (unsigned int) response_code,
1122 : (int) rs.hr.ec);
1123 0 : break;
1124 : }
1125 8 : if (NULL != rsh->cb)
1126 : {
1127 0 : rsh->cb (rsh->cb_cls,
1128 : &rs);
1129 0 : rsh->cb = NULL;
1130 : }
1131 8 : TALER_EXCHANGE_reserves_history_cancel (rsh);
1132 8 : }
1133 :
1134 :
1135 : struct TALER_EXCHANGE_ReservesHistoryHandle *
1136 8 : TALER_EXCHANGE_reserves_history (
1137 : struct GNUNET_CURL_Context *ctx,
1138 : const char *url,
1139 : struct TALER_EXCHANGE_Keys *keys,
1140 : const struct TALER_ReservePrivateKeyP *reserve_priv,
1141 : uint64_t start_off,
1142 : TALER_EXCHANGE_ReservesHistoryCallback cb,
1143 : void *cb_cls)
1144 : {
1145 : struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
1146 : CURL *eh;
1147 : char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64];
1148 : struct curl_slist *job_headers;
1149 :
1150 8 : rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
1151 8 : rsh->cb = cb;
1152 8 : rsh->cb_cls = cb_cls;
1153 8 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
1154 : &rsh->reserve_pub.eddsa_pub);
1155 : {
1156 : char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
1157 : char *end;
1158 :
1159 8 : end = GNUNET_STRINGS_data_to_string (
1160 8 : &rsh->reserve_pub,
1161 : sizeof (rsh->reserve_pub),
1162 : pub_str,
1163 : sizeof (pub_str));
1164 8 : *end = '\0';
1165 8 : if (0 != start_off)
1166 0 : GNUNET_snprintf (arg_str,
1167 : sizeof (arg_str),
1168 : "reserves/%s/history?start=%llu",
1169 : pub_str,
1170 : (unsigned long long) start_off);
1171 : else
1172 8 : GNUNET_snprintf (arg_str,
1173 : sizeof (arg_str),
1174 : "reserves/%s/history",
1175 : pub_str);
1176 : }
1177 8 : rsh->url = TALER_url_join (url,
1178 : arg_str,
1179 : NULL);
1180 8 : if (NULL == rsh->url)
1181 : {
1182 0 : GNUNET_free (rsh);
1183 0 : return NULL;
1184 : }
1185 8 : eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
1186 8 : if (NULL == eh)
1187 : {
1188 0 : GNUNET_break (0);
1189 0 : GNUNET_free (rsh->url);
1190 0 : GNUNET_free (rsh);
1191 0 : return NULL;
1192 : }
1193 8 : GNUNET_assert (CURLE_OK ==
1194 : curl_easy_setopt (eh,
1195 : CURLOPT_HEADERFUNCTION,
1196 : &handle_header));
1197 8 : GNUNET_assert (CURLE_OK ==
1198 : curl_easy_setopt (eh,
1199 : CURLOPT_HEADERDATA,
1200 : rsh));
1201 : {
1202 : struct TALER_ReserveSignatureP reserve_sig;
1203 : char *sig_hdr;
1204 : char *hdr;
1205 :
1206 8 : TALER_wallet_reserve_history_sign (start_off,
1207 : reserve_priv,
1208 : &reserve_sig);
1209 :
1210 8 : sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
1211 : &reserve_sig,
1212 : sizeof (reserve_sig));
1213 8 : GNUNET_asprintf (&hdr,
1214 : "%s: %s",
1215 : TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
1216 : sig_hdr);
1217 8 : GNUNET_free (sig_hdr);
1218 8 : job_headers = curl_slist_append (NULL,
1219 : hdr);
1220 8 : GNUNET_free (hdr);
1221 8 : if (NULL == job_headers)
1222 : {
1223 0 : GNUNET_break (0);
1224 0 : curl_easy_cleanup (eh);
1225 0 : return NULL;
1226 : }
1227 : }
1228 :
1229 8 : rsh->keys = TALER_EXCHANGE_keys_incref (keys);
1230 8 : rsh->job = GNUNET_CURL_job_add2 (ctx,
1231 : eh,
1232 : job_headers,
1233 : &handle_reserves_history_finished,
1234 : rsh);
1235 8 : curl_slist_free_all (job_headers);
1236 8 : return rsh;
1237 : }
1238 :
1239 :
1240 : void
1241 8 : TALER_EXCHANGE_reserves_history_cancel (
1242 : struct TALER_EXCHANGE_ReservesHistoryHandle *rsh)
1243 : {
1244 8 : if (NULL != rsh->job)
1245 : {
1246 0 : GNUNET_CURL_job_cancel (rsh->job);
1247 0 : rsh->job = NULL;
1248 : }
1249 8 : TALER_curl_easy_post_finished (&rsh->post_ctx);
1250 8 : GNUNET_free (rsh->url);
1251 8 : TALER_EXCHANGE_keys_decref (rsh->keys);
1252 8 : GNUNET_free (rsh);
1253 8 : }
1254 :
1255 :
1256 : /* end of exchange_api_reserves_history.c */
|