sync

Backup service to store encrypted wallet databases (experimental)
Log | Files | Refs | Submodules | README | LICENSE

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 }