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