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