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 : GNUNET_assert (0 <=
344 : TALER_amount_add (&fee_acc,
345 : &fee_acc,
346 : &dki->fees.withdraw));
347 9 : GNUNET_assert (0 <=
348 : TALER_amount_add (&amount_acc,
349 : &amount_acc,
350 : &dki->value));
351 : }
352 :
353 7 : if ( (GNUNET_YES !=
354 7 : TALER_amount_cmp_currency (&fee_acc,
355 7 : &withdraw_fee)) ||
356 : (0 !=
357 7 : TALER_amount_cmp (&amount_acc,
358 : &amount_without_fee)) )
359 : {
360 0 : GNUNET_break_op (0);
361 0 : GNUNET_JSON_parse_free (withdraw_spec);
362 0 : return GNUNET_SYSERR;
363 : }
364 7 : rh->details.withdraw.fee = withdraw_fee;
365 : }
366 :
367 : #pragma message "is out_authorization_sig still needed? Not set anywhere"
368 : rh->details.withdraw.out_authorization_sig
369 7 : = json_object_get (transaction,
370 : "signature");
371 : /* Check check that the same withdraw transaction
372 : isn't listed twice by the exchange. We use the
373 : "uuid" array to remember the hashes of all
374 : signatures, and compare the hashes to find
375 : duplicates. */
376 7 : GNUNET_CRYPTO_hash (&reserve_sig,
377 : sizeof (reserve_sig),
378 7 : &uc->uuids[uc->uuid_off]);
379 7 : for (unsigned int i = 0; i<uc->uuid_off; i++)
380 : {
381 0 : if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
382 : &uc->uuids[i]))
383 : {
384 0 : GNUNET_break_op (0);
385 0 : GNUNET_JSON_parse_free (withdraw_spec);
386 0 : return GNUNET_SYSERR;
387 : }
388 : }
389 7 : uc->uuid_off++;
390 :
391 7 : if (0 >
392 7 : TALER_amount_add (uc->total_out,
393 7 : uc->total_out,
394 7 : &rh->amount))
395 : {
396 : /* overflow in history already!? inconceivable! Bad exchange! */
397 0 : GNUNET_break_op (0);
398 0 : GNUNET_JSON_parse_free (withdraw_spec);
399 0 : return GNUNET_SYSERR;
400 : }
401 7 : GNUNET_JSON_parse_free (withdraw_spec);
402 7 : return GNUNET_OK;
403 : }
404 :
405 :
406 : /**
407 : * Parse "recoup" reserve history entry.
408 : *
409 : * @param[in,out] rh entry to parse
410 : * @param uc our context
411 : * @param transaction the transaction to parse
412 : * @return #GNUNET_OK on success
413 : */
414 : static enum GNUNET_GenericReturnValue
415 0 : parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
416 : struct HistoryParseContext *uc,
417 : const json_t *transaction)
418 : {
419 : const struct TALER_EXCHANGE_Keys *key_state;
420 : struct GNUNET_JSON_Specification recoup_spec[] = {
421 0 : GNUNET_JSON_spec_fixed_auto ("coin_pub",
422 : &rh->details.recoup_details.coin_pub),
423 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
424 : &rh->details.recoup_details.exchange_sig),
425 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
426 : &rh->details.recoup_details.exchange_pub),
427 0 : GNUNET_JSON_spec_timestamp ("timestamp",
428 : &rh->details.recoup_details.timestamp),
429 0 : GNUNET_JSON_spec_end ()
430 : };
431 :
432 0 : rh->type = TALER_EXCHANGE_RTT_RECOUP;
433 0 : if (GNUNET_OK !=
434 0 : GNUNET_JSON_parse (transaction,
435 : recoup_spec,
436 : NULL, NULL))
437 : {
438 0 : GNUNET_break_op (0);
439 0 : return GNUNET_SYSERR;
440 : }
441 0 : key_state = uc->keys;
442 0 : if (GNUNET_OK !=
443 0 : TALER_EXCHANGE_test_signing_key (key_state,
444 0 : &rh->details.
445 : recoup_details.exchange_pub))
446 : {
447 0 : GNUNET_break_op (0);
448 0 : return GNUNET_SYSERR;
449 : }
450 0 : if (GNUNET_OK !=
451 0 : TALER_exchange_online_confirm_recoup_verify (
452 : rh->details.recoup_details.timestamp,
453 0 : &rh->amount,
454 0 : &rh->details.recoup_details.coin_pub,
455 : uc->reserve_pub,
456 0 : &rh->details.recoup_details.exchange_pub,
457 0 : &rh->details.recoup_details.exchange_sig))
458 : {
459 0 : GNUNET_break_op (0);
460 0 : return GNUNET_SYSERR;
461 : }
462 0 : if (0 >
463 0 : TALER_amount_add (uc->total_in,
464 0 : uc->total_in,
465 0 : &rh->amount))
466 : {
467 : /* overflow in history already!? inconceivable! Bad exchange! */
468 0 : GNUNET_break_op (0);
469 0 : return GNUNET_SYSERR;
470 : }
471 0 : return GNUNET_OK;
472 : }
473 :
474 :
475 : /**
476 : * Parse "closing" reserve history entry.
477 : *
478 : * @param[in,out] rh entry to parse
479 : * @param uc our context
480 : * @param transaction the transaction to parse
481 : * @return #GNUNET_OK on success
482 : */
483 : static enum GNUNET_GenericReturnValue
484 0 : parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
485 : struct HistoryParseContext *uc,
486 : const json_t *transaction)
487 : {
488 : const struct TALER_EXCHANGE_Keys *key_state;
489 : struct TALER_FullPayto receiver_uri;
490 : struct GNUNET_JSON_Specification closing_spec[] = {
491 0 : TALER_JSON_spec_full_payto_uri (
492 : "receiver_account_details",
493 : &receiver_uri),
494 0 : GNUNET_JSON_spec_fixed_auto ("wtid",
495 : &rh->details.close_details.wtid),
496 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
497 : &rh->details.close_details.exchange_sig),
498 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
499 : &rh->details.close_details.exchange_pub),
500 0 : TALER_JSON_spec_amount_any ("closing_fee",
501 : &rh->details.close_details.fee),
502 0 : GNUNET_JSON_spec_timestamp ("timestamp",
503 : &rh->details.close_details.timestamp),
504 0 : GNUNET_JSON_spec_end ()
505 : };
506 :
507 0 : rh->type = TALER_EXCHANGE_RTT_CLOSING;
508 0 : if (GNUNET_OK !=
509 0 : GNUNET_JSON_parse (transaction,
510 : closing_spec,
511 : NULL, NULL))
512 : {
513 0 : GNUNET_break_op (0);
514 0 : return GNUNET_SYSERR;
515 : }
516 0 : key_state = uc->keys;
517 0 : if (GNUNET_OK !=
518 0 : TALER_EXCHANGE_test_signing_key (
519 : key_state,
520 0 : &rh->details.close_details.exchange_pub))
521 : {
522 0 : GNUNET_break_op (0);
523 0 : return GNUNET_SYSERR;
524 : }
525 0 : if (GNUNET_OK !=
526 0 : TALER_exchange_online_reserve_closed_verify (
527 : rh->details.close_details.timestamp,
528 0 : &rh->amount,
529 0 : &rh->details.close_details.fee,
530 : receiver_uri,
531 0 : &rh->details.close_details.wtid,
532 : uc->reserve_pub,
533 0 : &rh->details.close_details.exchange_pub,
534 0 : &rh->details.close_details.exchange_sig))
535 : {
536 0 : GNUNET_break_op (0);
537 0 : return GNUNET_SYSERR;
538 : }
539 0 : if (0 >
540 0 : TALER_amount_add (uc->total_out,
541 0 : uc->total_out,
542 0 : &rh->amount))
543 : {
544 : /* overflow in history already!? inconceivable! Bad exchange! */
545 0 : GNUNET_break_op (0);
546 0 : return GNUNET_SYSERR;
547 : }
548 : rh->details.close_details.receiver_account_details.full_payto
549 0 : = GNUNET_strdup (receiver_uri.full_payto);
550 0 : return GNUNET_OK;
551 : }
552 :
553 :
554 : /**
555 : * Parse "merge" reserve history entry.
556 : *
557 : * @param[in,out] rh entry to parse
558 : * @param uc our context
559 : * @param transaction the transaction to parse
560 : * @return #GNUNET_OK on success
561 : */
562 : static enum GNUNET_GenericReturnValue
563 8 : parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
564 : struct HistoryParseContext *uc,
565 : const json_t *transaction)
566 : {
567 : uint32_t flags32;
568 : struct GNUNET_JSON_Specification merge_spec[] = {
569 8 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
570 : &rh->details.merge_details.h_contract_terms),
571 8 : GNUNET_JSON_spec_fixed_auto ("merge_pub",
572 : &rh->details.merge_details.merge_pub),
573 8 : GNUNET_JSON_spec_fixed_auto ("purse_pub",
574 : &rh->details.merge_details.purse_pub),
575 8 : GNUNET_JSON_spec_uint32 ("min_age",
576 : &rh->details.merge_details.min_age),
577 8 : GNUNET_JSON_spec_uint32 ("flags",
578 : &flags32),
579 8 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
580 : &rh->details.merge_details.reserve_sig),
581 8 : TALER_JSON_spec_amount_any ("purse_fee",
582 : &rh->details.merge_details.purse_fee),
583 8 : GNUNET_JSON_spec_timestamp ("merge_timestamp",
584 : &rh->details.merge_details.merge_timestamp),
585 8 : GNUNET_JSON_spec_timestamp ("purse_expiration",
586 : &rh->details.merge_details.purse_expiration),
587 8 : GNUNET_JSON_spec_bool ("merged",
588 : &rh->details.merge_details.merged),
589 8 : GNUNET_JSON_spec_end ()
590 : };
591 :
592 8 : rh->type = TALER_EXCHANGE_RTT_MERGE;
593 8 : if (GNUNET_OK !=
594 8 : GNUNET_JSON_parse (transaction,
595 : merge_spec,
596 : NULL, NULL))
597 : {
598 0 : GNUNET_break_op (0);
599 0 : return GNUNET_SYSERR;
600 : }
601 8 : rh->details.merge_details.flags =
602 : (enum TALER_WalletAccountMergeFlags) flags32;
603 8 : if (GNUNET_OK !=
604 8 : TALER_wallet_account_merge_verify (
605 : rh->details.merge_details.merge_timestamp,
606 8 : &rh->details.merge_details.purse_pub,
607 : rh->details.merge_details.purse_expiration,
608 8 : &rh->details.merge_details.h_contract_terms,
609 8 : &rh->amount,
610 8 : &rh->details.merge_details.purse_fee,
611 : rh->details.merge_details.min_age,
612 : rh->details.merge_details.flags,
613 : uc->reserve_pub,
614 8 : &rh->details.merge_details.reserve_sig))
615 : {
616 0 : GNUNET_break_op (0);
617 0 : return GNUNET_SYSERR;
618 : }
619 8 : if (rh->details.merge_details.merged)
620 : {
621 8 : if (0 >
622 8 : TALER_amount_add (uc->total_in,
623 8 : uc->total_in,
624 8 : &rh->amount))
625 : {
626 : /* overflow in history already!? inconceivable! Bad exchange! */
627 0 : GNUNET_break_op (0);
628 0 : return GNUNET_SYSERR;
629 : }
630 : }
631 : else
632 : {
633 0 : if (0 >
634 0 : TALER_amount_add (uc->total_out,
635 0 : uc->total_out,
636 0 : &rh->details.merge_details.purse_fee))
637 : {
638 : /* overflow in history already!? inconceivable! Bad exchange! */
639 0 : GNUNET_break_op (0);
640 0 : return GNUNET_SYSERR;
641 : }
642 : }
643 8 : return GNUNET_OK;
644 : }
645 :
646 :
647 : /**
648 : * Parse "open" reserve open entry.
649 : *
650 : * @param[in,out] rh entry to parse
651 : * @param uc our context
652 : * @param transaction the transaction to parse
653 : * @return #GNUNET_OK on success
654 : */
655 : static enum GNUNET_GenericReturnValue
656 0 : parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
657 : struct HistoryParseContext *uc,
658 : const json_t *transaction)
659 : {
660 : struct GNUNET_JSON_Specification open_spec[] = {
661 0 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
662 : &rh->details.open_request.reserve_sig),
663 0 : TALER_JSON_spec_amount_any ("open_payment",
664 : &rh->details.open_request.reserve_payment),
665 0 : GNUNET_JSON_spec_uint32 ("requested_min_purses",
666 : &rh->details.open_request.purse_limit),
667 0 : GNUNET_JSON_spec_timestamp ("request_timestamp",
668 : &rh->details.open_request.request_timestamp),
669 0 : GNUNET_JSON_spec_timestamp ("requested_expiration",
670 : &rh->details.open_request.reserve_expiration),
671 0 : GNUNET_JSON_spec_end ()
672 : };
673 :
674 0 : rh->type = TALER_EXCHANGE_RTT_OPEN;
675 0 : if (GNUNET_OK !=
676 0 : GNUNET_JSON_parse (transaction,
677 : open_spec,
678 : NULL, NULL))
679 : {
680 0 : GNUNET_break_op (0);
681 0 : return GNUNET_SYSERR;
682 : }
683 0 : if (GNUNET_OK !=
684 0 : TALER_wallet_reserve_open_verify (
685 0 : &rh->amount,
686 : rh->details.open_request.request_timestamp,
687 : rh->details.open_request.reserve_expiration,
688 : rh->details.open_request.purse_limit,
689 : uc->reserve_pub,
690 0 : &rh->details.open_request.reserve_sig))
691 : {
692 0 : GNUNET_break_op (0);
693 0 : return GNUNET_SYSERR;
694 : }
695 0 : if (0 >
696 0 : TALER_amount_add (uc->total_out,
697 0 : uc->total_out,
698 0 : &rh->amount))
699 : {
700 : /* overflow in history already!? inconceivable! Bad exchange! */
701 0 : GNUNET_break_op (0);
702 0 : return GNUNET_SYSERR;
703 : }
704 0 : return GNUNET_OK;
705 : }
706 :
707 :
708 : /**
709 : * Parse "close" reserve close entry.
710 : *
711 : * @param[in,out] rh entry to parse
712 : * @param uc our context
713 : * @param transaction the transaction to parse
714 : * @return #GNUNET_OK on success
715 : */
716 : static enum GNUNET_GenericReturnValue
717 0 : parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
718 : struct HistoryParseContext *uc,
719 : const json_t *transaction)
720 : {
721 : struct GNUNET_JSON_Specification close_spec[] = {
722 0 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
723 : &rh->details.close_request.reserve_sig),
724 0 : GNUNET_JSON_spec_mark_optional (
725 0 : GNUNET_JSON_spec_fixed_auto ("h_payto",
726 : &rh->details.close_request.
727 : target_account_h_payto),
728 : NULL),
729 0 : GNUNET_JSON_spec_timestamp ("request_timestamp",
730 : &rh->details.close_request.request_timestamp),
731 0 : GNUNET_JSON_spec_end ()
732 : };
733 :
734 0 : rh->type = TALER_EXCHANGE_RTT_CLOSE;
735 0 : if (GNUNET_OK !=
736 0 : GNUNET_JSON_parse (transaction,
737 : close_spec,
738 : NULL, NULL))
739 : {
740 0 : GNUNET_break_op (0);
741 0 : return GNUNET_SYSERR;
742 : }
743 : /* force amount to invalid */
744 0 : memset (&rh->amount,
745 : 0,
746 : sizeof (rh->amount));
747 0 : if (GNUNET_OK !=
748 0 : TALER_wallet_reserve_close_verify (
749 : rh->details.close_request.request_timestamp,
750 0 : &rh->details.close_request.target_account_h_payto,
751 : uc->reserve_pub,
752 0 : &rh->details.close_request.reserve_sig))
753 : {
754 0 : GNUNET_break_op (0);
755 0 : return GNUNET_SYSERR;
756 : }
757 0 : return GNUNET_OK;
758 : }
759 :
760 :
761 : static void
762 8 : free_reserve_history (
763 : unsigned int len,
764 : struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
765 8 : {
766 31 : for (unsigned int i = 0; i<len; i++)
767 : {
768 23 : struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i];
769 :
770 23 : switch (rhi->type)
771 : {
772 8 : case TALER_EXCHANGE_RTT_CREDIT:
773 8 : GNUNET_free (rhi->details.in_details.sender_url.full_payto);
774 8 : break;
775 7 : case TALER_EXCHANGE_RTT_WITHDRAWAL:
776 7 : break;
777 0 : case TALER_EXCHANGE_RTT_RECOUP:
778 0 : break;
779 0 : case TALER_EXCHANGE_RTT_CLOSING:
780 0 : break;
781 8 : case TALER_EXCHANGE_RTT_MERGE:
782 8 : break;
783 0 : case TALER_EXCHANGE_RTT_OPEN:
784 0 : break;
785 0 : case TALER_EXCHANGE_RTT_CLOSE:
786 0 : GNUNET_free (rhi->details.close_details
787 : .receiver_account_details.full_payto);
788 0 : break;
789 : }
790 : }
791 8 : GNUNET_free (rhistory);
792 8 : }
793 :
794 :
795 : /**
796 : * Parse history given in JSON format and return it in binary
797 : * format.
798 : *
799 : * @param keys exchange keys
800 : * @param history JSON array with the history
801 : * @param reserve_pub public key of the reserve to inspect
802 : * @param currency currency we expect the balance to be in
803 : * @param[out] total_in set to value of credits to reserve
804 : * @param[out] total_out set to value of debits from reserve
805 : * @param history_length number of entries in @a history
806 : * @param[out] rhistory array of length @a history_length, set to the
807 : * parsed history entries
808 : * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
809 : * were set,
810 : * #GNUNET_SYSERR if there was a protocol violation in @a history
811 : */
812 : static enum GNUNET_GenericReturnValue
813 8 : parse_reserve_history (
814 : const struct TALER_EXCHANGE_Keys *keys,
815 : const json_t *history,
816 : const struct TALER_ReservePublicKeyP *reserve_pub,
817 : const char *currency,
818 : struct TALER_Amount *total_in,
819 : struct TALER_Amount *total_out,
820 : unsigned int history_length,
821 : struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
822 8 : {
823 : const struct
824 : {
825 : const char *type;
826 : ParseHelper helper;
827 8 : } map[] = {
828 : { "CREDIT", &parse_credit },
829 : { "WITHDRAW", &parse_withdraw },
830 : { "RECOUP", &parse_recoup },
831 : { "MERGE", &parse_merge },
832 : { "CLOSING", &parse_closing },
833 : { "OPEN", &parse_open },
834 : { "CLOSE", &parse_close },
835 : { NULL, NULL }
836 : };
837 8 : struct GNUNET_HashCode uuid[history_length];
838 8 : struct HistoryParseContext uc = {
839 : .keys = keys,
840 : .reserve_pub = reserve_pub,
841 : .uuids = uuid,
842 : .total_in = total_in,
843 : .total_out = total_out
844 : };
845 :
846 8 : GNUNET_assert (GNUNET_OK ==
847 : TALER_amount_set_zero (currency,
848 : total_in));
849 8 : GNUNET_assert (GNUNET_OK ==
850 : TALER_amount_set_zero (currency,
851 : total_out));
852 31 : for (unsigned int off = 0; off<history_length; off++)
853 : {
854 23 : struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
855 : json_t *transaction;
856 : struct TALER_Amount amount;
857 : const char *type;
858 : struct GNUNET_JSON_Specification hist_spec[] = {
859 23 : GNUNET_JSON_spec_string ("type",
860 : &type),
861 23 : TALER_JSON_spec_amount_any ("amount",
862 : &amount),
863 : /* 'wire' and 'signature' are optional depending on 'type'! */
864 23 : GNUNET_JSON_spec_end ()
865 : };
866 23 : bool found = false;
867 :
868 23 : transaction = json_array_get (history,
869 : off);
870 23 : if (GNUNET_OK !=
871 23 : GNUNET_JSON_parse (transaction,
872 : hist_spec,
873 : NULL, NULL))
874 : {
875 0 : GNUNET_break_op (0);
876 0 : json_dumpf (transaction,
877 : stderr,
878 : JSON_INDENT (2));
879 0 : return GNUNET_SYSERR;
880 : }
881 23 : rh->amount = amount;
882 23 : if (GNUNET_YES !=
883 23 : TALER_amount_cmp_currency (&amount,
884 : total_in))
885 : {
886 0 : GNUNET_break_op (0);
887 0 : return GNUNET_SYSERR;
888 : }
889 54 : for (unsigned int i = 0; NULL != map[i].type; i++)
890 : {
891 54 : if (0 == strcasecmp (map[i].type,
892 : type))
893 : {
894 23 : found = true;
895 23 : if (GNUNET_OK !=
896 23 : map[i].helper (rh,
897 : &uc,
898 : transaction))
899 : {
900 0 : GNUNET_break_op (0);
901 0 : return GNUNET_SYSERR;
902 : }
903 23 : break;
904 : }
905 : }
906 23 : if (! found)
907 : {
908 : /* unexpected 'type', protocol incompatibility, complain! */
909 0 : GNUNET_break_op (0);
910 0 : return GNUNET_SYSERR;
911 : }
912 : }
913 8 : return GNUNET_OK;
914 : }
915 :
916 :
917 : /**
918 : * Handle HTTP header received by curl.
919 : *
920 : * @param buffer one line of HTTP header data
921 : * @param size size of an item
922 : * @param nitems number of items passed
923 : * @param userdata our `struct TALER_EXCHANGE_ReservesHistoryHandle *`
924 : * @return `size * nitems`
925 : */
926 : static size_t
927 88 : handle_header (char *buffer,
928 : size_t size,
929 : size_t nitems,
930 : void *userdata)
931 : {
932 88 : struct TALER_EXCHANGE_ReservesHistoryHandle *rhh = userdata;
933 88 : size_t total = size * nitems;
934 : char *ndup;
935 : const char *hdr_type;
936 : char *hdr_val;
937 : char *sp;
938 :
939 88 : ndup = GNUNET_strndup (buffer,
940 : total);
941 88 : hdr_type = strtok_r (ndup,
942 : ":",
943 : &sp);
944 88 : if (NULL == hdr_type)
945 : {
946 0 : GNUNET_free (ndup);
947 0 : return total;
948 : }
949 88 : hdr_val = strtok_r (NULL,
950 : "\n\r",
951 : &sp);
952 88 : if (NULL == hdr_val)
953 : {
954 16 : GNUNET_free (ndup);
955 16 : return total;
956 : }
957 72 : if (' ' == *hdr_val)
958 72 : hdr_val++;
959 72 : if (0 == strcasecmp (hdr_type,
960 : MHD_HTTP_HEADER_ETAG))
961 : {
962 : unsigned long long tval;
963 : char dummy;
964 :
965 8 : if (1 !=
966 8 : sscanf (hdr_val,
967 : "\"%llu\"%c",
968 : &tval,
969 : &dummy))
970 : {
971 0 : GNUNET_break_op (0);
972 0 : GNUNET_free (ndup);
973 0 : return 0;
974 : }
975 8 : rhh->etag = (uint64_t) tval;
976 : }
977 72 : GNUNET_free (ndup);
978 72 : return total;
979 : }
980 :
981 :
982 : /**
983 : * We received an #MHD_HTTP_OK history code. Handle the JSON
984 : * response.
985 : *
986 : * @param rsh handle of the request
987 : * @param j JSON response
988 : * @return #GNUNET_OK on success
989 : */
990 : static enum GNUNET_GenericReturnValue
991 8 : handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
992 : const json_t *j)
993 : {
994 : const json_t *history;
995 : unsigned int len;
996 8 : struct TALER_EXCHANGE_ReserveHistory rs = {
997 : .hr.reply = j,
998 : .hr.http_status = MHD_HTTP_OK,
999 8 : .details.ok.etag = rsh->etag
1000 : };
1001 : struct GNUNET_JSON_Specification spec[] = {
1002 8 : TALER_JSON_spec_amount_any ("balance",
1003 : &rs.details.ok.balance),
1004 8 : GNUNET_JSON_spec_array_const ("history",
1005 : &history),
1006 8 : GNUNET_JSON_spec_end ()
1007 : };
1008 :
1009 8 : if (GNUNET_OK !=
1010 8 : GNUNET_JSON_parse (j,
1011 : spec,
1012 : NULL,
1013 : NULL))
1014 : {
1015 0 : GNUNET_break_op (0);
1016 0 : return GNUNET_SYSERR;
1017 : }
1018 8 : len = json_array_size (history);
1019 : {
1020 : struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
1021 :
1022 8 : rhistory = GNUNET_new_array (len,
1023 : struct TALER_EXCHANGE_ReserveHistoryEntry);
1024 8 : if (GNUNET_OK !=
1025 8 : parse_reserve_history (rsh->keys,
1026 : history,
1027 8 : &rsh->reserve_pub,
1028 : rs.details.ok.balance.currency,
1029 : &rs.details.ok.total_in,
1030 : &rs.details.ok.total_out,
1031 : len,
1032 : rhistory))
1033 : {
1034 0 : GNUNET_break_op (0);
1035 0 : free_reserve_history (len,
1036 : rhistory);
1037 0 : GNUNET_JSON_parse_free (spec);
1038 0 : return GNUNET_SYSERR;
1039 : }
1040 8 : if (NULL != rsh->cb)
1041 : {
1042 8 : rs.details.ok.history = rhistory;
1043 8 : rs.details.ok.history_len = len;
1044 8 : rsh->cb (rsh->cb_cls,
1045 : &rs);
1046 8 : rsh->cb = NULL;
1047 : }
1048 8 : free_reserve_history (len,
1049 : rhistory);
1050 : }
1051 8 : return GNUNET_OK;
1052 : }
1053 :
1054 :
1055 : /**
1056 : * Function called when we're done processing the
1057 : * HTTP /reserves/$RID/history request.
1058 : *
1059 : * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle`
1060 : * @param response_code HTTP response code, 0 on error
1061 : * @param response parsed JSON result, NULL on error
1062 : */
1063 : static void
1064 8 : handle_reserves_history_finished (void *cls,
1065 : long response_code,
1066 : const void *response)
1067 : {
1068 8 : struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls;
1069 8 : const json_t *j = response;
1070 8 : struct TALER_EXCHANGE_ReserveHistory rs = {
1071 : .hr.reply = j,
1072 8 : .hr.http_status = (unsigned int) response_code
1073 : };
1074 :
1075 8 : rsh->job = NULL;
1076 8 : switch (response_code)
1077 : {
1078 0 : case 0:
1079 0 : rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
1080 0 : break;
1081 8 : case MHD_HTTP_OK:
1082 8 : if (GNUNET_OK !=
1083 8 : handle_reserves_history_ok (rsh,
1084 : j))
1085 : {
1086 0 : rs.hr.http_status = 0;
1087 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
1088 : }
1089 8 : break;
1090 0 : case MHD_HTTP_BAD_REQUEST:
1091 : /* This should never happen, either us or the exchange is buggy
1092 : (or API version conflict); just pass JSON reply to the application */
1093 0 : GNUNET_break (0);
1094 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1095 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1096 0 : break;
1097 0 : case MHD_HTTP_FORBIDDEN:
1098 : /* This should never happen, either us or the exchange is buggy
1099 : (or API version conflict); just pass JSON reply to the application */
1100 0 : GNUNET_break (0);
1101 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1102 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1103 0 : break;
1104 0 : case MHD_HTTP_NOT_FOUND:
1105 : /* Nothing really to verify, this should never
1106 : happen, we should pass the JSON reply to the application */
1107 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1108 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1109 0 : break;
1110 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
1111 : /* Server had an internal issue; we should retry, but this API
1112 : leaves this to the application */
1113 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1114 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1115 0 : break;
1116 0 : default:
1117 : /* unexpected response code */
1118 0 : GNUNET_break_op (0);
1119 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1120 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1121 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1122 : "Unexpected response code %u/%d for reserves history\n",
1123 : (unsigned int) response_code,
1124 : (int) rs.hr.ec);
1125 0 : break;
1126 : }
1127 8 : if (NULL != rsh->cb)
1128 : {
1129 0 : rsh->cb (rsh->cb_cls,
1130 : &rs);
1131 0 : rsh->cb = NULL;
1132 : }
1133 8 : TALER_EXCHANGE_reserves_history_cancel (rsh);
1134 8 : }
1135 :
1136 :
1137 : struct TALER_EXCHANGE_ReservesHistoryHandle *
1138 8 : TALER_EXCHANGE_reserves_history (
1139 : struct GNUNET_CURL_Context *ctx,
1140 : const char *url,
1141 : struct TALER_EXCHANGE_Keys *keys,
1142 : const struct TALER_ReservePrivateKeyP *reserve_priv,
1143 : uint64_t start_off,
1144 : TALER_EXCHANGE_ReservesHistoryCallback cb,
1145 : void *cb_cls)
1146 : {
1147 : struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
1148 : CURL *eh;
1149 : char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64];
1150 : struct curl_slist *job_headers;
1151 :
1152 8 : rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
1153 8 : rsh->cb = cb;
1154 8 : rsh->cb_cls = cb_cls;
1155 8 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
1156 : &rsh->reserve_pub.eddsa_pub);
1157 : {
1158 : char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
1159 : char *end;
1160 :
1161 8 : end = GNUNET_STRINGS_data_to_string (
1162 8 : &rsh->reserve_pub,
1163 : sizeof (rsh->reserve_pub),
1164 : pub_str,
1165 : sizeof (pub_str));
1166 8 : *end = '\0';
1167 8 : if (0 != start_off)
1168 0 : GNUNET_snprintf (arg_str,
1169 : sizeof (arg_str),
1170 : "reserves/%s/history?start=%llu",
1171 : pub_str,
1172 : (unsigned long long) start_off);
1173 : else
1174 8 : GNUNET_snprintf (arg_str,
1175 : sizeof (arg_str),
1176 : "reserves/%s/history",
1177 : pub_str);
1178 : }
1179 8 : rsh->url = TALER_url_join (url,
1180 : arg_str,
1181 : NULL);
1182 8 : if (NULL == rsh->url)
1183 : {
1184 0 : GNUNET_free (rsh);
1185 0 : return NULL;
1186 : }
1187 8 : eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
1188 8 : if (NULL == eh)
1189 : {
1190 0 : GNUNET_break (0);
1191 0 : GNUNET_free (rsh->url);
1192 0 : GNUNET_free (rsh);
1193 0 : return NULL;
1194 : }
1195 8 : GNUNET_assert (CURLE_OK ==
1196 : curl_easy_setopt (eh,
1197 : CURLOPT_HEADERFUNCTION,
1198 : &handle_header));
1199 8 : GNUNET_assert (CURLE_OK ==
1200 : curl_easy_setopt (eh,
1201 : CURLOPT_HEADERDATA,
1202 : rsh));
1203 : {
1204 : struct TALER_ReserveSignatureP reserve_sig;
1205 : char *sig_hdr;
1206 : char *hdr;
1207 :
1208 8 : TALER_wallet_reserve_history_sign (start_off,
1209 : reserve_priv,
1210 : &reserve_sig);
1211 :
1212 8 : sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
1213 : &reserve_sig,
1214 : sizeof (reserve_sig));
1215 8 : GNUNET_asprintf (&hdr,
1216 : "%s: %s",
1217 : TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
1218 : sig_hdr);
1219 8 : GNUNET_free (sig_hdr);
1220 8 : job_headers = curl_slist_append (NULL,
1221 : hdr);
1222 8 : GNUNET_free (hdr);
1223 8 : if (NULL == job_headers)
1224 : {
1225 0 : GNUNET_break (0);
1226 0 : curl_easy_cleanup (eh);
1227 0 : return NULL;
1228 : }
1229 : }
1230 :
1231 8 : rsh->keys = TALER_EXCHANGE_keys_incref (keys);
1232 8 : rsh->job = GNUNET_CURL_job_add2 (ctx,
1233 : eh,
1234 : job_headers,
1235 : &handle_reserves_history_finished,
1236 : rsh);
1237 8 : curl_slist_free_all (job_headers);
1238 8 : return rsh;
1239 : }
1240 :
1241 :
1242 : void
1243 8 : TALER_EXCHANGE_reserves_history_cancel (
1244 : struct TALER_EXCHANGE_ReservesHistoryHandle *rsh)
1245 : {
1246 8 : if (NULL != rsh->job)
1247 : {
1248 0 : GNUNET_CURL_job_cancel (rsh->job);
1249 0 : rsh->job = NULL;
1250 : }
1251 8 : TALER_curl_easy_post_finished (&rsh->post_ctx);
1252 8 : GNUNET_free (rsh->url);
1253 8 : TALER_EXCHANGE_keys_decref (rsh->keys);
1254 8 : GNUNET_free (rsh);
1255 8 : }
1256 :
1257 :
1258 : /* end of exchange_api_reserves_history.c */
|