Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022, 2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it
6 : under the terms of the GNU General Public License as published by
7 : the Free Software Foundation; either version 3, or (at your
8 : option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing/testing_api_cmd_purse_merge.c
21 : * @brief command for testing /purses/$PID/merge
22 : * @author Christian Grothoff
23 : */
24 : #include "taler/taler_json_lib.h"
25 : #include <gnunet/gnunet_curl_lib.h>
26 : struct PurseMergeState;
27 : #define TALER_EXCHANGE_POST_PURSES_MERGE_RESULT_CLOSURE struct PurseMergeState
28 : #include "taler/exchange/post-purses-PURSE_PUB-merge.h"
29 : #include "taler/taler_testing_lib.h"
30 : #include "taler/taler_signatures.h"
31 :
32 :
33 : /**
34 : * State for a "purse create deposit" CMD.
35 : */
36 : struct PurseMergeState
37 : {
38 :
39 : /**
40 : * Merge time.
41 : */
42 : struct GNUNET_TIME_Timestamp merge_timestamp;
43 :
44 : /**
45 : * Reserve public key (to be merged into)
46 : */
47 : struct TALER_ReservePublicKeyP reserve_pub;
48 :
49 : /**
50 : * Reserve private key (useful especially if
51 : * @e reserve_ref is NULL).
52 : */
53 : struct TALER_ReservePrivateKeyP reserve_priv;
54 :
55 : /**
56 : * Handle while operation is running.
57 : */
58 : struct TALER_EXCHANGE_PostPursesMergeHandle *dh;
59 :
60 : /**
61 : * Reference to the merge capability.
62 : */
63 : const char *merge_ref;
64 :
65 : /**
66 : * Reference to the reserve, or NULL (!).
67 : */
68 : const char *reserve_ref;
69 :
70 : /**
71 : * Interpreter state.
72 : */
73 : struct TALER_TESTING_Interpreter *is;
74 :
75 : /**
76 : * Hash of the payto://-URI for the reserve we are
77 : * merging into.
78 : */
79 : struct TALER_NormalizedPaytoHashP h_payto;
80 :
81 : /**
82 : * Set to the KYC requirement row *if* the exchange replied with
83 : * a request for KYC.
84 : */
85 : uint64_t requirement_row;
86 :
87 : /**
88 : * Reserve history entry that corresponds to this operation.
89 : * Will be of type #TALER_EXCHANGE_RTT_MERGE.
90 : */
91 : struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
92 :
93 : /**
94 : * Public key of the purse.
95 : */
96 : struct TALER_PurseContractPublicKeyP purse_pub;
97 :
98 : /**
99 : * Public key of the merge capability.
100 : */
101 : struct TALER_PurseMergePublicKeyP merge_pub;
102 :
103 : /**
104 : * Contract value.
105 : */
106 : struct TALER_Amount value_after_fees;
107 :
108 : /**
109 : * Hash of the contract.
110 : */
111 : struct TALER_PrivateContractHashP h_contract_terms;
112 :
113 : /**
114 : * When does the purse expire.
115 : */
116 : struct GNUNET_TIME_Timestamp purse_expiration;
117 :
118 : /**
119 : * Minimum age of deposits into the purse.
120 : */
121 : uint32_t min_age;
122 :
123 : /**
124 : * Expected HTTP response code.
125 : */
126 : unsigned int expected_response_code;
127 :
128 : };
129 :
130 :
131 : /**
132 : * Callback to analyze the /purses/$PID/merge response, just used to check if
133 : * the response code is acceptable.
134 : *
135 : * @param ds closure.
136 : * @param dr merge response details
137 : */
138 : static void
139 6 : merge_cb (struct PurseMergeState *ds,
140 : const struct TALER_EXCHANGE_PostPursesMergeResponse *dr)
141 : {
142 6 : ds->dh = NULL;
143 6 : switch (dr->hr.http_status)
144 : {
145 3 : case MHD_HTTP_OK:
146 3 : ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE;
147 3 : ds->reserve_history.amount = ds->value_after_fees;
148 3 : GNUNET_assert (GNUNET_OK ==
149 : TALER_amount_set_zero (
150 : ds->value_after_fees.currency,
151 : &ds->reserve_history.details.merge_details.purse_fee));
152 : ds->reserve_history.details.merge_details.h_contract_terms
153 3 : = ds->h_contract_terms;
154 : ds->reserve_history.details.merge_details.merge_pub
155 3 : = ds->merge_pub;
156 : ds->reserve_history.details.merge_details.purse_pub
157 3 : = ds->purse_pub;
158 : ds->reserve_history.details.merge_details.reserve_sig
159 3 : = *dr->reserve_sig;
160 : ds->reserve_history.details.merge_details.merge_timestamp
161 3 : = ds->merge_timestamp;
162 : ds->reserve_history.details.merge_details.purse_expiration
163 3 : = ds->purse_expiration;
164 : ds->reserve_history.details.merge_details.min_age
165 3 : = ds->min_age;
166 : ds->reserve_history.details.merge_details.flags
167 3 : = TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE;
168 3 : break;
169 1 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
170 : /* KYC required */
171 1 : ds->requirement_row =
172 1 : dr->details.unavailable_for_legal_reasons.requirement_row;
173 1 : GNUNET_break (0 ==
174 : GNUNET_memcmp (
175 : &ds->h_payto,
176 : &dr->details.unavailable_for_legal_reasons.h_payto));
177 1 : break;
178 : }
179 :
180 :
181 6 : if (ds->expected_response_code != dr->hr.http_status)
182 : {
183 0 : TALER_TESTING_unexpected_status (ds->is,
184 : dr->hr.http_status,
185 : ds->expected_response_code);
186 0 : return;
187 : }
188 6 : TALER_TESTING_interpreter_next (ds->is);
189 : }
190 :
191 :
192 : /**
193 : * Run the command.
194 : *
195 : * @param cls closure.
196 : * @param cmd the command to execute.
197 : * @param is the interpreter state.
198 : */
199 : static void
200 6 : merge_run (void *cls,
201 : const struct TALER_TESTING_Command *cmd,
202 : struct TALER_TESTING_Interpreter *is)
203 : {
204 6 : struct PurseMergeState *ds = cls;
205 : const struct TALER_PurseMergePrivateKeyP *merge_priv;
206 : const json_t *ct;
207 : const struct TALER_TESTING_Command *ref;
208 :
209 : (void) cmd;
210 6 : ds->is = is;
211 6 : ref = TALER_TESTING_interpreter_lookup_command (ds->is,
212 : ds->merge_ref);
213 6 : GNUNET_assert (NULL != ref);
214 6 : if (GNUNET_OK !=
215 6 : TALER_TESTING_get_trait_merge_priv (ref,
216 : &merge_priv))
217 : {
218 0 : GNUNET_break (0);
219 0 : TALER_TESTING_interpreter_fail (ds->is);
220 0 : return;
221 : }
222 : {
223 : const struct TALER_PurseContractPublicKeyP *purse_pub;
224 :
225 6 : if (GNUNET_OK !=
226 6 : TALER_TESTING_get_trait_purse_pub (ref,
227 : &purse_pub))
228 : {
229 0 : GNUNET_break (0);
230 0 : TALER_TESTING_interpreter_fail (ds->is);
231 0 : return;
232 : }
233 6 : ds->purse_pub = *purse_pub;
234 : }
235 :
236 6 : if (GNUNET_OK !=
237 6 : TALER_TESTING_get_trait_contract_terms (ref,
238 : &ct))
239 : {
240 0 : GNUNET_break (0);
241 0 : TALER_TESTING_interpreter_fail (ds->is);
242 0 : return;
243 : }
244 6 : if (GNUNET_OK !=
245 6 : TALER_JSON_contract_hash (ct,
246 : &ds->h_contract_terms))
247 : {
248 0 : GNUNET_break (0);
249 0 : TALER_TESTING_interpreter_fail (ds->is);
250 0 : return;
251 : }
252 : {
253 : struct GNUNET_JSON_Specification spec[] = {
254 6 : GNUNET_JSON_spec_timestamp ("pay_deadline",
255 : &ds->purse_expiration),
256 6 : TALER_JSON_spec_amount_any ("amount",
257 : &ds->value_after_fees),
258 6 : GNUNET_JSON_spec_mark_optional (
259 : GNUNET_JSON_spec_uint32 ("minimum_age",
260 : &ds->min_age),
261 : NULL),
262 6 : GNUNET_JSON_spec_end ()
263 : };
264 :
265 6 : if (GNUNET_OK !=
266 6 : GNUNET_JSON_parse (ct,
267 : spec,
268 : NULL, NULL))
269 : {
270 0 : GNUNET_break (0);
271 0 : TALER_TESTING_interpreter_fail (ds->is);
272 0 : return;
273 : }
274 : }
275 :
276 6 : if (NULL == ds->reserve_ref)
277 : {
278 0 : GNUNET_CRYPTO_eddsa_key_create (&ds->reserve_priv.eddsa_priv);
279 : }
280 : else
281 : {
282 : const struct TALER_ReservePrivateKeyP *rp;
283 :
284 6 : ref = TALER_TESTING_interpreter_lookup_command (ds->is,
285 : ds->reserve_ref);
286 6 : GNUNET_assert (NULL != ref);
287 6 : if (GNUNET_OK !=
288 6 : TALER_TESTING_get_trait_reserve_priv (ref,
289 : &rp))
290 : {
291 0 : GNUNET_break (0);
292 0 : TALER_TESTING_interpreter_fail (ds->is);
293 0 : return;
294 : }
295 6 : ds->reserve_priv = *rp;
296 : }
297 6 : GNUNET_CRYPTO_eddsa_key_get_public (&ds->reserve_priv.eddsa_priv,
298 : &ds->reserve_pub.eddsa_pub);
299 : {
300 : struct TALER_NormalizedPayto payto_uri;
301 : const char *exchange_url;
302 : const struct TALER_TESTING_Command *exchange_cmd;
303 :
304 6 : exchange_cmd = TALER_TESTING_interpreter_get_command (is,
305 : "exchange");
306 6 : if (NULL == exchange_cmd)
307 : {
308 0 : GNUNET_break (0);
309 0 : TALER_TESTING_interpreter_fail (is);
310 0 : return;
311 : }
312 6 : GNUNET_assert (GNUNET_OK ==
313 : TALER_TESTING_get_trait_exchange_url (exchange_cmd,
314 : &exchange_url));
315 6 : payto_uri = TALER_reserve_make_payto (exchange_url,
316 6 : &ds->reserve_pub);
317 6 : TALER_normalized_payto_hash (payto_uri,
318 : &ds->h_payto);
319 6 : GNUNET_free (payto_uri.normalized_payto);
320 : }
321 6 : GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
322 : &ds->merge_pub.eddsa_pub);
323 6 : ds->merge_timestamp = GNUNET_TIME_timestamp_get ();
324 6 : ds->dh = TALER_EXCHANGE_post_purses_merge_create (
325 : TALER_TESTING_interpreter_get_context (is),
326 : TALER_TESTING_get_exchange_url (is),
327 : TALER_TESTING_get_keys (is),
328 : NULL, /* no wad */
329 6 : &ds->reserve_priv,
330 6 : &ds->purse_pub,
331 : merge_priv,
332 6 : &ds->h_contract_terms,
333 6 : ds->min_age,
334 6 : &ds->value_after_fees,
335 : ds->purse_expiration,
336 : ds->merge_timestamp);
337 6 : GNUNET_assert (NULL != ds->dh);
338 6 : GNUNET_assert (TALER_EC_NONE ==
339 : TALER_EXCHANGE_post_purses_merge_start (ds->dh,
340 : &merge_cb,
341 : ds));
342 : }
343 :
344 :
345 : /**
346 : * Free the state of a "merge" CMD, and possibly cancel a
347 : * pending operation thereof.
348 : *
349 : * @param cls closure, must be a `struct PurseMergeState`.
350 : * @param cmd the command which is being cleaned up.
351 : */
352 : static void
353 6 : merge_cleanup (void *cls,
354 : const struct TALER_TESTING_Command *cmd)
355 : {
356 6 : struct PurseMergeState *ds = cls;
357 :
358 6 : if (NULL != ds->dh)
359 : {
360 0 : TALER_TESTING_command_incomplete (ds->is,
361 : cmd->label);
362 0 : TALER_EXCHANGE_post_purses_merge_cancel (ds->dh);
363 0 : ds->dh = NULL;
364 : }
365 6 : GNUNET_free (ds);
366 6 : }
367 :
368 :
369 : /**
370 : * Offer internal data from a "merge" CMD, to other commands.
371 : *
372 : * @param cls closure.
373 : * @param[out] ret result.
374 : * @param trait name of the trait.
375 : * @param index index number of the object to offer.
376 : * @return #GNUNET_OK on success.
377 : */
378 : static enum GNUNET_GenericReturnValue
379 25 : merge_traits (void *cls,
380 : const void **ret,
381 : const char *trait,
382 : unsigned int index)
383 : {
384 25 : struct PurseMergeState *ds = cls;
385 : struct TALER_TESTING_Trait traits[] = {
386 : /* history entry MUST be first due to response code logic below! */
387 25 : TALER_TESTING_make_trait_reserve_history (0,
388 25 : &ds->reserve_history),
389 25 : TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
390 25 : TALER_TESTING_make_trait_timestamp (0,
391 25 : &ds->merge_timestamp),
392 25 : TALER_TESTING_make_trait_legi_requirement_row (&ds->requirement_row),
393 25 : TALER_TESTING_make_trait_h_normalized_payto (&ds->h_payto),
394 25 : TALER_TESTING_trait_end ()
395 : };
396 :
397 25 : return TALER_TESTING_get_trait ((ds->expected_response_code == MHD_HTTP_OK)
398 : ? &traits[0] /* we have reserve history */
399 : : &traits[1], /* skip reserve history */
400 : ret,
401 : trait,
402 : index);
403 : }
404 :
405 :
406 : struct TALER_TESTING_Command
407 6 : TALER_TESTING_cmd_purse_merge (
408 : const char *label,
409 : unsigned int expected_http_status,
410 : const char *merge_ref,
411 : const char *reserve_ref)
412 : {
413 : struct PurseMergeState *ds;
414 :
415 6 : ds = GNUNET_new (struct PurseMergeState);
416 6 : ds->merge_ref = merge_ref;
417 6 : ds->reserve_ref = reserve_ref;
418 6 : ds->expected_response_code = expected_http_status;
419 :
420 : {
421 6 : struct TALER_TESTING_Command cmd = {
422 : .cls = ds,
423 : .label = label,
424 : .run = &merge_run,
425 : .cleanup = &merge_cleanup,
426 : .traits = &merge_traits
427 : };
428 :
429 6 : return cmd;
430 : }
431 : }
432 :
433 :
434 : /* end of testing_api_cmd_purse_merge.c */
|