LCOV - code coverage report
Current view: top level - util - test_crypto.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 86.0 % 172 148
Test Date: 2026-09-11 18:55:36 Functions: 100.0 % 9 9

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (C) 2015, 2020-2024 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              : /**
      18              :  * @file util/test_crypto.c
      19              :  * @brief Tests for Taler-specific crypto logic
      20              :  * @author Christian Grothoff <christian@grothoff.org>
      21              :  */
      22              : #include "taler/taler_util.h"
      23              : 
      24              : 
      25              : /**
      26              :  * Test high-level link encryption/decryption API.
      27              :  *
      28              :  * @return 0 on success
      29              :  */
      30              : static int
      31            1 : test_high_level (void)
      32              : {
      33              :   struct TALER_CoinSpendPrivateKeyP coin_priv;
      34              :   struct TALER_CoinSpendPublicKeyP coin_pub;
      35              :   struct TALER_TransferPrivateKeyP trans_priv;
      36              :   struct TALER_TransferPublicKeyP trans_pub;
      37              :   struct TALER_TransferSecretP secret;
      38              :   struct TALER_TransferSecretP secret2;
      39              :   union GNUNET_CRYPTO_BlindingSecretP bks1;
      40              :   union GNUNET_CRYPTO_BlindingSecretP bks2;
      41              :   struct TALER_CoinSpendPrivateKeyP coin_priv1;
      42              :   struct TALER_CoinSpendPrivateKeyP coin_priv2;
      43              :   struct TALER_PlanchetMasterSecretP ps1;
      44              :   struct TALER_PlanchetMasterSecretP ps2;
      45            1 :   struct GNUNET_CRYPTO_BlindingInputValues bi = {
      46              :     .cipher = GNUNET_CRYPTO_BSA_RSA
      47              :   };
      48            1 :   struct TALER_ExchangeBlindingValues alg1 = {
      49              :     .blinding_inputs = &bi
      50              :   };
      51            1 :   struct TALER_ExchangeBlindingValues alg2 = {
      52              :     .blinding_inputs = &bi
      53              :   };
      54              : 
      55            1 :   GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv);
      56            1 :   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
      57              :                                       &coin_pub.eddsa_pub);
      58            1 :   GNUNET_CRYPTO_ecdhe_key_create (&trans_priv.ecdhe_priv);
      59            1 :   GNUNET_CRYPTO_ecdhe_key_get_public (&trans_priv.ecdhe_priv,
      60              :                                       &trans_pub.ecdhe_pub);
      61            1 :   TALER_link_derive_transfer_secret (&coin_priv,
      62              :                                      &trans_priv,
      63              :                                      &secret);
      64            1 :   TALER_link_reveal_transfer_secret (&trans_priv,
      65              :                                      &coin_pub,
      66              :                                      &secret2);
      67            1 :   GNUNET_assert (0 ==
      68              :                  GNUNET_memcmp (&secret,
      69              :                                 &secret2));
      70            1 :   TALER_link_recover_transfer_secret (&trans_pub,
      71              :                                       &coin_priv,
      72              :                                       &secret2);
      73            1 :   GNUNET_assert (0 ==
      74              :                  GNUNET_memcmp (&secret,
      75              :                                 &secret2));
      76            1 :   TALER_transfer_secret_to_planchet_secret (&secret,
      77              :                                             0,
      78              :                                             &ps1);
      79            1 :   TALER_planchet_setup_coin_priv (&ps1,
      80              :                                   &alg1,
      81              :                                   &coin_priv1);
      82            1 :   TALER_planchet_blinding_secret_create (&ps1,
      83              :                                          &alg1,
      84              :                                          &bks1);
      85            1 :   TALER_transfer_secret_to_planchet_secret (&secret,
      86              :                                             1,
      87              :                                             &ps2);
      88            1 :   TALER_planchet_setup_coin_priv (&ps2,
      89              :                                   &alg2,
      90              :                                   &coin_priv2);
      91            1 :   TALER_planchet_blinding_secret_create (&ps2,
      92              :                                          &alg2,
      93              :                                          &bks2);
      94            1 :   GNUNET_assert (0 !=
      95              :                  GNUNET_memcmp (&ps1,
      96              :                                 &ps2));
      97            1 :   GNUNET_assert (0 !=
      98              :                  GNUNET_memcmp (&coin_priv1,
      99              :                                 &coin_priv2));
     100            1 :   GNUNET_assert (0 !=
     101              :                  GNUNET_memcmp (&bks1,
     102              :                                 &bks2));
     103            1 :   return 0;
     104              : }
     105              : 
     106              : 
     107              : static struct TALER_AgeMask age_mask = {
     108              :   .bits = 1 | 1 << 8 | 1 << 10 | 1 << 12
     109              :           | 1 << 14 | 1 << 16 | 1 << 18 | 1 << 21
     110              : };
     111              : 
     112              : /**
     113              :  * Test the basic planchet functionality of creating a fresh planchet
     114              :  * and extracting the respective signature.
     115              :  *
     116              :  * @return 0 on success
     117              :  */
     118              : static int
     119            2 : test_planchets_rsa (uint8_t age)
     120              : {
     121              :   struct TALER_PlanchetMasterSecretP ps;
     122              :   struct TALER_CoinSpendPrivateKeyP coin_priv;
     123              :   union GNUNET_CRYPTO_BlindingSecretP bks;
     124              :   struct TALER_DenominationPrivateKey dk_priv;
     125              :   struct TALER_DenominationPublicKey dk_pub;
     126              :   const struct TALER_ExchangeBlindingValues *alg_values;
     127              :   struct TALER_PlanchetDetail pd;
     128              :   struct TALER_BlindedDenominationSignature blind_sig;
     129              :   struct TALER_FreshCoin coin;
     130              :   struct TALER_CoinPubHashP c_hash;
     131            2 :   struct TALER_AgeCommitmentHashP *ach = NULL;
     132            2 :   struct TALER_AgeCommitmentHashP ah = {0};
     133              : 
     134            2 :   alg_values = TALER_denom_ewv_rsa_singleton ();
     135            2 :   if (0 < age)
     136              :   {
     137              :     struct TALER_AgeCommitmentProof acp;
     138              :     struct GNUNET_HashCode seed;
     139              : 
     140            1 :     GNUNET_CRYPTO_random_block (&seed,
     141              :                                 sizeof(seed));
     142            1 :     TALER_age_restriction_commit (&age_mask,
     143              :                                   age,
     144              :                                   &seed,
     145              :                                   &acp);
     146            1 :     TALER_age_commitment_hash (&acp.commitment,
     147              :                                &ah);
     148            1 :     ach = &ah;
     149            1 :     TALER_age_commitment_proof_free (&acp);
     150              :   }
     151              : 
     152            2 :   GNUNET_CRYPTO_random_block (&ps,
     153              :                               sizeof (ps));
     154            2 :   GNUNET_log_skip (1, GNUNET_YES);
     155            2 :   GNUNET_assert (GNUNET_SYSERR ==
     156              :                  TALER_denom_priv_create (&dk_priv,
     157              :                                           &dk_pub,
     158              :                                           GNUNET_CRYPTO_BSA_INVALID));
     159            2 :   GNUNET_log_skip (1, GNUNET_YES);
     160            2 :   GNUNET_assert (GNUNET_SYSERR ==
     161              :                  TALER_denom_priv_create (&dk_priv,
     162              :                                           &dk_pub,
     163              :                                           42));
     164              : 
     165            2 :   GNUNET_assert (GNUNET_OK ==
     166              :                  TALER_denom_priv_create (&dk_priv,
     167              :                                           &dk_pub,
     168              :                                           GNUNET_CRYPTO_BSA_RSA,
     169              :                                           1024));
     170            2 :   TALER_planchet_setup_coin_priv (&ps,
     171              :                                   alg_values,
     172              :                                   &coin_priv);
     173            2 :   TALER_planchet_blinding_secret_create (&ps,
     174              :                                          alg_values,
     175              :                                          &bks);
     176            2 :   GNUNET_assert (GNUNET_OK ==
     177              :                  TALER_planchet_prepare (&dk_pub,
     178              :                                          alg_values,
     179              :                                          &bks,
     180              :                                          NULL,
     181              :                                          &coin_priv,
     182              :                                          ach,
     183              :                                          &c_hash,
     184              :                                          &pd));
     185            2 :   GNUNET_assert (GNUNET_OK ==
     186              :                  TALER_denom_sign_blinded (&blind_sig,
     187              :                                            &dk_priv,
     188              :                                            false,
     189              :                                            &pd.blinded_planchet));
     190            2 :   TALER_planchet_detail_free (&pd);
     191            2 :   GNUNET_assert (GNUNET_OK ==
     192              :                  TALER_planchet_to_coin (&dk_pub,
     193              :                                          &blind_sig,
     194              :                                          &bks,
     195              :                                          &coin_priv,
     196              :                                          ach,
     197              :                                          &c_hash,
     198              :                                          alg_values,
     199              :                                          &coin));
     200            2 :   TALER_blinded_denom_sig_free (&blind_sig);
     201            2 :   TALER_denom_sig_free (&coin.sig);
     202            2 :   TALER_denom_priv_free (&dk_priv);
     203            2 :   TALER_denom_pub_free (&dk_pub);
     204            2 :   return 0;
     205              : }
     206              : 
     207              : 
     208              : /**
     209              :  * Test the basic planchet functionality of creating a fresh planchet with CS denomination
     210              :  * and extracting the respective signature.
     211              :  *
     212              :  * @return 0 on success
     213              :  */
     214              : static int
     215            2 : test_planchets_cs (uint8_t age)
     216              : {
     217              :   struct TALER_WithdrawMasterSeedP seed;
     218              :   struct TALER_BlindingMasterSeedP blinding_seed;
     219              :   struct TALER_PlanchetMasterSecretP ps;
     220              :   struct TALER_CoinSpendPrivateKeyP coin_priv;
     221              :   union GNUNET_CRYPTO_BlindingSecretP bks;
     222              :   struct TALER_DenominationPrivateKey dk_priv;
     223              :   struct TALER_DenominationPublicKey dk_pub;
     224              :   struct TALER_PlanchetDetail pd;
     225              :   struct TALER_CoinPubHashP c_hash;
     226              :   union GNUNET_CRYPTO_BlindSessionNonce nonce;
     227              :   struct TALER_BlindedDenominationSignature blind_sig;
     228              :   struct TALER_FreshCoin coin;
     229              :   struct TALER_ExchangeBlindingValues alg_values;
     230            2 :   struct TALER_AgeCommitmentHashP *ach = NULL;
     231            2 :   struct TALER_AgeCommitmentHashP ah = {0};
     232            2 :   const uint32_t coin_offset = 0;
     233              : 
     234            2 :   if (0 < age)
     235              :   {
     236              :     struct TALER_AgeCommitmentProof acp;
     237              :     struct GNUNET_HashCode seed;
     238              : 
     239            1 :     GNUNET_CRYPTO_random_block (&seed,
     240              :                                 sizeof(seed));
     241            1 :     TALER_age_restriction_commit (&age_mask,
     242              :                                   age,
     243              :                                   &seed,
     244              :                                   &acp);
     245            1 :     TALER_age_commitment_hash (&acp.commitment,
     246              :                                &ah);
     247            1 :     ach = &ah;
     248            1 :     TALER_age_commitment_proof_free (&acp);
     249              :   }
     250              : 
     251            2 :   TALER_withdraw_master_seed_setup_random (&seed);
     252            2 :   TALER_withdraw_expand_secrets (1,
     253              :                                  &seed,
     254              :                                  &ps);
     255            2 :   TALER_cs_withdraw_seed_to_blinding_seed (&seed,
     256              :                                            &blinding_seed);
     257            2 :   GNUNET_assert (GNUNET_OK ==
     258              :                  TALER_denom_priv_create (&dk_priv,
     259              :                                           &dk_pub,
     260              :                                           GNUNET_CRYPTO_BSA_CS));
     261            2 :   TALER_cs_derive_only_cs_blind_nonces_from_seed (
     262              :     &blinding_seed,
     263              :     false,
     264              :     1,
     265              :     &coin_offset,
     266              :     &nonce);
     267              :   // FIXME: define Taler abstraction for this:
     268              :   alg_values.blinding_inputs
     269            2 :     = GNUNET_CRYPTO_get_blinding_input_values (dk_priv.bsign_priv_key,
     270              :                                                &nonce,
     271              :                                                "rw");
     272            2 :   TALER_denom_pub_hash (&dk_pub,
     273              :                         &pd.denom_pub_hash);
     274            2 :   TALER_planchet_setup_coin_priv (&ps,
     275              :                                   &alg_values,
     276              :                                   &coin_priv);
     277            2 :   TALER_planchet_blinding_secret_create (&ps,
     278              :                                          &alg_values,
     279              :                                          &bks);
     280            2 :   GNUNET_assert (GNUNET_OK ==
     281              :                  TALER_planchet_prepare (&dk_pub,
     282              :                                          &alg_values,
     283              :                                          &bks,
     284              :                                          &nonce,
     285              :                                          &coin_priv,
     286              :                                          ach,
     287              :                                          &c_hash,
     288              :                                          &pd));
     289            2 :   GNUNET_assert (GNUNET_OK ==
     290              :                  TALER_denom_sign_blinded (&blind_sig,
     291              :                                            &dk_priv,
     292              :                                            false,
     293              :                                            &pd.blinded_planchet));
     294            2 :   GNUNET_assert (GNUNET_OK ==
     295              :                  TALER_planchet_to_coin (&dk_pub,
     296              :                                          &blind_sig,
     297              :                                          &bks,
     298              :                                          &coin_priv,
     299              :                                          ach,
     300              :                                          &c_hash,
     301              :                                          &alg_values,
     302              :                                          &coin));
     303            2 :   TALER_blinded_denom_sig_free (&blind_sig);
     304            2 :   TALER_denom_sig_free (&coin.sig);
     305            2 :   TALER_denom_priv_free (&dk_priv);
     306            2 :   TALER_denom_pub_free (&dk_pub);
     307            2 :   return 0;
     308              : }
     309              : 
     310              : 
     311              : /**
     312              :  * Test the basic planchet functionality of creating a fresh planchet
     313              :  * and extracting the respective signature.
     314              :  * Calls test_planchets_rsa and test_planchets_cs
     315              :  *
     316              :  * @return 0 on success
     317              :  */
     318              : static int
     319            2 : test_planchets (uint8_t age)
     320              : {
     321            2 :   if (0 != test_planchets_rsa (age))
     322            0 :     return -1;
     323            2 :   return test_planchets_cs (age);
     324              : }
     325              : 
     326              : 
     327              : static int
     328            1 : test_exchange_sigs (void)
     329              : {
     330            1 :   const struct TALER_FullPayto pt = {
     331              :     .full_payto
     332              :       = (char *) "payto://x-taler-bank/localhost/Account?receiver-name=ACC"
     333              :   };
     334            1 :   const struct TALER_FullPayto pto = {
     335              :     .full_payto
     336              :       = (char *) "payto://x-taler-bank/localhost/Other?receiver-name=OTH"
     337              :   };
     338              :   struct TALER_MasterPrivateKeyP priv;
     339              :   struct TALER_MasterPublicKeyP pub;
     340              :   struct TALER_MasterSignatureP sig;
     341              :   json_t *rest;
     342              : 
     343            1 :   GNUNET_CRYPTO_eddsa_key_create (&priv.eddsa_priv);
     344            1 :   rest = json_array ();
     345            1 :   GNUNET_assert (NULL != rest);
     346            1 :   TALER_exchange_wire_signature_make (pt,
     347              :                                       NULL,
     348              :                                       "https://example.com/",
     349              :                                       NULL,
     350              :                                       rest,
     351              :                                       rest,
     352              :                                       &priv,
     353              :                                       &sig);
     354            1 :   GNUNET_CRYPTO_eddsa_key_get_public (&priv.eddsa_priv,
     355              :                                       &pub.eddsa_pub);
     356            1 :   if (GNUNET_OK !=
     357            1 :       TALER_exchange_wire_signature_check (pt,
     358              :                                            NULL,
     359              :                                            "https://example.com/",
     360              :                                            NULL,
     361              :                                            rest,
     362              :                                            rest,
     363              :                                            &pub,
     364              :                                            &sig))
     365              :   {
     366            0 :     GNUNET_break (0);
     367            0 :     return 1;
     368              :   }
     369            1 :   if (GNUNET_OK ==
     370            1 :       TALER_exchange_wire_signature_check (
     371              :         pto,
     372              :         NULL,
     373              :         "https://example.com/",
     374              :         NULL,
     375              :         rest,
     376              :         rest,
     377              :         &pub,
     378              :         &sig))
     379              :   {
     380            0 :     GNUNET_break (0);
     381            0 :     return 1;
     382              :   }
     383            1 :   if (GNUNET_OK ==
     384            1 :       TALER_exchange_wire_signature_check (
     385              :         pt,
     386              :         "http://example.com/",
     387              :         NULL,
     388              :         NULL,
     389              :         rest,
     390              :         rest,
     391              :         &pub,
     392              :         &sig))
     393              :   {
     394            0 :     GNUNET_break (0);
     395            0 :     return 1;
     396              :   }
     397            1 :   json_decref (rest);
     398            1 :   return 0;
     399              : }
     400              : 
     401              : 
     402              : static int
     403            1 : test_merchant_sigs (void)
     404              : {
     405            1 :   const struct TALER_FullPayto pt = {
     406              :     .full_payto
     407              :       = (char *) "payto://x-taler-bank/localhost/Account?receiver-name=ACC"
     408              :   };
     409            1 :   const struct TALER_FullPayto pto = {
     410              :     .full_payto
     411              :       = (char *) "payto://x-taler-bank/localhost/Other?receiver-name=OTH"
     412              :   };
     413              :   struct TALER_WireSaltP salt;
     414              :   struct TALER_MerchantPrivateKeyP priv;
     415              :   struct TALER_MerchantPublicKeyP pub;
     416              :   struct TALER_MerchantSignatureP sig;
     417              : 
     418            1 :   GNUNET_CRYPTO_eddsa_key_create (&priv.eddsa_priv);
     419            1 :   memset (&salt,
     420              :           42,
     421              :           sizeof (salt));
     422            1 :   TALER_merchant_wire_signature_make (pt,
     423              :                                       &salt,
     424              :                                       &priv,
     425              :                                       &sig);
     426            1 :   GNUNET_CRYPTO_eddsa_key_get_public (&priv.eddsa_priv,
     427              :                                       &pub.eddsa_pub);
     428            1 :   if (GNUNET_OK !=
     429            1 :       TALER_merchant_wire_signature_check (pt,
     430              :                                            &salt,
     431              :                                            &pub,
     432              :                                            &sig))
     433              :   {
     434            0 :     GNUNET_break (0);
     435            0 :     return 1;
     436              :   }
     437            1 :   if (GNUNET_OK ==
     438            1 :       TALER_merchant_wire_signature_check (
     439              :         pto,
     440              :         &salt,
     441              :         &pub,
     442              :         &sig))
     443              :   {
     444            0 :     GNUNET_break (0);
     445            0 :     return 1;
     446              :   }
     447            1 :   memset (&salt,
     448              :           43,
     449              :           sizeof (salt));
     450            1 :   if (GNUNET_OK ==
     451            1 :       TALER_merchant_wire_signature_check (pt,
     452              :                                            &salt,
     453              :                                            &pub,
     454              :                                            &sig))
     455              :   {
     456            0 :     GNUNET_break (0);
     457            0 :     return 1;
     458              :   }
     459            1 :   return 0;
     460              : }
     461              : 
     462              : 
     463              : static int
     464            1 : test_contracts (void)
     465              : {
     466              :   struct TALER_ContractDiffiePrivateP cpriv;
     467              :   struct TALER_PurseContractPublicKeyP purse_pub;
     468              :   struct TALER_PurseContractPrivateKeyP purse_priv;
     469              :   void *econtract;
     470              :   size_t econtract_size;
     471              :   struct TALER_PurseMergePrivateKeyP mpriv_in;
     472              :   struct TALER_PurseMergePrivateKeyP mpriv_out;
     473              :   json_t *c;
     474              : 
     475            1 :   GNUNET_CRYPTO_ecdhe_key_create (&cpriv.ecdhe_priv);
     476            1 :   GNUNET_CRYPTO_eddsa_key_create (&purse_priv.eddsa_priv);
     477            1 :   GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv.eddsa_priv,
     478              :                                       &purse_pub.eddsa_pub);
     479            1 :   memset (&mpriv_in,
     480              :           42,
     481              :           sizeof (mpriv_in));
     482            1 :   c = json_pack ("{s:s}", "test", "value");
     483            1 :   GNUNET_assert (NULL != c);
     484            1 :   TALER_CRYPTO_contract_encrypt_for_merge (&purse_pub,
     485              :                                            &cpriv,
     486              :                                            &mpriv_in,
     487              :                                            c,
     488              :                                            &econtract,
     489              :                                            &econtract_size);
     490            1 :   json_decref (c);
     491            1 :   c = TALER_CRYPTO_contract_decrypt_for_merge (&cpriv,
     492              :                                                &purse_pub,
     493              :                                                econtract,
     494              :                                                econtract_size,
     495              :                                                &mpriv_out);
     496            1 :   GNUNET_free (econtract);
     497            1 :   if (NULL == c)
     498            0 :     return 1;
     499            1 :   json_decref (c);
     500            1 :   if (0 != GNUNET_memcmp (&mpriv_in,
     501              :                           &mpriv_out))
     502            0 :     return 1;
     503            1 :   return 0;
     504              : }
     505              : 
     506              : 
     507              : static int
     508            1 : test_attributes (void)
     509              : {
     510              :   struct TALER_AttributeEncryptionKeyP key;
     511              :   void *eattr;
     512              :   size_t eattr_size;
     513              :   json_t *c;
     514              : 
     515            1 :   GNUNET_CRYPTO_random_block (&key,
     516              :                               sizeof (key));
     517            1 :   c = json_pack ("{s:s}", "test", "value");
     518            1 :   GNUNET_assert (NULL != c);
     519            1 :   TALER_CRYPTO_kyc_attributes_encrypt (&key,
     520              :                                        c,
     521              :                                        &eattr,
     522              :                                        &eattr_size);
     523            1 :   json_decref (c);
     524            1 :   c = TALER_CRYPTO_kyc_attributes_decrypt (&key,
     525              :                                            eattr,
     526              :                                            eattr_size);
     527            1 :   GNUNET_free (eattr);
     528            1 :   if (NULL == c)
     529              :   {
     530            0 :     GNUNET_break (0);
     531            0 :     return 1;
     532              :   }
     533            1 :   GNUNET_assert (0 ==
     534              :                  strcmp ("value",
     535              :                          json_string_value (json_object_get (c,
     536              :                                                              "test"))));
     537            1 :   json_decref (c);
     538            1 :   return 0;
     539              : }
     540              : 
     541              : 
     542              : int
     543            1 : main (int argc,
     544              :       const char *const argv[])
     545              : {
     546              :   (void) argc;
     547              :   (void) argv;
     548            1 :   GNUNET_log_setup ("test-crypto",
     549              :                     "WARNING",
     550              :                     NULL);
     551            1 :   if (0 != test_high_level ())
     552            0 :     return 1;
     553            1 :   if (0 != test_planchets (0))
     554            0 :     return 2;
     555            1 :   if (0 != test_planchets (13))
     556            0 :     return 3;
     557            1 :   if (0 != test_exchange_sigs ())
     558            0 :     return 4;
     559            1 :   if (0 != test_merchant_sigs ())
     560            0 :     return 5;
     561            1 :   if (0 != test_contracts ())
     562            0 :     return 6;
     563            1 :   if (0 != test_attributes ())
     564            0 :     return 7;
     565            1 :   return 0;
     566              : }
     567              : 
     568              : 
     569              : /* end of test_crypto.c */
        

Generated by: LCOV version 2.0-1