sync

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

testing_api_cmd_block_reconcile.c (13973B)


      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_reconcile.c
     21  * @brief command to fetch and verify the invertible bloom filter of an
     22  *        account from the sync backend service (DD 92)
     23  * @author Iván Ávalos
     24  */
     25 #include "platform.h"
     26 #include "sync/sync_service.h"
     27 #include "sync/sync_signatures.h"
     28 #include "sync/sync_ibf.h"
     29 #include "sync/sync_testing_lib.h"
     30 #include <taler/taler_util.h>
     31 #include <taler/taler_testing_lib.h>
     32 #include <gnunet/gnunet_curl_lib.h>
     33 
     34 
     35 /**
     36  * Maximum number of entries in the local set and in the expected
     37  * differences.
     38  */
     39 #define MAX_RECONCILE_ENTRIES 256
     40 
     41 
     42 /**
     43  * State for a "block reconcile" CMD.
     44  */
     45 struct BlockReconcileState
     46 {
     47 
     48   /**
     49    * URL of the sync backend.
     50    */
     51   const char *sync_url;
     52 
     53   /**
     54    * Reference to a command that provides the account public key.
     55    */
     56   const char *account_reference;
     57 
     58   /**
     59    * Account public key to query.
     60    */
     61   struct SYNC_AccountPublicKeyP account_pub;
     62 
     63   /**
     64    * Optional mixing salt to request (NULL for none).
     65    */
     66   uint32_t *prefix;
     67 
     68   /**
     69    * Expected HTTP status code.
     70    */
     71   unsigned int http_status;
     72 
     73   /**
     74    * Expected number of blocks the filter must report.
     75    */
     76   uint32_t expected_total_blocks;
     77 
     78   /**
     79    * The interpreter state.
     80    */
     81   struct TALER_TESTING_Interpreter *is;
     82 
     83   /**
     84    * Handle for the library block reconcile operation.
     85    */
     86   struct SYNC_ReconcileOperation *ro;
     87 
     88   /**
     89    * Number of blocks the wallet holds locally.
     90    */
     91   unsigned int num_local;
     92 
     93   /**
     94    * Nonces of the locally held blocks (array of @e num_local).
     95    */
     96   struct SYNC_BlockNonce local_nonces[MAX_RECONCILE_ENTRIES];
     97 
     98   /**
     99    * Hashes of the locally held blocks (array of @e num_local).
    100    */
    101   struct GNUNET_HashCode local_hashes[MAX_RECONCILE_ENTRIES];
    102 
    103   /**
    104    * Number of blocks expected to be missing locally (server-only).
    105    */
    106   unsigned int num_missing;
    107 
    108   /**
    109    * Nonces of the blocks expected missing (array of @e num_missing).
    110    */
    111   struct SYNC_BlockNonce missing_nonces[MAX_RECONCILE_ENTRIES];
    112 
    113   /**
    114    * Hashes of the blocks expected missing (array of @e num_missing).
    115    */
    116   struct GNUNET_HashCode missing_hashes[MAX_RECONCILE_ENTRIES];
    117 
    118   /**
    119    * Number of blocks expected to be stale locally (local-only).
    120    */
    121   unsigned int num_stale;
    122 
    123   /**
    124    * Nonces of the blocks expected stale (array of @e num_stale).
    125    */
    126   struct SYNC_BlockNonce stale_nonces[MAX_RECONCILE_ENTRIES];
    127 
    128   /**
    129    * Hashes of the blocks expected stale (array of @e num_stale).
    130    */
    131   struct GNUNET_HashCode stale_hashes[MAX_RECONCILE_ENTRIES];
    132 
    133 };
    134 
    135 
    136 /**
    137  * Look up a decoded element in the expected arrays.
    138  *
    139  * @param key element to find (nonce || hash)
    140  * @param num number of entries in @a nonces and @a hashes
    141  * @param nonces expected nonces
    142  * @param hashes expected hashes
    143  * @param[out] used set to true when found (for duplicate checks)
    144  * @return #GNUNET_YES if found, #GNUNET_NO if not
    145  */
    146 static enum GNUNET_GenericReturnValue
    147 match_expected (const struct SYNC_IBFKey *key,
    148                 unsigned int num,
    149                 const struct SYNC_BlockNonce *nonces,
    150                 const struct GNUNET_HashCode *hashes,
    151                 bool *used)
    152 {
    153   for (unsigned int i = 0; i < num; i++)
    154   {
    155     if (used[i])
    156       continue;
    157     if ( (0 == memcmp (key->nonce,
    158                        &nonces[i],
    159                        sizeof (key->nonce))) &&
    160          (0 == memcmp (key->hash,
    161                        &hashes[i],
    162                        sizeof (key->hash))) )
    163     {
    164       used[i] = true;
    165       return GNUNET_YES;
    166     }
    167   }
    168   return GNUNET_NO;
    169 }
    170 
    171 
    172 /**
    173  * Function called when we're done processing the block reconcile.
    174  *
    175  * @param cls the `struct BlockReconcileState`
    176  * @param rd details about the reconcile operation
    177  */
    178 static void
    179 handle_reconcile_finished (void *cls,
    180                            const struct SYNC_ReconcileDetails *rd)
    181 {
    182   struct BlockReconcileState *brs = cls;
    183   struct SYNC_IBF *server_ibf;
    184   struct SYNC_IBF *local_ibf;
    185   bool used_missing[MAX_RECONCILE_ENTRIES] = { false };
    186   bool used_stale[MAX_RECONCILE_ENTRIES] = { false };
    187 
    188   brs->ro = NULL;
    189   if (rd->http_status != brs->http_status)
    190   {
    191     TALER_TESTING_unexpected_status (brs->is,
    192                                      rd->http_status,
    193                                      brs->http_status);
    194     return;
    195   }
    196   if (MHD_HTTP_OK != rd->http_status)
    197   {
    198     /* error case: nothing more to check */
    199     TALER_TESTING_interpreter_next (brs->is);
    200     return;
    201   }
    202 
    203   if (rd->total_blocks != brs->expected_total_blocks)
    204   {
    205     GNUNET_break_op (0);
    206     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    207                 "Expected %u blocks in reconcile, got %u\n",
    208                 (unsigned int) brs->expected_total_blocks,
    209                 (unsigned int) rd->total_blocks);
    210     TALER_TESTING_interpreter_fail (brs->is);
    211     return;
    212   }
    213   if ( (NULL != brs->prefix) &&
    214        (*brs->prefix != rd->prefix) )
    215   {
    216     GNUNET_break_op (0);
    217     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    218                 "Reconcile used prefix %u, expected %u\n",
    219                 (unsigned int) rd->prefix,
    220                 (unsigned int) *brs->prefix);
    221     TALER_TESTING_interpreter_fail (brs->is);
    222     return;
    223   }
    224 
    225   server_ibf = SYNC_ibf_deserialize (rd->data,
    226                                      rd->bucket_count,
    227                                      rd->k,
    228                                      rd->prefix);
    229   if (NULL == server_ibf)
    230   {
    231     GNUNET_break_op (0);
    232     TALER_TESTING_interpreter_fail (brs->is);
    233     return;
    234   }
    235   /* Rebuild the wallet's side of the reconciliation, as it would: the
    236      local filter over the blocks it holds, subtracted from the
    237      server's, enumerates exactly the difference. */
    238   local_ibf = SYNC_ibf_create (rd->bucket_count,
    239                                rd->k,
    240                                rd->prefix);
    241   if (NULL == local_ibf)
    242   {
    243     GNUNET_break_op (0);
    244     SYNC_ibf_destroy (server_ibf);
    245     TALER_TESTING_interpreter_fail (brs->is);
    246     return;
    247   }
    248   for (unsigned int i = 0; i < brs->num_local; i++)
    249   {
    250     struct SYNC_IBFKey key;
    251 
    252     memcpy (key.nonce,
    253             &brs->local_nonces[i],
    254             sizeof (key.nonce));
    255     memcpy (key.hash,
    256             &brs->local_hashes[i],
    257             sizeof (key.hash));
    258     SYNC_ibf_insert (local_ibf,
    259                      &key);
    260   }
    261   SYNC_ibf_subtract (server_ibf,
    262                      local_ibf);
    263   {
    264     bool mismatch = false;
    265     unsigned int got_missing = 0;
    266     unsigned int got_stale = 0;
    267 
    268     for (;;)
    269     {
    270       enum GNUNET_GenericReturnValue res;
    271       struct SYNC_IBFKey key;
    272       int16_t side;
    273 
    274       res = SYNC_ibf_decode (server_ibf,
    275                              &side,
    276                              &key);
    277       if (GNUNET_YES != res)
    278       {
    279         if (GNUNET_NO != res)
    280         {
    281           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    282                       "Reconcile filter cannot be decoded (difference"
    283                       " too large for its size)\n");
    284           mismatch = true;
    285         }
    286         break;
    287       }
    288       if (1 == side)
    289       {
    290         if (GNUNET_YES !=
    291             match_expected (&key,
    292                             brs->num_missing,
    293                             brs->missing_nonces,
    294                             brs->missing_hashes,
    295                             used_missing))
    296         {
    297           GNUNET_break_op (0);
    298           mismatch = true;
    299           break;
    300         }
    301         got_missing++;
    302       }
    303       else
    304       {
    305         if (GNUNET_YES !=
    306             match_expected (&key,
    307                             brs->num_stale,
    308                             brs->stale_nonces,
    309                             brs->stale_hashes,
    310                             used_stale))
    311         {
    312           GNUNET_break_op (0);
    313           mismatch = true;
    314           break;
    315         }
    316         got_stale++;
    317       }
    318     }
    319     if ( (! mismatch) &&
    320          (got_missing != brs->num_missing) )
    321     {
    322       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    323                   "Reconcile decoded %u missing blocks, expected %u\n",
    324                   got_missing,
    325                   brs->num_missing);
    326       mismatch = true;
    327     }
    328     if ( (! mismatch) &&
    329          (got_stale != brs->num_stale) )
    330     {
    331       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    332                   "Reconcile decoded %u stale blocks, expected %u\n",
    333                   got_stale,
    334                   brs->num_stale);
    335       mismatch = true;
    336     }
    337     if (mismatch)
    338     {
    339       GNUNET_break_op (0);
    340       TALER_TESTING_interpreter_fail (brs->is);
    341       SYNC_ibf_destroy (local_ibf);
    342       SYNC_ibf_destroy (server_ibf);
    343       return;
    344     }
    345   }
    346   SYNC_ibf_destroy (local_ibf);
    347   SYNC_ibf_destroy (server_ibf);
    348   TALER_TESTING_interpreter_next (brs->is);
    349 }
    350 
    351 
    352 /**
    353  * Run a "block reconcile" CMD.
    354  *
    355  * @param cls closure.
    356  * @param cmd command currently being run.
    357  * @param is interpreter state.
    358  */
    359 static void
    360 reconcile_run (void *cls,
    361                const struct TALER_TESTING_Command *cmd,
    362                struct TALER_TESTING_Interpreter *is)
    363 {
    364   struct BlockReconcileState *brs = cls;
    365 
    366   (void) cmd;
    367   brs->is = is;
    368 
    369   if (NULL != brs->account_reference)
    370   {
    371     const struct TALER_TESTING_Command *ref;
    372 
    373     ref = TALER_TESTING_interpreter_lookup_command (is,
    374                                                     brs->account_reference);
    375     if (NULL == ref)
    376     {
    377       GNUNET_break (0);
    378       goto fail;
    379     }
    380     {
    381       const struct SYNC_AccountPublicKeyP *pub;
    382 
    383       if (GNUNET_OK !=
    384           TALER_TESTING_get_trait_sync_account_pub (ref,
    385                                                     &pub))
    386         goto fail;
    387       brs->account_pub = *pub;
    388     }
    389   }
    390   else
    391     goto fail;
    392 
    393   brs->ro = SYNC_block_reconcile (
    394     TALER_TESTING_interpreter_get_context (is),
    395     brs->sync_url,
    396     &brs->account_pub,
    397     brs->prefix,
    398     &handle_reconcile_finished,
    399     brs);
    400   if (NULL == brs->ro)
    401     goto fail;
    402 
    403   return;
    404 
    405 fail:
    406   GNUNET_break (0);
    407   TALER_TESTING_interpreter_fail (is);
    408 }
    409 
    410 
    411 /**
    412  * Free the state of a "block reconcile" CMD, and possibly
    413  * cancel it if it did not complete.
    414  *
    415  * @param cls closure.
    416  * @param cmd command being freed.
    417  */
    418 static void
    419 reconcile_cleanup (void *cls,
    420                    const struct TALER_TESTING_Command *cmd)
    421 {
    422   struct BlockReconcileState *brs = cls;
    423 
    424   if (NULL != brs->ro)
    425   {
    426     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    427                 "Command '%s' did not complete (block reconcile)\n",
    428                 cmd->label);
    429     SYNC_block_reconcile_cancel (brs->ro);
    430     brs->ro = NULL;
    431   }
    432   GNUNET_free (brs->prefix);
    433   GNUNET_free (brs);
    434 }
    435 
    436 
    437 struct TALER_TESTING_Command
    438 SYNC_TESTING_cmd_block_reconcile (const char *label,
    439                                   const char *sync_url,
    440                                   const char *account_reference,
    441                                   unsigned int http_status,
    442                                   uint32_t expected_total_blocks,
    443                                   const uint32_t *prefix,
    444                                   unsigned int num_local,
    445                                   const struct SYNC_BlockNonce *local_nonces,
    446                                   const struct GNUNET_HashCode *local_hashes,
    447                                   unsigned int num_missing,
    448                                   const struct SYNC_BlockNonce *missing_nonces,
    449                                   const struct GNUNET_HashCode *missing_hashes,
    450                                   unsigned int num_stale,
    451                                   const struct SYNC_BlockNonce *stale_nonces,
    452                                   const struct GNUNET_HashCode *stale_hashes)
    453 {
    454   struct BlockReconcileState *brs;
    455 
    456   GNUNET_assert (num_local <= MAX_RECONCILE_ENTRIES);
    457   GNUNET_assert (num_missing <= MAX_RECONCILE_ENTRIES);
    458   GNUNET_assert (num_stale <= MAX_RECONCILE_ENTRIES);
    459   brs = GNUNET_new (struct BlockReconcileState);
    460   brs->sync_url = sync_url;
    461   brs->account_reference = account_reference;
    462   brs->http_status = http_status;
    463   brs->expected_total_blocks = expected_total_blocks;
    464   if (NULL != prefix)
    465   {
    466     brs->prefix = GNUNET_new (uint32_t);
    467     *brs->prefix = *prefix;
    468   }
    469   else
    470     brs->prefix = NULL;
    471   brs->num_local = num_local;
    472   if (0 < num_local)
    473   {
    474     GNUNET_memcpy (brs->local_nonces,
    475                    local_nonces,
    476                    num_local * sizeof (struct SYNC_BlockNonce));
    477     GNUNET_memcpy (brs->local_hashes,
    478                    local_hashes,
    479                    num_local * sizeof (struct GNUNET_HashCode));
    480   }
    481   brs->num_missing = num_missing;
    482   if (0 < num_missing)
    483   {
    484     GNUNET_memcpy (brs->missing_nonces,
    485                    missing_nonces,
    486                    num_missing * sizeof (struct SYNC_BlockNonce));
    487     GNUNET_memcpy (brs->missing_hashes,
    488                    missing_hashes,
    489                    num_missing * sizeof (struct GNUNET_HashCode));
    490   }
    491   brs->num_stale = num_stale;
    492   if (0 < num_stale)
    493   {
    494     GNUNET_memcpy (brs->stale_nonces,
    495                    stale_nonces,
    496                    num_stale * sizeof (struct SYNC_BlockNonce));
    497     GNUNET_memcpy (brs->stale_hashes,
    498                    stale_hashes,
    499                    num_stale * sizeof (struct GNUNET_HashCode));
    500   }
    501   {
    502     struct TALER_TESTING_Command cmd = {
    503       .cls = brs,
    504       .label = label,
    505       .run = &reconcile_run,
    506       .cleanup = &reconcile_cleanup
    507     };
    508 
    509     return cmd;
    510   }
    511 }
    512 
    513 
    514 /* end of testing_api_cmd_block_reconcile.c */