• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 20579846577

29 Dec 2025 06:24PM UTC coverage: 90.415% (+0.2%) from 90.243%
20579846577

push

github

web-flow
Merge pull request #5167 from randombit/jack/src-size-reductions

Changes to reduce unnecessary inclusions

101523 of 112285 relevant lines covered (90.42%)

12817276.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.03
/src/tests/test_xof.cpp
1
/*
2
* (C) 2023 Jack Lloyd
3
*     2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#if defined(BOTAN_HAS_XOF)
11
   #include <botan/rng.h>
12
   #include <botan/xof.h>
13
   #include <botan/internal/fmt.h>
14
   #include <botan/internal/stl_util.h>
15

16
   #if defined(BOTAN_HAS_CSHAKE_XOF)
17
      // This XOF implementation is not exposed via the library's public interface
18
      // and is therefore not registered in the XOF::create() factory.
19
      #include <botan/internal/cshake_xof.h>
20
   #endif
21

22
   #if defined(BOTAN_HAS_AES_CRYSTALS_XOF)
23
      // This XOF implementation is not exposed via the library's public interface
24
      // and is therefore not registered in the XOF::create() factory.
25
      #include <botan/internal/aes_crystals_xof.h>
26
   #endif
27
#endif
28

29
namespace Botan_Tests {
30

31
#if defined(BOTAN_HAS_XOF)
32

33
class XOF_Tests final : public Text_Based_Test {
×
34
   public:
35
      XOF_Tests() : Text_Based_Test("xof", "In,Out", "Salt,Key,Name") {}
2✔
36

37
      Test::Result run_one_test(const std::string& algo, const VarMap& vars) override {
318✔
38
         const std::vector<uint8_t> in = vars.get_req_bin("In");
318✔
39
         const std::vector<uint8_t> expected = vars.get_req_bin("Out");
318✔
40
         const std::vector<uint8_t> salt = vars.get_opt_bin("Salt");
318✔
41
         const std::vector<uint8_t> key = vars.get_opt_bin("Key");
318✔
42

43
         // used exclusively for cSHAKE
44
         [[maybe_unused]] const std::vector<uint8_t> name = vars.get_opt_bin("Name");
318✔
45

46
         Test::Result result(algo);
636✔
47

48
         const auto providers = [&]() -> std::vector<std::string> {
954✔
49
   #if defined(BOTAN_HAS_CSHAKE_XOF)
50
            if(algo == "cSHAKE-128" || algo == "cSHAKE-256") {
318✔
51
               return {"base"};
8✔
52
            }
53
   #endif
54

55
   #if defined(BOTAN_HAS_AES_CRYSTALS_XOF)
56
            if(algo == "CTR-BE(AES-256)") {
310✔
57
               return {"base"};
27✔
58
            }
59
   #endif
60
            return provider_filter(Botan::XOF::providers(algo));
283✔
61
         }();
318✔
62

63
         if(providers.empty()) {
318✔
64
            result.note_missing("XOF " + algo);
×
65
            return result;
×
66
         }
67

68
         for(const auto& provider_ask : providers) {
636✔
69
            auto xof = [&]() -> std::unique_ptr<Botan::XOF> {
954✔
70
   #if defined(BOTAN_HAS_CSHAKE_XOF)
71
               if(algo == "cSHAKE-128") {
318✔
72
                  return std::make_unique<Botan::cSHAKE_128_XOF>(name);
4✔
73
               }
74
               if(algo == "cSHAKE-256") {
314✔
75
                  return std::make_unique<Botan::cSHAKE_256_XOF>(name);
4✔
76
               }
77
   #endif
78

79
   #if defined(BOTAN_HAS_AES_CRYSTALS_XOF)
80
               if(algo == "CTR-BE(AES-256)") {
310✔
81
                  return std::make_unique<Botan::AES_256_CTR_XOF>();
27✔
82
               }
83
   #endif
84
               return Botan::XOF::create(algo, provider_ask);
283✔
85
            }();
318✔
86

87
            if(!xof) {
318✔
88
               result.test_failure(Botan::fmt("XOF {} supported by {} but not found", algo, provider_ask));
×
89
               continue;
×
90
            }
91

92
            const std::string provider(xof->provider());
318✔
93
            result.test_is_nonempty("provider", provider);
318✔
94
            result.test_eq(provider, xof->name(), algo);
318✔
95

96
            // Some XOFs don't accept input at all. We assume that this stays the same
97
            // after calling `XOF::clear()`.
98
            const auto new_accepts_input = xof->accepts_input();
318✔
99

100
            result.confirm("advertised block size is > 0", xof->block_size() > 0);
636✔
101
            result.test_eq("new object may accept input", xof->accepts_input(), new_accepts_input);
318✔
102

103
            // input and output in bulk
104
            xof->start(salt, key);
318✔
105
            xof->update(in);
318✔
106
            result.test_eq("object may accept input before first output", xof->accepts_input(), new_accepts_input);
318✔
107
            result.test_eq("generated output", xof->output_stdvec(expected.size()), expected);
636✔
108
            result.confirm("object does not accept input after first output", !xof->accepts_input());
636✔
109

110
            // input and output (overwriting existing data)
111
            // -> regression test where the buffer content was inadvertently
112
            //    xor'ed into the state. Fine with zero-buffer not so fine with
113
            //    random buffer -.-
114
            xof->clear();
318✔
115
            xof->start(salt, key);
318✔
116
            xof->update(in);
318✔
117
            auto nonzero_data = rng().random_vec(expected.size());
318✔
118
            xof->output(nonzero_data);
318✔
119
            result.test_eq("generated output (overwriting existing data)", nonzero_data, expected);
636✔
120

121
            // if not necessary, invoking start() should be optional
122
            if(salt.empty() && key.empty()) {
318✔
123
               xof->clear();
285✔
124
               xof->update(in);
285✔
125
               result.test_eq("generated output (w/o start())", xof->output_stdvec(expected.size()), expected);
855✔
126
            }
127

128
            // input again and output bytewise
129
            xof->clear();
318✔
130
            result.test_eq("object might accept input after clear()", xof->accepts_input(), new_accepts_input);
318✔
131
            xof->start(salt, key);
318✔
132
            xof->update(in);
318✔
133

134
            std::vector<uint8_t> singlebyte_out(expected.size());
318✔
135
            for(uint8_t& chr : singlebyte_out) {
12,358✔
136
               chr = xof->output_next_byte();
12,040✔
137
            }
138
            result.test_eq("generated singlebyte output", singlebyte_out, expected);
636✔
139

140
            // input and output blocksize-ish wise
141
            auto process_as_blocks = [&](const std::string& id, size_t block_size) {
1,272✔
142
               auto new_xof = xof->new_object();
954✔
143
               result.test_eq(Botan::fmt("reconstructed XOF may accept input ({})", id),
954✔
144
                              new_xof->accepts_input(),
954✔
145
                              new_accepts_input);
954✔
146

147
               new_xof->start(salt, key);
954✔
148
               std::span<const uint8_t> in_span(in);
954✔
149
               while(!in_span.empty()) {
3,542✔
150
                  const auto bytes = std::min(block_size, in_span.size());
2,588✔
151
                  new_xof->update(in_span.first(bytes));
2,588✔
152
                  in_span = in_span.last(in_span.size() - bytes);
2,588✔
153
               }
154
               std::vector<uint8_t> blockwise_out(expected.size());
954✔
155
               std::span<uint8_t> out_span(blockwise_out);
954✔
156
               while(!out_span.empty()) {
4,413✔
157
                  const auto bytes = std::min(block_size, out_span.size());
3,459✔
158
                  new_xof->output(out_span.first(bytes));
3,459✔
159
                  out_span = out_span.last(out_span.size() - bytes);
3,459✔
160
               }
161
               result.test_eq(Botan::fmt("generated blockwise output ({})", id), blockwise_out, expected);
1,908✔
162
            };
1,908✔
163

164
            process_as_blocks("-1", xof->block_size() - 1);
318✔
165
            process_as_blocks("+0", xof->block_size());
318✔
166
            process_as_blocks("+1", xof->block_size() + 1);
318✔
167

168
            // copy state during processing
169
            try {
318✔
170
               xof->clear();
318✔
171
               xof->start(salt, key);
318✔
172
               xof->update(std::span(in).first(in.size() / 2));
318✔
173
               auto xof2 = xof->copy_state();
318✔
174
               result.test_eq("copied object might still accept input", xof2->accepts_input(), new_accepts_input);
291✔
175
               xof->update(std::span(in).last(in.size() - in.size() / 2));
291✔
176
               xof2->update(std::span(in).last(in.size() - in.size() / 2));
291✔
177
               auto cp_out1 = xof->output_stdvec(expected.size());
291✔
178
               auto cp_out2_1 = xof2->output_stdvec(expected.size() / 2);
291✔
179
               auto xof3 = xof2->copy_state();
291✔
180
               result.confirm("copied object doesn't allow input after reading output", !xof3->accepts_input());
582✔
181
               auto cp_out2_2a = xof2->output_stdvec(expected.size() - expected.size() / 2);
291✔
182
               auto cp_out2_2b = xof3->output_stdvec(expected.size() - expected.size() / 2);
291✔
183
               result.test_eq("output is equal, after state copy", cp_out1, expected);
582✔
184
               result.test_eq("output is equal, after state copy (A)", Botan::concat(cp_out2_1, cp_out2_2a), expected);
582✔
185
               result.test_eq("output is equal, after state copy (B)", Botan::concat(cp_out2_1, cp_out2_2b), expected);
873✔
186
            } catch(const Botan::Not_Implemented&) {
1,772✔
187
               // pass...
188
            }
27✔
189
         }
954✔
190

191
         return result;
192
      }
1,018✔
193

194
      std::vector<Test::Result> run_final_tests() override {
1✔
195
         return {
1✔
196
   #if defined(BOTAN_HAS_CSHAKE_XOF)
197
            CHECK("cSHAKE without a name",
198
                  [](Test::Result& result) {
1✔
199
                     std::vector<std::unique_ptr<Botan::XOF>> cshakes;
1✔
200
                     cshakes.push_back(std::make_unique<Botan::cSHAKE_128_XOF>(""));
1✔
201
                     cshakes.push_back(std::make_unique<Botan::cSHAKE_256_XOF>(""));
2✔
202

203
                     for(auto& cshake : cshakes) {
3✔
204
                        result.confirm("cSHAKE without a name rejects empty salt", !cshake->valid_salt_length(0));
4✔
205
                        result.confirm("cSHAKE without a name requests at least one byte of salt",
4✔
206
                                       cshake->valid_salt_length(1));
2✔
207
                        result.test_throws("cSHAKE without a name throws without salt", [&]() { cshake->start({}); });
8✔
208
                     }
209
                  }),
1✔
210
   #endif
211
   #if defined(BOTAN_HAS_AES_CRYSTALS_XOF)
212
               CHECK("AES-256/CTR XOF failure modes", [](Test::Result& result) {
1✔
213
                  Botan::AES_256_CTR_XOF aes_xof;
1✔
214
                  result.test_throws("AES-256/CTR XOF throws for empty key", [&]() { aes_xof.start({}, {}); });
3✔
215
                  result.test_throws("AES-256/CTR XOF throws for too long key",
2✔
216
                                     [&]() { aes_xof.start({}, std::vector<uint8_t>(33)); });
2✔
217
                  result.test_throws("AES-256/CTR XOF throws for too long IV",
2✔
218
                                     [&]() { aes_xof.start(std::vector<uint8_t>(17), std::vector<uint8_t>(32)); });
3✔
219
               }),
1✔
220
   #endif
221
         };
3✔
222
      }
1✔
223
};
224

225
BOTAN_REGISTER_SERIALIZED_TEST("xof", "extendable_output_functions", XOF_Tests);
226

227
#endif
228

229
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc