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