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