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

randombit / botan / 21786344715

07 Feb 2026 08:25PM UTC coverage: 90.068% (-0.005%) from 90.073%
21786344715

Pull #5295

github

web-flow
Merge 8d5fc3b23 into ebf8f0044
Pull Request #5295: Reduce header dependencies in tests and cli

102234 of 113507 relevant lines covered (90.07%)

11372278.81 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/exceptn.h>
12
   #include <botan/rng.h>
13
   #include <botan/xof.h>
14
   #include <botan/internal/fmt.h>
15
   #include <botan/internal/stl_util.h>
16

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

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

30
namespace Botan_Tests {
31

32
#if defined(BOTAN_HAS_XOF)
33

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

192
         return result;
193
      }
1,018✔
194

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

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

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

228
#endif
229

230
}  // 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