testing_api_cmd_block_list.c (12206B)
1 /* 2 This file is part of SYNC 3 Copyright (C) 2026 Taler Systems SA 4 5 SYNC is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as 7 published by the Free Software Foundation; either version 3, or 8 (at your option) any later version. 9 10 SYNC 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 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with SYNC; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file testing/testing_api_cmd_block_list.c 21 * @brief command to list blocks from the sync backend service. 22 * @author Iván Ávalos 23 */ 24 #include "platform.h" 25 #include "sync/sync_service.h" 26 #include "sync/sync_signatures.h" 27 #include "sync/sync_testing_lib.h" 28 #include <taler/taler_util.h> 29 #include <taler/taler_testing_lib.h> 30 #include <gnunet/gnunet_curl_lib.h> 31 32 33 /** 34 * Maximum number of expected entries in block list. 35 */ 36 #define MAX_BLOCK_LIST_ENTRIES 256 37 38 39 /** 40 * State for a "block list" CMD. 41 */ 42 struct BlockListState 43 { 44 45 /** 46 * URL of the sync backend. 47 */ 48 const char *sync_url; 49 50 /** 51 * Reference to a command that provides the account public key. 52 */ 53 const char *account_reference; 54 55 /** 56 * Account public key to query. 57 */ 58 struct SYNC_AccountPublicKeyP account_pub; 59 60 /** 61 * Maximum number of blocks to return. 62 */ 63 int16_t limit; 64 65 /** 66 * Optional starting nonce. 67 */ 68 struct SYNC_BlockNonce start_nonce; 69 70 /** 71 * True if @e start_nonce should be used. 72 */ 73 bool have_start_nonce; 74 75 /** 76 * Expected HTTP status code. 77 */ 78 unsigned int http_status; 79 80 /** 81 * The interpreter state. 82 */ 83 struct TALER_TESTING_Interpreter *is; 84 85 /** 86 * Handle for the library block list operation. 87 */ 88 struct SYNC_BlockListOperation *blop; 89 90 /** 91 * Result details to expose as trait. 92 */ 93 struct SYNC_BlockListDetails details; 94 95 /** 96 * Number of expected entries. 97 */ 98 unsigned int num_expected; 99 100 /** 101 * Expected nonces (array of @e num_expected, up to 102 * #MAX_BLOCK_LIST_ENTRIES). 103 */ 104 struct SYNC_BlockNonce expected_nonces[MAX_BLOCK_LIST_ENTRIES]; 105 106 /** 107 * Expected block hashes (array of @e num_expected, up to 108 * #MAX_BLOCK_LIST_ENTRIES). 109 */ 110 struct GNUNET_HashCode expected_hashes[MAX_BLOCK_LIST_ENTRIES]; 111 112 }; 113 114 115 /** 116 * Function called when we're done processing the 117 * block list. 118 * 119 * @param cls the `struct BlockListState` 120 * @param ld details about the list operation 121 */ 122 static void 123 handle_block_list_finished (void *cls, 124 const struct SYNC_BlockListDetails *ld) 125 { 126 struct BlockListState *bls = cls; 127 bool mismatch = false; 128 129 bls->blop = NULL; 130 bls->details.http_status = ld->http_status; 131 bls->details.ec = ld->ec; 132 if (ld->http_status != bls->http_status) 133 { 134 TALER_TESTING_unexpected_status (bls->is, 135 ld->http_status, 136 bls->http_status); 137 return; 138 } 139 /* Whatever the server returns must carry a signature that verifies 140 against the stored context, or a client has no way to trust the 141 backup it is about to restore */ 142 for (unsigned int i = 0; i < ld->num_entries; i++) 143 { 144 const struct SYNC_BlockListEntry *e = &ld->entries[i]; 145 146 if (GNUNET_OK != 147 SYNC_block_upload_verify (&bls->account_pub, 148 &e->nonce, 149 e->have_prev_nonce 150 ? &e->prev_nonce 151 : NULL, 152 e->have_next_nonce 153 ? &e->next_nonce 154 : NULL, 155 &e->old_hash, 156 &e->hash, 157 &e->refs_hash, 158 &e->upload_sig)) 159 { 160 char *nonce_s; 161 162 mismatch = true; 163 nonce_s = GNUNET_STRINGS_data_to_string_alloc ( 164 &e->nonce, 165 sizeof (struct SYNC_BlockNonce)); 166 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 167 "Block list entry %u (%s): stored signature does not" 168 " verify against the stored context\n", 169 i, 170 nonce_s); 171 GNUNET_free (nonce_s); 172 } 173 } 174 175 if (bls->num_expected > 0) 176 { 177 if (ld->num_entries != bls->num_expected) 178 { 179 GNUNET_break_op (0); 180 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 181 "Expected %u entries in block list, got %u\n", 182 bls->num_expected, 183 ld->num_entries); 184 TALER_TESTING_interpreter_fail (bls->is); 185 return; 186 } 187 for (unsigned int i = 0; i < bls->num_expected; i++) 188 { 189 if (0 != GNUNET_memcmp (&ld->entries[i].nonce, 190 &bls->expected_nonces[i])) 191 { 192 char *nonce_a; 193 char *nonce_b; 194 195 mismatch = true; 196 nonce_a = GNUNET_STRINGS_data_to_string_alloc ( 197 &ld->entries[i].nonce, 198 sizeof (struct SYNC_BlockNonce)); 199 nonce_b = GNUNET_STRINGS_data_to_string_alloc ( 200 &bls->expected_nonces[i], 201 sizeof (struct SYNC_BlockNonce)); 202 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 203 "Block list entry %u nonce mismatch\n(%s vs.\n %s)\n", 204 i, 205 nonce_a, 206 nonce_b); 207 GNUNET_free (nonce_a); 208 GNUNET_free (nonce_b); 209 } 210 if (0 != GNUNET_memcmp (&ld->entries[i].hash, 211 &bls->expected_hashes[i])) 212 { 213 char *hash_a; 214 char *hash_b; 215 216 mismatch = true; 217 hash_a = GNUNET_STRINGS_data_to_string_alloc ( 218 &ld->entries[i].hash, 219 sizeof (struct GNUNET_HashCode)); 220 hash_b = GNUNET_STRINGS_data_to_string_alloc ( 221 &bls->expected_hashes[i], 222 sizeof (struct GNUNET_HashCode)); 223 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 224 "Block list entry %u hash mismatch\n(%s vs.\n %s)\n", 225 i, 226 hash_a, 227 hash_b); 228 GNUNET_free (hash_a); 229 GNUNET_free (hash_b); 230 } 231 /* The payload must hash to the hash reported for the block */ 232 { 233 struct GNUNET_HashCode dh; 234 235 GNUNET_CRYPTO_hash (ld->entries[i].data, 236 ld->entries[i].data_size, 237 &dh); 238 if (0 != GNUNET_memcmp (&dh, 239 &ld->entries[i].hash)) 240 { 241 mismatch = true; 242 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 243 "Block list entry %u: data of %u bytes does not" 244 " hash to the reported block hash\n", 245 i, 246 (unsigned int) ld->entries[i].data_size); 247 } 248 } 249 } 250 } 251 252 if (mismatch) 253 { 254 GNUNET_break_op (0); 255 TALER_TESTING_interpreter_fail (bls->is); 256 return; 257 } 258 259 TALER_TESTING_interpreter_next (bls->is); 260 } 261 262 263 /** 264 * Run a "block list" CMD. 265 * 266 * @param cls closure. 267 * @param cmd command currently being run. 268 * @param is interpreter state. 269 */ 270 static void 271 block_list_run (void *cls, 272 const struct TALER_TESTING_Command *cmd, 273 struct TALER_TESTING_Interpreter *is) 274 { 275 struct BlockListState *bls = cls; 276 277 (void) cmd; 278 bls->is = is; 279 280 if (NULL != bls->account_reference) 281 { 282 const struct TALER_TESTING_Command *ref; 283 284 ref = TALER_TESTING_interpreter_lookup_command (is, 285 bls->account_reference); 286 if (NULL == ref) 287 { 288 GNUNET_break (0); 289 goto fail; 290 } 291 { 292 const struct SYNC_AccountPublicKeyP *pub; 293 294 if (GNUNET_OK != 295 TALER_TESTING_get_trait_sync_account_pub (ref, 296 &pub)) 297 goto fail; 298 bls->account_pub = *pub; 299 } 300 } 301 else 302 goto fail; 303 304 bls->blop = SYNC_block_list ( 305 TALER_TESTING_interpreter_get_context (is), 306 bls->sync_url, 307 &bls->account_pub, 308 bls->limit, 309 bls->have_start_nonce ? &bls->start_nonce : NULL, 310 &handle_block_list_finished, 311 bls); 312 if (NULL == bls->blop) 313 goto fail; 314 315 return; 316 317 fail: 318 GNUNET_break (0); 319 TALER_TESTING_interpreter_fail (is); 320 } 321 322 323 /** 324 * Free the state of a "block list" CMD, and possibly 325 * cancel it if it did not complete. 326 * 327 * @param cls closure. 328 * @param cmd command being freed. 329 */ 330 static void 331 block_list_cleanup (void *cls, 332 const struct TALER_TESTING_Command *cmd) 333 { 334 struct BlockListState *bls = cls; 335 336 if (NULL != bls->blop) 337 { 338 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 339 "Command '%s' did not complete (block list)\n", 340 cmd->label); 341 SYNC_block_list_cancel (bls->blop); 342 bls->blop = NULL; 343 } 344 GNUNET_free (bls); 345 } 346 347 348 /** 349 * Offer internal data to other commands. 350 * 351 * @param cls closure 352 * @param[out] ret result (could be anything) 353 * @param trait name of the trait 354 * @param index index number of the object to extract. 355 * @return #GNUNET_OK on success 356 */ 357 static enum GNUNET_GenericReturnValue 358 block_list_traits (void *cls, 359 const void **ret, 360 const char *trait, 361 unsigned int index) 362 { 363 struct BlockListState *bls = cls; 364 struct TALER_TESTING_Trait traits[] = { 365 TALER_TESTING_make_trait_block_list_result (&bls->details), 366 TALER_TESTING_trait_end () 367 }; 368 const struct TALER_TESTING_Trait *t; 369 enum GNUNET_GenericReturnValue res; 370 371 res = TALER_TESTING_get_trait (traits, 372 ret, 373 trait, 374 index); 375 if (GNUNET_OK == res) 376 return res; 377 if (0 < bls->details.num_entries) 378 { 379 /* Expose the last entry's signed context to the block write 380 commands (needed for relink signatures). */ 381 const struct SYNC_BlockListEntry *last = 382 &bls->details.entries[bls->details.num_entries - 1]; 383 struct TALER_TESTING_Trait st[] = { 384 TALER_TESTING_make_trait_block_nonce (&last->nonce), 385 TALER_TESTING_make_trait_prev_nonce (&last->prev_nonce), 386 TALER_TESTING_make_trait_next_nonce (&last->next_nonce), 387 TALER_TESTING_make_trait_block_refs_hash (&last->refs_hash), 388 TALER_TESTING_trait_end () 389 }; 390 391 return TALER_TESTING_get_trait (st, 392 ret, 393 trait, 394 index); 395 } 396 return res; 397 } 398 399 400 struct TALER_TESTING_Command 401 SYNC_TESTING_cmd_block_list (const char *label, 402 const char *sync_url, 403 const char *account_reference, 404 int16_t limit, 405 const struct SYNC_BlockNonce *start_nonce, 406 unsigned int http_status, 407 unsigned int num_expected, 408 const struct SYNC_BlockNonce *expected_nonces, 409 const struct GNUNET_HashCode *expected_hashes) 410 { 411 struct BlockListState *bls; 412 413 GNUNET_assert (num_expected <= MAX_BLOCK_LIST_ENTRIES); 414 bls = GNUNET_new (struct BlockListState); 415 bls->sync_url = sync_url; 416 bls->account_reference = account_reference; 417 bls->limit = limit; 418 if (NULL != start_nonce) 419 { 420 bls->start_nonce = *start_nonce; 421 bls->have_start_nonce = true; 422 } 423 bls->http_status = http_status; 424 bls->num_expected = num_expected; 425 if (0 < num_expected) 426 { 427 GNUNET_memcpy (bls->expected_nonces, 428 expected_nonces, 429 num_expected * sizeof (struct SYNC_BlockNonce)); 430 GNUNET_memcpy (bls->expected_hashes, 431 expected_hashes, 432 num_expected * sizeof (struct GNUNET_HashCode)); 433 } 434 { 435 struct TALER_TESTING_Command cmd = { 436 .cls = bls, 437 .label = label, 438 .run = &block_list_run, 439 .cleanup = &block_list_cleanup, 440 .traits = &block_list_traits 441 }; 442 443 return cmd; 444 } 445 }