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