Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 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 <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file exchangedb/test_contracts.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `contracts`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_insert_contract(),
23 : * #TALER_EXCHANGEDB_get_contract() and
24 : * #TALER_EXCHANGEDB_get_contract_by_purse().
25 : *
26 : * `contracts` references `purse_requests`: the encrypted contract belongs
27 : * to a purse. It is fetched either by the purse or by the Diffie-Hellman
28 : * public key the client derived, which is why there are two getters. A
29 : * second, different contract for the same purse is reported through
30 : * @e in_conflict rather than as an error.
31 : */
32 : #include "test_common.h"
33 : #include "exchange-database/get_contract.h"
34 : #include "exchange-database/get_contract_by_purse.h"
35 : #include "exchange-database/insert_contract.h"
36 :
37 :
38 : /**
39 : * Build a timestamp from a number of seconds since the epoch.
40 : *
41 : * @param secs seconds since the epoch
42 : * @return the timestamp
43 : */
44 : static struct GNUNET_TIME_Timestamp
45 3 : ts (uint64_t secs)
46 : {
47 3 : struct GNUNET_TIME_Absolute abs = {
48 3 : .abs_value_us = secs * 1000LLU * 1000LLU
49 : };
50 :
51 3 : return GNUNET_TIME_absolute_to_timestamp (abs);
52 : }
53 :
54 :
55 : /**
56 : * Build an encrypted contract.
57 : *
58 : * @param seed seed for the key, signature and payload
59 : * @param payload contents of the "encrypted" contract
60 : * @param[out] econtract set to the contract; free @e econtract with
61 : * GNUNET_free()
62 : */
63 : static void
64 3 : make_econtract (uint32_t seed,
65 : const char *payload,
66 : struct TALER_EncryptedContract *econtract)
67 : {
68 3 : memset (econtract,
69 : 0,
70 : sizeof (*econtract));
71 3 : TDB_fill (&econtract->contract_pub,
72 : sizeof (econtract->contract_pub),
73 : seed);
74 3 : TDB_fill (&econtract->econtract_sig,
75 : sizeof (econtract->econtract_sig),
76 : seed);
77 3 : econtract->econtract_size = strlen (payload) + 1;
78 3 : econtract->econtract = GNUNET_memdup (payload,
79 : econtract->econtract_size);
80 3 : }
81 :
82 :
83 : /**
84 : * Nothing is known while the table is empty, and a contract for a purse
85 : * that does not exist is not stored.
86 : *
87 : * @param pg the database context
88 : * @return 0 on success
89 : */
90 : static int
91 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
92 : {
93 : struct TALER_PurseContractPublicKeyP purse_pub;
94 : struct TALER_PurseContractSignatureP econtract_sig;
95 : struct TALER_ContractDiffiePublicP pub_ckey;
96 : struct TALER_EncryptedContract econtract;
97 : size_t econtract_size;
98 1 : void *econtract_data = NULL;
99 :
100 1 : TDB_FILL (purse_pub,
101 : 1);
102 1 : TDB_FILL (pub_ckey,
103 : 1);
104 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
105 : TALER_EXCHANGEDB_get_contract (pg,
106 : &pub_ckey,
107 : &purse_pub,
108 : &econtract_sig,
109 : &econtract_size,
110 : &econtract_data));
111 1 : FAILIF (NULL != econtract_data);
112 1 : memset (&econtract,
113 : 0,
114 : sizeof (econtract));
115 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
116 : TALER_EXCHANGEDB_get_contract_by_purse (pg,
117 : &purse_pub,
118 : &econtract));
119 1 : return 0;
120 : }
121 :
122 :
123 : /**
124 : * A contract is stored and found by both keys.
125 : *
126 : * @param pg the database context
127 : * @return 0 on success
128 : */
129 : static int
130 1 : check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg)
131 : {
132 : struct TDB_Purse purse;
133 : struct TALER_EncryptedContract econtract;
134 : struct TALER_EncryptedContract got;
135 : struct TALER_PurseContractPublicKeyP got_purse;
136 : struct TALER_PurseContractSignatureP got_sig;
137 : size_t got_size;
138 1 : void *got_data = NULL;
139 1 : bool in_conflict = true;
140 :
141 1 : TDB_purse (pg,
142 : 10,
143 : "5",
144 : ts (1700000000),
145 : &purse);
146 1 : make_econtract (10,
147 : "the-contract",
148 : &econtract);
149 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
150 : TALER_EXCHANGEDB_insert_contract (pg,
151 : &purse.purse_pub,
152 : &econtract,
153 : &in_conflict),
154 : GNUNET_free (econtract.econtract));
155 1 : FAILIF_C (in_conflict,
156 : GNUNET_free (econtract.econtract));
157 1 : FAILIF_C (1 != TDB_count (pg,
158 : "FROM contracts"),
159 : GNUNET_free (econtract.econtract));
160 :
161 : /* by the Diffie-Hellman key the client derived */
162 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
163 : TALER_EXCHANGEDB_get_contract (pg,
164 : &econtract.contract_pub,
165 : &got_purse,
166 : &got_sig,
167 : &got_size,
168 : &got_data),
169 : GNUNET_free (econtract.econtract));
170 1 : FAILIF_C (0 != GNUNET_memcmp (&got_purse,
171 : &purse.purse_pub),
172 : GNUNET_free (got_data); GNUNET_free (econtract.econtract));
173 1 : FAILIF_C (0 != GNUNET_memcmp (&got_sig,
174 : &econtract.econtract_sig),
175 : GNUNET_free (got_data); GNUNET_free (econtract.econtract));
176 1 : FAILIF_C (got_size != econtract.econtract_size,
177 : GNUNET_free (got_data); GNUNET_free (econtract.econtract));
178 1 : FAILIF_C (0 != memcmp (got_data,
179 : econtract.econtract,
180 : got_size),
181 : GNUNET_free (got_data); GNUNET_free (econtract.econtract));
182 1 : GNUNET_free (got_data);
183 :
184 : /* and by the purse */
185 1 : memset (&got,
186 : 0,
187 : sizeof (got));
188 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
189 : TALER_EXCHANGEDB_get_contract_by_purse (pg,
190 : &purse.purse_pub,
191 : &got),
192 : GNUNET_free (econtract.econtract));
193 1 : FAILIF_C (0 != GNUNET_memcmp (&got.contract_pub,
194 : &econtract.contract_pub),
195 : GNUNET_free (got.econtract); GNUNET_free (econtract.econtract));
196 1 : FAILIF_C (got.econtract_size != econtract.econtract_size,
197 : GNUNET_free (got.econtract); GNUNET_free (econtract.econtract));
198 1 : FAILIF_C (0 != memcmp (got.econtract,
199 : econtract.econtract,
200 : got.econtract_size),
201 : GNUNET_free (got.econtract); GNUNET_free (econtract.econtract));
202 1 : GNUNET_free (got.econtract);
203 :
204 : /* re-inserting the identical contract is a no-op */
205 1 : in_conflict = true;
206 1 : FAILIF_C (0 >
207 : TALER_EXCHANGEDB_insert_contract (pg,
208 : &purse.purse_pub,
209 : &econtract,
210 : &in_conflict),
211 : GNUNET_free (econtract.econtract));
212 1 : FAILIF_C (1 != TDB_count (pg,
213 : "FROM contracts"),
214 : GNUNET_free (econtract.econtract));
215 1 : GNUNET_free (econtract.econtract);
216 1 : return 0;
217 : }
218 :
219 :
220 : /**
221 : * A different contract for the same purse is a conflict.
222 : *
223 : * @param pg the database context
224 : * @return 0 on success
225 : */
226 : static int
227 1 : check_conflict (struct TALER_EXCHANGEDB_PostgresContext *pg)
228 : {
229 : struct TDB_Purse purse;
230 : struct TALER_EncryptedContract econtract;
231 1 : bool in_conflict = false;
232 :
233 1 : TDB_purse (pg,
234 : 10,
235 : "5",
236 : ts (1700000000),
237 : &purse);
238 1 : make_econtract (11,
239 : "another-contract",
240 : &econtract);
241 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
242 : TALER_EXCHANGEDB_insert_contract (pg,
243 : &purse.purse_pub,
244 : &econtract,
245 : &in_conflict),
246 : GNUNET_free (econtract.econtract));
247 1 : FAILIF_C (! in_conflict,
248 : GNUNET_free (econtract.econtract));
249 1 : FAILIF_C (1 != TDB_count (pg,
250 : "FROM contracts"),
251 : GNUNET_free (econtract.econtract));
252 1 : GNUNET_free (econtract.econtract);
253 1 : return 0;
254 : }
255 :
256 :
257 : /**
258 : * A second purse can have its own contract, and the two do not mix.
259 : *
260 : * @param pg the database context
261 : * @return 0 on success
262 : */
263 : static int
264 1 : check_second_purse (struct TALER_EXCHANGEDB_PostgresContext *pg)
265 : {
266 : struct TDB_Purse purse;
267 : struct TALER_EncryptedContract econtract;
268 : struct TALER_EncryptedContract got;
269 1 : bool in_conflict = true;
270 :
271 1 : TDB_purse (pg,
272 : 12,
273 : "3",
274 : ts (1700000000),
275 : &purse);
276 1 : make_econtract (12,
277 : "second-contract",
278 : &econtract);
279 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
280 : TALER_EXCHANGEDB_insert_contract (pg,
281 : &purse.purse_pub,
282 : &econtract,
283 : &in_conflict),
284 : GNUNET_free (econtract.econtract));
285 1 : FAILIF_C (in_conflict,
286 : GNUNET_free (econtract.econtract));
287 1 : FAILIF_C (2 != TDB_count (pg,
288 : "FROM contracts"),
289 : GNUNET_free (econtract.econtract));
290 1 : memset (&got,
291 : 0,
292 : sizeof (got));
293 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
294 : TALER_EXCHANGEDB_get_contract_by_purse (pg,
295 : &purse.purse_pub,
296 : &got),
297 : GNUNET_free (econtract.econtract));
298 1 : FAILIF_C (0 != GNUNET_memcmp (&got.contract_pub,
299 : &econtract.contract_pub),
300 : GNUNET_free (got.econtract); GNUNET_free (econtract.econtract));
301 1 : GNUNET_free (got.econtract);
302 1 : GNUNET_free (econtract.econtract);
303 1 : return 0;
304 : }
305 :
306 :
307 : /**
308 : * The checks to run, in order.
309 : */
310 : static const struct TDB_Test tests[] = {
311 : { "contracts-empty",
312 : &check_empty },
313 : { "contracts-insert-and-lookup",
314 : &check_insert_and_lookup },
315 : { "contracts-conflict",
316 : &check_conflict },
317 : { "contracts-second-purse",
318 : &check_second_purse },
319 : { NULL, NULL }
320 : };
321 :
322 :
323 : int
324 1 : main (int argc,
325 : char *const *argv)
326 : {
327 1 : return TDB_main (argc,
328 : argv,
329 : "test-contracts",
330 : "Tests for the exchangedb `contracts' table",
331 : tests);
332 : }
333 :
334 :
335 : /* end of test_contracts.c */
|