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

randombit / botan / 27595034679

15 Jun 2026 11:31PM UTC coverage: 91.711% (+2.3%) from 89.427%
27595034679

push

github

randombit
Add volatile specifier to rdseed inline asm annotation

114099 of 124411 relevant lines covered (91.71%)

10688437.1 hits per line

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

98.45
/src/lib/utils/bitvector/bitvector.h
1
/*
2
 * An abstraction for an arbitrarily large bitvector that can
3
 * optionally use the secure_allocator. All bitwise accesses and all
4
 * constructors are implemented in constant time. Otherwise, only methods
5
 * with the "ct_" pre-fix run in constant time.
6
 *
7
 * (C) 2023-2024 Jack Lloyd
8
 * (C) 2023-2024 René Meusel, Rohde & Schwarz Cybersecurity
9
 *
10
 * Botan is released under the Simplified BSD License (see license.txt)
11
 */
12

13
#ifndef BOTAN_BIT_VECTOR_H_
14
#define BOTAN_BIT_VECTOR_H_
15

16
#include <botan/concepts.h>
17
#include <botan/exceptn.h>
18
#include <botan/mem_ops.h>
19
#include <botan/secmem.h>
20
#include <botan/strong_type.h>
21
#include <botan/internal/bit_ops.h>
22
#include <botan/internal/ct_utils.h>
23
#include <botan/internal/int_utils.h>
24
#include <botan/internal/loadstor.h>
25
#include <botan/internal/stl_util.h>
26

27
#include <memory>
28
#include <optional>
29
#include <span>
30
#include <sstream>
31
#include <string>
32
#include <utility>
33
#include <vector>
34

35
namespace Botan {
36

37
template <template <typename> typename AllocatorT>
38
class bitvector_base;
39

40
template <typename T>
41
struct is_bitvector : std::false_type {};
42

43
template <template <typename> typename T>
44
struct is_bitvector<bitvector_base<T>> : std::true_type {};
45

46
template <typename T>
47
constexpr static bool is_bitvector_v = is_bitvector<T>::value;
48

49
template <typename T>
50
concept bitvectorish = is_bitvector_v<strong_type_wrapped_type<T>>;
51

52
namespace detail {
53

54
template <typename T0, typename... Ts>
55
struct first_type {
56
      using type = T0;
57
};
58

59
// get the first type from a parameter pack
60
// TODO: C++26 will bring Parameter Pack indexing:
61
//       using first_t = Ts...[0];
62
template <typename... Ts>
63
   requires(sizeof...(Ts) > 0)
64
using first_t = typename first_type<Ts...>::type;
65

66
// get the first object from a parameter pack
67
// TODO: C++26 will bring Parameter Pack indexing:
68
//       auto first = s...[0];
69
template <typename T0, typename... Ts>
70
constexpr static first_t<T0, Ts...> first(T0&& t, Ts&&... /*rest*/) {
71
   return std::forward<T0>(t);
72
}
73

74
template <typename OutT, typename>
75
using as = OutT;
76

77
template <typename FnT, std::unsigned_integral BlockT, typename... ParamTs>
78
using blockwise_processing_callback_return_type = std::invoke_result_t<FnT, as<BlockT, ParamTs>...>;
79

80
template <typename FnT, typename BlockT, typename... ParamTs>
81
concept is_blockwise_processing_callback_return_type =
82
   std::unsigned_integral<BlockT> &&
83
   (std::same_as<BlockT, blockwise_processing_callback_return_type<FnT, BlockT, ParamTs...>> ||
84
    std::same_as<bool, blockwise_processing_callback_return_type<FnT, BlockT, ParamTs...>> ||
85
    std::same_as<void, blockwise_processing_callback_return_type<FnT, BlockT, ParamTs...>>);
86

87
template <typename FnT, typename... ParamTs>
88
concept blockwise_processing_callback_without_mask =
89
   is_blockwise_processing_callback_return_type<FnT, uint8_t, ParamTs...> &&
90
   is_blockwise_processing_callback_return_type<FnT, uint16_t, ParamTs...> &&
91
   is_blockwise_processing_callback_return_type<FnT, uint32_t, ParamTs...> &&
92
   is_blockwise_processing_callback_return_type<FnT, uint64_t, ParamTs...>;
93

94
template <typename FnT, typename... ParamTs>
95
concept blockwise_processing_callback_with_mask =
96
   is_blockwise_processing_callback_return_type<FnT, uint8_t, ParamTs..., uint8_t /* mask */> &&
97
   is_blockwise_processing_callback_return_type<FnT, uint16_t, ParamTs..., uint16_t /* mask */> &&
98
   is_blockwise_processing_callback_return_type<FnT, uint32_t, ParamTs..., uint32_t /* mask */> &&
99
   is_blockwise_processing_callback_return_type<FnT, uint64_t, ParamTs..., uint64_t /* mask */>;
100

101
/**
102
 * Defines the callback constraints for the BitRangeOperator. For further
103
 * details, see bitvector_base::range_operation().
104
 */
105
template <typename FnT, typename... ParamTs>
106
concept blockwise_processing_callback = blockwise_processing_callback_with_mask<FnT, ParamTs...> ||
107
                                        blockwise_processing_callback_without_mask<FnT, ParamTs...>;
108

109
template <typename FnT, typename... ParamTs>
110
concept manipulating_blockwise_processing_callback =
111
   (blockwise_processing_callback_without_mask<FnT, ParamTs...> &&
112
    std::same_as<uint32_t, blockwise_processing_callback_return_type<FnT, uint32_t, ParamTs...>>) ||
113
   (blockwise_processing_callback_with_mask<FnT, ParamTs...> &&
114
    std::same_as<uint32_t, blockwise_processing_callback_return_type<FnT, uint32_t, ParamTs..., first_t<ParamTs...>>>);
115

116
template <typename FnT, typename... ParamTs>
117
concept predicate_blockwise_processing_callback =
118
   (blockwise_processing_callback_without_mask<FnT, ParamTs...> &&
119
    std::same_as<bool, blockwise_processing_callback_return_type<FnT, uint32_t, ParamTs...>>) ||
120
   (blockwise_processing_callback_with_mask<FnT, ParamTs...> &&
121
    std::same_as<bool, blockwise_processing_callback_return_type<FnT, uint32_t, ParamTs..., first_t<ParamTs...>>>);
122

123
template <typename T>
124
class bitvector_iterator {
125
   private:
126
      using size_type = typename T::size_type;
127

128
   public:
129
      using difference_type = std::make_signed_t<size_type>;
130
      using value_type = std::remove_const_t<decltype(std::declval<T>().at(0))>;
131
      using pointer = value_type*;
132
      using reference = value_type&;
133

134
      // TODO: technically, this could be a random access iterator
135
      using iterator_category = std::bidirectional_iterator_tag;
136

137
   public:
138
      bitvector_iterator() = default;
139
      ~bitvector_iterator() = default;
140

141
      bitvector_iterator(T* bitvector, size_t offset) : m_bitvector(bitvector) { update(offset); }
1,589✔
142

143
      bitvector_iterator(const bitvector_iterator& other) noexcept : m_bitvector(other.m_bitvector) {
2,002✔
144
         update(other.m_offset);
2,004✔
145
      }
146

147
      bitvector_iterator(bitvector_iterator&& other) noexcept : m_bitvector(other.m_bitvector) {
148
         update(other.m_offset);
149
      }
150

151
      bitvector_iterator& operator=(const bitvector_iterator& other) noexcept {
152
         if(this != &other) {
153
            m_bitvector = other.m_bitvector;
154
            update(other.m_offset);
155
         }
156
         return *this;
157
      }
158

159
      bitvector_iterator& operator=(bitvector_iterator&& other) noexcept {
160
         m_bitvector = other.m_bitvector;
161
         update(other.m_offset);
162
         return *this;
163
      }
164

165
      bitvector_iterator& operator++() noexcept {
252,224✔
166
         update(signed_offset() + 1);
504,475✔
167
         return *this;
168
      }
169

170
      bitvector_iterator operator++(int) noexcept {
6✔
171
         auto copy = *this;
6✔
172
         update(signed_offset() + 1);
12✔
173
         return copy;
6✔
174
      }
175

176
      bitvector_iterator& operator--() noexcept {
6✔
177
         update(signed_offset() - 1);
12✔
178
         return *this;
179
      }
180

181
      bitvector_iterator operator--(int) noexcept {
182
         auto copy = *this;
183
         update(signed_offset() - 1);
184
         return copy;
185
      }
186

187
      std::partial_ordering operator<=>(const bitvector_iterator& other) const noexcept {
188
         if(m_bitvector == other.m_bitvector) {
189
            return m_offset <=> other.m_offset;
190
         } else {
191
            return std::partial_ordering::unordered;
192
         }
193
      }
194

195
      bool operator==(const bitvector_iterator& other) const noexcept {
253,265✔
196
         return m_bitvector == other.m_bitvector && m_offset == other.m_offset;
252,737✔
197
      }
198

199
      reference operator*() const { return m_bitref.value(); }
2,379✔
200

201
      pointer operator->() const { return &(m_bitref.value()); }
256✔
202

203
   private:
204
      void update(size_type new_offset) {
524✔
205
         m_offset = new_offset;
1,029✔
206
         if(m_offset < m_bitvector->size()) {
1,506✔
207
            m_bitref.emplace((*m_bitvector)[m_offset]);
254,245✔
208
         } else {
209
            // end() iterator
210
            m_bitref.reset();
251,784✔
211
         }
212
      }
213

214
      difference_type signed_offset() const { return static_cast<difference_type>(m_offset); }
6✔
215

216
   private:
217
      T* m_bitvector;
218
      size_type m_offset;
219
      mutable std::optional<value_type> m_bitref;
220
};
221

222
}  // namespace detail
223

224
/**
225
 * An arbitrarily large bitvector with typical bit manipulation and convenient
226
 * bitwise access methods. Don't use `bitvector_base` directly, but the type
227
 * aliases::
228
 *
229
 *    * bitvector         - with a standard allocator
230
 *    * secure_bitvector  - with a secure allocator that auto-scrubs the memory
231
 */
232
template <template <typename> typename AllocatorT>
233
class bitvector_base final {
981,285✔
234
   public:
235
      using block_type = uint8_t;
236
      using size_type = size_t;
237
      using allocator_type = AllocatorT<block_type>;
238
      using value_type = block_type;
239
      using iterator = detail::bitvector_iterator<bitvector_base<AllocatorT>>;
240
      using const_iterator = detail::bitvector_iterator<const bitvector_base<AllocatorT>>;
241

242
      static constexpr size_type block_size_bytes = sizeof(block_type);
243
      static constexpr size_type block_size_bits = block_size_bytes * 8;
244
      static constexpr bool uses_secure_allocator = std::is_same_v<allocator_type, secure_allocator<block_type>>;
245

246
   private:
247
      template <template <typename> typename FriendAllocatorT>
248
      friend class bitvector_base;
249

250
      static constexpr block_type one = block_type(1);
251

252
      static constexpr size_type block_offset_shift = size_type(3) + ceil_log2(block_size_bytes);
253
      static constexpr size_type block_index_mask = (one << block_offset_shift) - 1;
254

255
      static constexpr size_type block_index(size_type pos) { return pos >> block_offset_shift; }
21,179,009✔
256

257
      static constexpr size_type block_offset(size_type pos) { return pos & block_index_mask; }
1,614,569,902✔
258

259
   private:
260
      /**
261
       * Internal helper to wrap a single bit in the bitvector and provide
262
       * certain convenience access methods.
263
       */
264
      template <typename BlockT>
265
         requires std::same_as<block_type, std::remove_cv_t<BlockT>>
266
      class bitref_base {
267
         private:
268
            friend class bitvector_base<AllocatorT>;
269

270
            constexpr bitref_base(std::span<BlockT> blocks, size_type pos) noexcept :
1,614,574,356✔
271
                  m_block(blocks[block_index(pos)]), m_mask(one << block_offset(pos)) {}
1,614,574,356✔
272

273
         public:
274
            bitref_base() = delete;
275
            bitref_base(const bitref_base&) noexcept = default;
276
            bitref_base(bitref_base&&) noexcept = default;
277
            bitref_base& operator=(const bitref_base&) = delete;
278
            bitref_base& operator=(bitref_base&&) = delete;
279

280
            ~bitref_base() = default;
281

282
         public:
283
            // NOLINTNEXTLINE(*-explicit-conversions)
284
            constexpr operator bool() const noexcept { return is_set(); }
12,734,495✔
285

286
            constexpr bool is_set() const noexcept { return (m_block & m_mask) > 0; }
12,734,518✔
287

288
            template <std::unsigned_integral T>
289
            constexpr T as() const noexcept {
193,849✔
290
               return static_cast<T>(is_set());
193,841✔
291
            }
292

293
            constexpr CT::Choice as_choice() const noexcept {
404,043,801✔
294
               return CT::Choice::from_int(static_cast<BlockT>(m_block & m_mask));
404,043,801✔
295
            }
296

297
         protected:
298
            BlockT& m_block;  // NOLINT(*-non-private-member-variable*)
299
            BlockT m_mask;    // NOLINT(*-non-private-member-variable*)
300
      };
301

302
   public:
303
      /**
304
       * Wraps a constant reference into the bitvector. Bit can be accessed
305
       * but not modified.
306
       */
307
      template <typename BlockT>
308
      class bitref final : public bitref_base<BlockT> {
309
         public:
310
            using bitref_base<BlockT>::bitref_base;
12,641,743✔
311
      };
312

313
      /**
314
       * Wraps a modifiable reference into the bitvector. Bit may be accessed
315
       * and modified (e.g. flipped or XOR'ed).
316
       *
317
       * Constant-time operations are used for the bit manipulations. The
318
       * location of the bit in the bit vector may be leaked, though.
319
       */
320
      template <typename BlockT>
321
         requires(!std::is_const_v<BlockT>)
322
      class bitref<BlockT> : public bitref_base<BlockT> {
323
         public:
324
            using bitref_base<BlockT>::bitref_base;
1,197,090,546✔
325

326
            ~bitref() = default;
327
            bitref(const bitref&) noexcept = default;
328
            bitref(bitref&&) noexcept = default;
329

330
            constexpr bitref& set() noexcept {
401✔
331
               this->m_block |= this->m_mask;
41✔
332
               return *this;
250✔
333
            }
334

335
            constexpr bitref& unset() noexcept {
14✔
336
               this->m_block &= ~this->m_mask;
1✔
337
               return *this;
338
            }
339

340
            constexpr bitref& flip() noexcept {
27✔
341
               this->m_block ^= this->m_mask;
7✔
342
               return *this;
343
            }
344

345
            // NOLINTBEGIN
346

347
            constexpr bitref& operator=(bool bit) noexcept {
8,542,090✔
348
               this->m_block =
8,542,090✔
349
                  CT::Mask<BlockT>::expand(bit).select(this->m_mask | this->m_block, this->m_block & ~this->m_mask);
8,542,090✔
350
               return *this;
8,542,090✔
351
            }
352

353
            constexpr bitref& operator=(CT::Choice bit) noexcept {
1,188,729,009✔
354
               const auto mask = CT::Mask<BlockT>::from_choice(bit);
1,188,729,011✔
355
               this->m_block = mask.select(this->m_mask | this->m_block, this->m_block & ~this->m_mask);
1,188,729,009✔
356
               return *this;
357
            }
358

359
            constexpr bitref& operator=(const bitref& bit) noexcept { return *this = bit.is_set(); }
360

361
            constexpr bitref& operator=(bitref&& bit) noexcept { return *this = bit.is_set(); }
362

363
            // NOLINTEND
364

365
            constexpr bitref& operator&=(bool other) noexcept {
4✔
366
               this->m_block &= ~CT::Mask<BlockT>::expand(other).if_not_set_return(this->m_mask);
4✔
367
               return *this;
4✔
368
            }
369

370
            constexpr bitref& operator&=(CT::Choice other) noexcept {
1✔
371
               this->m_block &= ~CT::Mask<BlockT>::from_choice(other).if_not_set_return(this->m_mask);
1✔
372
               return *this;
373
            }
374

375
            constexpr bitref& operator|=(bool other) noexcept {
1✔
376
               this->m_block |= CT::Mask<BlockT>::expand(other).if_set_return(this->m_mask);
1✔
377
               return *this;
378
            }
379

380
            constexpr bitref& operator|=(CT::Choice other) noexcept {
1✔
381
               this->m_block |= CT::Mask<BlockT>::from_choice(other).if_set_return(this->m_mask);
1✔
382
               return *this;
383
            }
384

385
            constexpr bitref& operator^=(bool other) noexcept {
1✔
386
               this->m_block ^= CT::Mask<BlockT>::expand(other).if_set_return(this->m_mask);
1✔
387
               return *this;
388
            }
389

390
            constexpr bitref& operator^=(CT::Choice other) noexcept {
79,045✔
391
               this->m_block ^= CT::Mask<BlockT>::from_choice(other).if_set_return(this->m_mask);
79,045✔
392
               return *this;
393
            }
394
      };
395

396
   public:
397
      bitvector_base() : m_bits(0) {}
80✔
398

399
      explicit bitvector_base(size_type bits) : m_bits(bits), m_blocks(ceil_toblocks(bits)) {}
385,997✔
400

401
      /**
402
       * Initialize the bitvector from a byte-array. Bits are taken byte-wise
403
       * from least significant to most significant. Example::
404
       *
405
       *    bitvector[0] -> LSB(Byte[0])
406
       *    bitvector[1] -> LSB+1(Byte[0])
407
       *    ...
408
       *    bitvector[8] -> LSB(Byte[1])
409
       *
410
       * @param bytes The byte vector to be loaded
411
       * @param bits  The number of bits to be loaded. This must not be more
412
       *              than the number of bytes in @p bytes.
413
       */
414
      bitvector_base(std::span<const uint8_t> bytes, /* NOLINT(*-explicit-conversions) */
80,942✔
415
                     std::optional<size_type> bits = std::nullopt) :
416
            m_bits() {
80,942✔
417
         from_bytes(bytes, bits);
80,942✔
418
      }
80,942✔
419

420
      bitvector_base(std::initializer_list<block_type> blocks, std::optional<size_type> bits = std::nullopt) :
183✔
421
            m_bits(bits.value_or(blocks.size() * block_size_bits)), m_blocks(blocks.begin(), blocks.end()) {}
183✔
422

423
      bool empty() const { return m_bits == 0; }
6✔
424

425
      size_type size() const { return m_bits; }
8,455,998✔
426

427
      /**
428
       * @returns true iff the number of 1-bits in this is odd, false otherwise (constant time)
429
       */
430
      CT::Choice has_odd_hamming_weight() const {
79,047✔
431
         uint64_t acc = 0;
79,047✔
432
         full_range_operation([&](std::unsigned_integral auto block) { acc ^= block; }, *this);
79,047✔
433

434
         for(size_t i = (sizeof(acc) * 8) >> 1; i > 0; i >>= 1) {
553,329✔
435
            acc ^= acc >> i;
474,282✔
436
         }
437

438
         return CT::Choice::from_int(acc & one);
79,047✔
439
      }
440

441
      /**
442
       * Counts the number of 1-bits in the bitvector in constant time.
443
       * @returns the "population count" (or hamming weight) of the bitvector
444
       */
445
      size_type hamming_weight() const {
113✔
446
         size_type acc = 0;
113✔
447
         full_range_operation([&](std::unsigned_integral auto block) { acc += ct_popcount(block); }, *this);
94✔
448
         return acc;
94✔
449
      }
450

451
      /**
452
       * @returns copies this bitvector into a new bitvector of type @p OutT
453
       */
454
      template <bitvectorish OutT>
455
      OutT as() const {
88✔
456
         return subvector<OutT>(0, size());
144✔
457
      }
458

459
      /**
460
       * @returns true if @p other contains the same bit pattern as this
461
       */
462
      template <bitvectorish OtherT>
463
      bool equals_vartime(const OtherT& other) const noexcept {
18✔
464
         return size() == other.size() &&
20✔
465
                full_range_operation([]<std::unsigned_integral BlockT>(BlockT lhs, BlockT rhs) { return lhs == rhs; },
20✔
466
                                     *this,
467
                                     unwrap_strong_type(other));
468
      }
469

470
      /**
471
       * @returns true if @p other contains the same bit pattern as this
472
       */
473
      template <bitvectorish OtherT>
474
      bool equals(const OtherT& other) const noexcept {
50✔
475
         if(size() != other.size()) {
50✔
476
            return false;
477
         }
478

479
         uint64_t acc = 0;
49✔
480
         full_range_operation(
49✔
481
            [&]<std::unsigned_integral BlockT>(BlockT lhs, BlockT rhs) { acc |= static_cast<uint64_t>(lhs ^ rhs); },
53✔
482
            *this,
483
            unwrap_strong_type(other));
484
         return !CT::Choice::from_int(acc).as_bool();
49✔
485
      }
486

487
      /// @name Serialization
488
      /// @{
489

490
      /**
491
       * Re-initialize the bitvector with the given bytes. See the respective
492
       * constructor for details. This should be used only when trying to save
493
       * allocations. Otherwise, use the constructor.
494
       *
495
       * @param bytes  the byte range to load bits from
496
       * @param bits   (optional) if not all @p bytes should be loaded in full
497
       */
498
      void from_bytes(std::span<const uint8_t> bytes, std::optional<size_type> bits = std::nullopt) {
80,942✔
499
         const size_type new_bits = bits.has_value()
161,884✔
500
                                       ? bits.value()
80,942✔
501
                                       : mul_or_throw<size_t>(8, bytes.size_bytes(), "bitvector input is too large");
1,766✔
502
         const size_type bytes_needed = (new_bits / 8) + (new_bits % 8 != 0 ? 1 : 0);
143,306✔
503
         BOTAN_ARG_CHECK(bytes_needed <= bytes.size_bytes(), "not enough data to load so many bits");
80,942✔
504
         resize(new_bits);
80,942✔
505

506
         // load as much aligned data as possible
507
         const auto verbatim_blocks = new_bits / block_size_bits;
80,942✔
508
         const auto verbatim_bytes = verbatim_blocks * block_size_bytes;
80,942✔
509
         if(verbatim_blocks > 0) {
80,942✔
510
            typecast_copy(std::span{m_blocks}.first(verbatim_blocks), bytes.first(verbatim_bytes));
80,941✔
511
         }
512

513
         // load remaining unaligned data
514
         for(size_type i = verbatim_bytes * 8; i < new_bits; ++i) {
173,806✔
515
            ref(i) = ((bytes[i >> 3] & (uint8_t(1) << (i & 7))) != 0);
92,864✔
516
         }
517
      }
80,942✔
518

519
      /**
520
       * Renders the bitvector into a byte array. By default, this will use
521
       * `std::vector<uint8_t>` or `Botan::secure_vector<uint8_t>`, depending on
522
       * the allocator used by the bitvector. The rendering is compatible with
523
       * the bit layout explained in the respective constructor.
524
       */
525
      template <concepts::resizable_byte_buffer OutT =
526
                   std::conditional_t<uses_secure_allocator, secure_vector<uint8_t>, std::vector<uint8_t>>>
527
      OutT to_bytes() const {
328✔
528
         OutT out(ceil_tobytes(m_bits));
328✔
529
         to_bytes(out);
328✔
530
         return out;
328✔
531
      }
×
532

533
      /**
534
       * Renders the bitvector into a properly sized byte range.
535
       *
536
       * @param out  a byte range that has a length of at least `ceil_tobytes(size())`.
537
       */
538
      void to_bytes(std::span<uint8_t> out) const {
128,535✔
539
         const auto bytes_needed = ceil_tobytes(m_bits);
128,535✔
540
         BOTAN_ARG_CHECK(bytes_needed <= out.size_bytes(), "Not enough space to render bitvector");
128,535✔
541

542
         // copy as much aligned data as possible
543
         const auto verbatim_blocks = m_bits / block_size_bits;
128,535✔
544
         const auto verbatim_bytes = verbatim_blocks * block_size_bytes;
128,535✔
545
         if(verbatim_blocks > 0) {
128,535✔
546
            typecast_copy(out.first(verbatim_bytes), std::span{m_blocks}.first(verbatim_blocks));
128,532✔
547
         }
548

549
         // copy remaining unaligned data
550
         clear_mem(out.subspan(verbatim_bytes));
128,535✔
551
         for(size_type i = verbatim_bytes * 8; i < m_bits; ++i) {
321,954✔
552
            out[i >> 3] |= ref(i).template as<uint8_t>() << (i & 7);
193,419✔
553
         }
554
      }
128,535✔
555

556
      /**
557
       * Renders this bitvector into a sequence of "0"s and "1"s.
558
       * This is meant for debugging purposes and is not efficient.
559
       */
560
      std::string to_string() const {
1✔
561
         std::stringstream ss;
1✔
562
         for(size_type i = 0; i < size(); ++i) {
6✔
563
            ss << ref(i);
5✔
564
         }
565
         return ss.str();
1✔
566
      }
1✔
567

568
      /// @}
569

570
      /// @name Capacity Accessors and Modifiers
571
      /// @{
572

573
      size_type capacity() const { return m_blocks.capacity() * block_size_bits; }
6✔
574

575
      void reserve(size_type bits) { m_blocks.reserve(ceil_toblocks(bits)); }
219✔
576

577
      void resize(size_type bits) {
478,327✔
578
         const auto new_number_of_blocks = ceil_toblocks(bits);
478,327✔
579
         const auto old_number_of_blocks = m_blocks.size();
478,327✔
580
         if(new_number_of_blocks > old_number_of_blocks) {
478,327✔
581
            m_blocks.insert(m_blocks.end(), new_number_of_blocks - old_number_of_blocks, block_type(0));
130,681✔
582
         } else if(new_number_of_blocks < old_number_of_blocks) {
347,646✔
583
            m_blocks.erase(m_blocks.begin() + new_number_of_blocks, m_blocks.end());
3✔
584
         }
585

586
         m_bits = bits;
478,327✔
587
         zero_unused_bits();
478,327✔
588
      }
478,327✔
589

590
      void push_back(bool bit) {
15✔
591
         const auto i = size();
15✔
592
         resize(i + 1);
15✔
593
         ref(i) = bit;
30✔
594
      }
15✔
595

596
      void push_back(CT::Choice bit) {
397,282✔
597
         const auto i = size();
397,282✔
598
         resize(i + 1);
397,282✔
599
         ref(i) = bit;
397,282✔
600
      }
397,282✔
601

602
      void pop_back() {
9✔
603
         if(!empty()) {
9✔
604
            resize(size() - 1);
9✔
605
         }
606
      }
607

608
      /// @}
609

610
      /// @name Bitwise and Global Accessors and Modifiers
611
      /// @{
612

613
      auto at(size_type pos) {
412,001,829✔
614
         check_offset(pos);
412,001,825✔
615
         return ref(pos);
412,001,524✔
616
      }
617

618
      // TODO C++23: deducing this
619
      auto at(size_type pos) const {
890,817✔
620
         check_offset(pos);
890,816✔
621
         return ref(pos);
890,816✔
622
      }
623

624
      auto front() { return ref(0); }
2✔
625

626
      // TODO C++23: deducing this
627
      auto front() const { return ref(0); }
628

629
      auto back() { return ref(size() - 1); }
5✔
630

631
      // TODO C++23: deducing this
632
      auto back() const { return ref(size() - 1); }
633

634
      /**
635
       * Sets the bit at position @p pos.
636
       * @throws Botan::Invalid_Argument if @p pos is out of range
637
       */
638
      bitvector_base& set(size_type pos) {
111✔
639
         check_offset(pos);
110✔
640
         ref(pos).set();
76✔
641
         return *this;
57✔
642
      }
643

644
      /**
645
       * Sets all currently allocated bits.
646
       */
647
      bitvector_base& set() {
2✔
648
         full_range_operation(
2✔
649
            [](std::unsigned_integral auto block) -> decltype(block) {
2✔
650
               return static_cast<decltype(block)>(~static_cast<decltype(block)>(0));
651
            },
652
            *this);
653
         zero_unused_bits();
2✔
654
         return *this;
2✔
655
      }
656

657
      /**
658
       * Unsets the bit at position @p pos.
659
       * @throws Botan::Invalid_Argument if @p pos is out of range
660
       */
661
      bitvector_base& unset(size_type pos) {
14✔
662
         check_offset(pos);
13✔
663
         ref(pos).unset();
13✔
664
         return *this;
13✔
665
      }
666

667
      /**
668
       * Unsets all currently allocated bits.
669
       */
670
      bitvector_base& unset() {
4✔
671
         full_range_operation(
4✔
672
            [](std::unsigned_integral auto block) -> decltype(block) { return static_cast<decltype(block)>(0); },
4✔
673
            *this);
674
         return *this;
675
      }
676

677
      /**
678
       * Flips the bit at position @p pos.
679
       * @throws Botan::Invalid_Argument if @p pos is out of range
680
       */
681
      bitvector_base& flip(size_type pos) {
21✔
682
         check_offset(pos);
20✔
683
         ref(pos).flip();
20✔
684
         return *this;
20✔
685
      }
686

687
      /**
688
       * Flips all currently allocated bits.
689
       */
690
      bitvector_base& flip() {
5✔
691
         full_range_operation([](std::unsigned_integral auto block) -> decltype(block) { return ~block; }, *this);
5✔
692
         zero_unused_bits();
5✔
693
         return *this;
5✔
694
      }
695

696
      /**
697
       * @returns true iff no bit is set
698
       */
699
      bool none_vartime() const {
15✔
700
         return full_range_operation([](std::unsigned_integral auto block) { return block == 0; }, *this);
7✔
701
      }
702

703
      /**
704
       * @returns true iff no bit is set in constant time
705
       */
706
      bool none() const { return hamming_weight() == 0; }
7✔
707

708
      /**
709
       * @returns true iff at least one bit is set
710
       */
711
      bool any_vartime() const { return !none_vartime(); }
8✔
712

713
      /**
714
       * @returns true iff at least one bit is set in constant time
715
       */
716
      bool any() const { return !none(); }
6✔
717

718
      /**
719
       * @returns true iff all bits are set
720
       */
721
      bool all_vartime() const {
8✔
722
         return full_range_operation(
8✔
723
            []<std::unsigned_integral BlockT>(BlockT block, BlockT mask) { return block == mask; }, *this);
7✔
724
      }
725

726
      /**
727
       * @returns true iff all bits are set in constant time
728
       */
729
      bool all() const { return hamming_weight() == m_bits; }
6✔
730

731
      auto operator[](size_type pos) { return ref(pos); }
1,188,549,661✔
732

733
      // TODO C++23: deducing this
734
      auto operator[](size_type pos) const { return ref(pos); }
12,448,319✔
735

736
      /// @}
737

738
      /// @name Subvectors
739
      /// @{
740

741
      /**
742
       * Creates a new bitvector with a subsection of this bitvector starting at
743
       * @p pos copying exactly @p length bits.
744
       */
745
      template <bitvectorish OutT = bitvector_base<AllocatorT>>
746
      auto subvector(size_type pos, std::optional<size_type> length = std::nullopt) const {
128,376✔
747
         const size_type bitlen = length.value_or(size() - pos);
128,376✔
748
         BOTAN_ARG_CHECK(pos + bitlen <= size(), "Not enough bits to copy");
128,376✔
749

750
         OutT newvector(bitlen);
128,372✔
751

752
         // Handle bitvectors that are wrapped in strong types
753
         auto& newvector_unwrapped = unwrap_strong_type(newvector);
128,372✔
754

755
         if(bitlen > 0) {
128,372✔
756
            if(pos % 8 == 0) {
128,369✔
757
               copy_mem(
89,675✔
758
                  newvector_unwrapped.m_blocks,
89,675✔
759
                  std::span{m_blocks}.subspan(block_index(pos), block_index(pos + bitlen - 1) - block_index(pos) + 1));
89,675✔
760
            } else {
761
               const BitRangeOperator<const bitvector_base<AllocatorT>, BitRangeAlignment::no_alignment> from_op(
38,694✔
762
                  *this, pos, bitlen);
763
               const BitRangeOperator<strong_type_wrapped_type<OutT>> to_op(
38,694✔
764
                  unwrap_strong_type(newvector_unwrapped), 0, bitlen);
765
               range_operation([](auto /* to */, auto from) { return from; }, to_op, from_op);
38,694✔
766
            }
767

768
            newvector_unwrapped.zero_unused_bits();
128,372✔
769
         }
770

771
         return newvector;
128,372✔
772
      }
×
773

774
      /**
775
       * Extracts a subvector of bits as an unsigned integral type @p OutT
776
       * starting from bit @p pos and copying exactly sizeof(OutT)*8 bits.
777
       *
778
       * Hint: The bits are in big-endian order, i.e. the least significant bit
779
       *       is the 0th bit and the most significant bit it the n-th. Hence,
780
       *       addressing the bits with bitwise operations is done like so:
781
       *       bool bit = (out_int >> pos) & 1;
782
       */
783
      template <typename OutT>
784
         requires(std::unsigned_integral<strong_type_wrapped_type<OutT>> &&
785
                  !std::same_as<bool, strong_type_wrapped_type<OutT>>)
786
      OutT subvector(size_type pos) const {
74,960✔
787
         using result_t = strong_type_wrapped_type<OutT>;
788
         constexpr size_t bits = sizeof(result_t) * 8;
74,960✔
789
         BOTAN_ARG_CHECK(pos + bits <= size(), "Not enough bits to copy");
74,960✔
790
         result_t out = 0;
74,956✔
791

792
         if(pos % 8 == 0) {
74,956✔
793
            out = load_le<result_t>(std::span{m_blocks}.subspan(block_index(pos)).template first<sizeof(result_t)>());
48,648✔
794
         } else {
795
            const BitRangeOperator<const bitvector_base<AllocatorT>, BitRangeAlignment::no_alignment> op(
26,308✔
796
               *this, pos, bits);
797
            range_operation(
26,308✔
798
               [&](std::unsigned_integral auto integer) {
26,308✔
799
                  if constexpr(std::same_as<result_t, decltype(integer)>) {
800
                     out = integer;
26,308✔
801
                  }
802
               },
803
               op);
804
         }
805

806
         return wrap_strong_type<OutT>(out);
74,956✔
807
      }
808

809
      /**
810
       * Replaces a subvector of bits with the bits of another bitvector @p value
811
       * starting at bit @p pos. The number of bits to replace is determined by
812
       * the size of @p value.
813
       *
814
       * @note This is currently supported for byte-aligned @p pos only.
815
       *
816
       * @throws Not_Implemented when called with @p pos not divisible by 8.
817
       *
818
       * @param pos    the position to start replacing bits
819
       * @param value  the bitvector to copy bits from
820
       */
821
      template <typename InT>
822
         requires(std::unsigned_integral<strong_type_wrapped_type<InT>> && !std::same_as<bool, InT>)
823
      void subvector_replace(size_type pos, InT value) {
74,967✔
824
         using in_t = strong_type_wrapped_type<InT>;
825
         constexpr size_t bits = sizeof(in_t) * 8;
74,967✔
826
         BOTAN_ARG_CHECK(pos + bits <= size(), "Not enough bits to replace");
74,967✔
827

828
         if(pos % 8 == 0) {
74,963✔
829
            store_le(std::span{m_blocks}.subspan(block_index(pos)).template first<sizeof(in_t)>(),
48,650✔
830
                     unwrap_strong_type(value));
831
         } else {
832
            const BitRangeOperator<bitvector_base<AllocatorT>, BitRangeAlignment::no_alignment> op(*this, pos, bits);
26,313✔
833
            range_operation(
26,313✔
834
               [&]<std::unsigned_integral BlockT>(BlockT block) -> BlockT {
26,313✔
835
                  if constexpr(std::same_as<in_t, BlockT>) {
836
                     return unwrap_strong_type(value);
26,313✔
837
                  } else {
838
                     // This should never be reached. BOTAN_ASSERT_UNREACHABLE()
839
                     // caused warning "unreachable code" on MSVC, though. You
840
                     // don't say!
841
                     //
842
                     // Returning the given block back, is the most reasonable
843
                     // thing to do in this case, though.
844
                     return block;
845
                  }
846
               },
847
               op);
848
         }
849
      }
74,963✔
850

851
      /// @}
852

853
      /// @name Operators
854
      ///
855
      /// @{
856

857
      auto operator~() {
1✔
858
         auto newbv = *this;
1✔
859
         newbv.flip();
1✔
860
         return newbv;
1✔
861
      }
×
862

863
      template <bitvectorish OtherT>
864
      auto& operator|=(const OtherT& other) {
6✔
865
         full_range_operation([]<std::unsigned_integral BlockT>(BlockT lhs, BlockT rhs) -> BlockT { return lhs | rhs; },
2✔
866
                              *this,
867
                              unwrap_strong_type(other));
868
         return *this;
869
      }
870

871
      template <bitvectorish OtherT>
872
      auto& operator&=(const OtherT& other) {
79,049✔
873
         full_range_operation([]<std::unsigned_integral BlockT>(BlockT lhs, BlockT rhs) -> BlockT { return lhs & rhs; },
79,045✔
874
                              *this,
875
                              unwrap_strong_type(other));
876
         return *this;
877
      }
878

879
      template <bitvectorish OtherT>
880
      auto& operator^=(const OtherT& other) {
8✔
881
         full_range_operation([]<std::unsigned_integral BlockT>(BlockT lhs, BlockT rhs) -> BlockT { return lhs ^ rhs; },
1✔
882
                              *this,
883
                              unwrap_strong_type(other));
884
         return *this;
885
      }
886

887
      /// @}
888

889
      /// @name Constant Time Operations
890
      ///
891
      /// @{
892

893
      /**
894
       * Implements::
895
       *
896
       *    if(condition) {
897
       *       *this ^= other;
898
       *    }
899
       *
900
       * omitting runtime dependence on any of the parameters.
901
       */
902
      template <bitvectorish OtherT>
903
      void ct_conditional_xor(CT::Choice condition, const OtherT& other) {
407,282,975✔
904
         BOTAN_ASSERT_NOMSG(m_bits == other.m_bits);
407,282,975✔
905
         BOTAN_ASSERT_NOMSG(m_blocks.size() == other.m_blocks.size());
407,282,975✔
906

907
         auto maybe_xor = overloaded{
908
            [m = CT::Mask<uint64_t>::from_choice(condition)](uint64_t lhs, uint64_t rhs) -> uint64_t {
2,147,483,647✔
909
               return lhs ^ m.if_set_return(rhs);
2,147,483,647✔
910
            },
911
            [m = CT::Mask<uint32_t>::from_choice(condition)](uint32_t lhs, uint32_t rhs) -> uint32_t {
656,912,328✔
912
               return lhs ^ m.if_set_return(rhs);
249,629,353✔
913
            },
914
            [m = CT::Mask<uint16_t>::from_choice(condition)](uint16_t lhs, uint16_t rhs) -> uint16_t {
511,306,354✔
915
               return lhs ^ m.if_set_return(rhs);
104,023,379✔
916
            },
917
            [m = CT::Mask<uint8_t>::from_choice(condition)](uint8_t lhs, uint8_t rhs) -> uint8_t {
407,282,981✔
918
               return lhs ^ m.if_set_return(rhs);
6✔
919
            },
920
         };
921

922
         full_range_operation(maybe_xor, *this, unwrap_strong_type(other));
407,282,975✔
923
      }
407,282,975✔
924

925
      constexpr void _const_time_poison() const { CT::poison(m_blocks); }
71✔
926

927
      constexpr void _const_time_unpoison() const { CT::unpoison(m_blocks); }
98✔
928

929
      /// @}
930

931
      /// @name Iterators
932
      ///
933
      /// @{
934

935
      iterator begin() noexcept { return iterator(this, 0); }
1,067✔
936

937
      const_iterator begin() const noexcept { return const_iterator(this, 0); }
10✔
938

939
      const_iterator cbegin() const noexcept { return const_iterator(this, 0); }
10✔
940

941
      iterator end() noexcept { return iterator(this, size()); }
537✔
942

943
      const_iterator end() const noexcept { return const_iterator(this, size()); }
5✔
944

945
      const_iterator cend() noexcept { return const_iterator(this, size()); }
8✔
946

947
      /// @}
948

949
   private:
950
      void check_offset(size_type pos) const {
412,892,337✔
951
         // BOTAN_ASSERT_NOMSG(!CT::is_poisoned(&m_bits, sizeof(m_bits)));
952
         // BOTAN_ASSERT_NOMSG(!CT::is_poisoned(&pos, sizeof(pos)));
953
         BOTAN_ARG_CHECK(pos < m_bits, "Out of range");
412,892,758✔
954
      }
955

956
      void zero_unused_bits() {
606,703✔
957
         const auto first_unused_bit = size();
606,703✔
958

959
         // Zero out any unused bits in the last block
960
         if(first_unused_bit % block_size_bits != 0) {
606,703✔
961
            const block_type mask = (one << block_offset(first_unused_bit)) - one;
404,956✔
962
            m_blocks[block_index(first_unused_bit)] &= mask;
404,956✔
963
         }
964
      }
965

966
      static constexpr size_type ceil_toblocks(size_type bits) {
607,113✔
967
         return add_or_throw(bits, block_size_bits - 1, "bitvector size is too large") / block_size_bits;
607,113✔
968
      }
969

970
      auto ref(size_type pos) const { return bitref<const block_type>(m_blocks, pos); }
12,641,743✔
971

972
      auto ref(size_type pos) { return bitref<block_type>(m_blocks, pos); }
1,197,090,546✔
973

974
   private:
975
      enum class BitRangeAlignment : uint8_t { byte_aligned, no_alignment };
976

977
      /**
978
       * Helper construction to implement bit range operations on the bitvector.
979
       * It basically implements an iterator to read and write blocks of bits
980
       * from the underlying bitvector. Where "blocks of bits" are unsigned
981
       * integers of varying bit lengths.
982
       *
983
       * If the iteration starts at a byte boundary in the underlying bitvector,
984
       * this applies certain optimizations (i.e. loading blocks of bits straight
985
       * from the underlying byte buffer). The optimizations are enabled at
986
       * compile time (with the template parameter `alignment`).
987
       */
988
      template <typename BitvectorT, auto alignment = BitRangeAlignment::byte_aligned>
989
         requires is_bitvector_v<std::remove_cvref_t<BitvectorT>>
990
      class BitRangeOperator {
991
         private:
992
            constexpr static bool is_const() { return std::is_const_v<BitvectorT>; }
993

994
            struct UnalignedDataHelper {
995
                  const uint8_t padding_bits;
996
                  const uint8_t bits_to_byte_alignment;
997
            };
998

999
         public:
1000
            BitRangeOperator(BitvectorT& source, size_type start_bitoffset, size_type bitlength) :
814,933,409✔
1001
                  m_source(source),
814,933,409✔
1002
                  m_start_bitoffset(start_bitoffset),
814,933,409✔
1003
                  m_bitlength(bitlength),
814,933,409✔
1004
                  m_unaligned_helper({.padding_bits = static_cast<uint8_t>(start_bitoffset % 8),
814,933,409✔
1005
                                      .bits_to_byte_alignment = static_cast<uint8_t>(8 - (start_bitoffset % 8))}),
814,933,409✔
1006
                  m_read_bitpos(start_bitoffset),
814,933,409✔
1007
                  m_write_bitpos(start_bitoffset) {
814,933,409✔
1008
               BOTAN_ASSERT(is_byte_aligned() == (m_start_bitoffset % 8 == 0), "byte alignment guarantee");
814,933,409✔
1009
               BOTAN_ASSERT(m_source.size() >= m_start_bitoffset + m_bitlength, "enough bytes in underlying source");
814,933,409✔
1010
            }
814,933,409✔
1011

1012
            explicit BitRangeOperator(BitvectorT& source) : BitRangeOperator(source, 0, source.size()) {}
814,803,400✔
1013

1014
            static constexpr bool is_byte_aligned() { return alignment == BitRangeAlignment::byte_aligned; }
1015

1016
            /**
1017
             * @returns the overall number of bits to be iterated with this operator
1018
             */
1019
            size_type size() const { return m_bitlength; }
407,479,991✔
1020

1021
            /**
1022
             * @returns the number of bits not yet read from this operator
1023
             */
1024
            size_type bits_to_read() const { return m_bitlength - m_read_bitpos + m_start_bitoffset; }
1,636,893,029✔
1025

1026
            /**
1027
             * @returns the number of bits still to be written into this operator
1028
             */
1029
            size_type bits_to_write() const { return m_bitlength - m_write_bitpos + m_start_bitoffset; }
3,398,318✔
1030

1031
            /**
1032
             * Loads the next block of bits from the underlying bitvector. No
1033
             * bounds checks are performed. The caller can define the size of
1034
             * the resulting unsigned integer block.
1035
             */
1036
            template <std::unsigned_integral BlockT>
1037
            BlockT load_next() const {
6,762,618✔
1038
               constexpr size_type block_size = sizeof(BlockT);
6,762,618✔
1039
               constexpr size_type block_bits = block_size * 8;
6,762,618✔
1040
               const auto bits_remaining = bits_to_read();
6,762,618✔
1041

1042
               BlockT result_block = 0;
6,762,618✔
1043
               if constexpr(is_byte_aligned()) {
1044
                  result_block = load_le(m_source.as_byte_span().subspan(read_bytepos()).template first<block_size>());
3,382,904✔
1045
               } else {
1046
                  const size_type byte_pos = read_bytepos();
3,379,714✔
1047
                  const size_type bits_to_collect = std::min(block_bits, bits_to_read());
6,759,428✔
1048

1049
                  const uint8_t first_byte = m_source.as_byte_span()[byte_pos];
3,379,714✔
1050

1051
                  // Initialize the left-most bits from the first byte.
1052
                  result_block = BlockT(first_byte) >> m_unaligned_helper.padding_bits;
3,379,714✔
1053

1054
                  // If more bits are needed, we pull them from the remaining bytes.
1055
                  if(m_unaligned_helper.bits_to_byte_alignment < bits_to_collect) {
3,379,714✔
1056
                     const BlockT block =
1057
                        load_le(m_source.as_byte_span().subspan(byte_pos + 1).template first<block_size>());
6,682,042✔
1058
                     result_block |= block << m_unaligned_helper.bits_to_byte_alignment;
3,341,024✔
1059
                  }
1060
               }
1061

1062
               m_read_bitpos += std::min(block_bits, bits_remaining);
6,762,618✔
1063
               return result_block;
6,668,117✔
1064
            }
1065

1066
            /**
1067
             * Stores the next block of bits into the underlying bitvector.
1068
             * No bounds checks are performed. Storing bit blocks that are not
1069
             * aligned at a byte-boundary in the underlying bitvector is
1070
             * currently not implemented.
1071
             */
1072
            template <std::unsigned_integral BlockT>
1073
               requires(!is_const())
1074
            void store_next(BlockT block) {
3,372,005✔
1075
               constexpr size_type block_size = sizeof(BlockT);
3,372,005✔
1076
               constexpr size_type block_bits = block_size * 8;
3,372,005✔
1077

1078
               if constexpr(is_byte_aligned()) {
1079
                  auto sink = m_source.as_byte_span().subspan(write_bytepos()).template first<block_size>();
3,345,692✔
1080
                  store_le(sink, block);
3,345,692✔
1081
               } else {
1082
                  const size_type byte_pos = write_bytepos();
26,313✔
1083
                  const size_type bits_to_store = std::min(block_bits, bits_to_write());
52,626✔
1084

1085
                  uint8_t& first_byte = m_source.as_byte_span()[byte_pos];
26,313✔
1086

1087
                  // Set the left-most bits in the first byte, leaving all others unchanged
1088
                  first_byte = (first_byte & uint8_t(0xFF >> m_unaligned_helper.bits_to_byte_alignment)) |
26,313✔
1089
                               uint8_t(block << m_unaligned_helper.padding_bits);
26,313✔
1090

1091
                  // If more bits are provided, we store them in the remaining bytes.
1092
                  if(m_unaligned_helper.bits_to_byte_alignment < bits_to_store) {
26,313✔
1093
                     const auto remaining_bytes =
1094
                        m_source.as_byte_span().subspan(byte_pos + 1).template first<block_size>();
26,313✔
1095
                     const BlockT padding_mask = ~(BlockT(-1) >> m_unaligned_helper.bits_to_byte_alignment);
26,313✔
1096
                     const BlockT new_bytes =
26,313✔
1097
                        (load_le(remaining_bytes) & padding_mask) | block >> m_unaligned_helper.bits_to_byte_alignment;
26,313✔
1098
                     store_le(remaining_bytes, new_bytes);
26,313✔
1099
                  }
1100
               }
1101

1102
               m_write_bitpos += std::min(block_bits, bits_to_write());
3,372,005✔
1103
            }
3,372,005✔
1104

1105
            template <std::unsigned_integral BlockT>
1106
               requires(is_byte_aligned() && !is_const())
1107
            std::span<BlockT> span(size_type blocks) const {
1,222,086,147✔
1108
               BOTAN_DEBUG_ASSERT(blocks == 0 || is_memory_aligned_to<BlockT>());
1,222,086,147✔
1109
               BOTAN_DEBUG_ASSERT(read_bytepos() % sizeof(BlockT) == 0);
1,222,086,147✔
1110
               // Intermittently casting to void* to avoid a compiler warning
1111
               void* ptr = reinterpret_cast<void*>(m_source.as_byte_span().data() + read_bytepos());
1,222,086,147✔
1112
               return {reinterpret_cast<BlockT*>(ptr), blocks};
1,222,086,147✔
1113
            }
1114

1115
            template <std::unsigned_integral BlockT>
1116
               requires(is_byte_aligned() && is_const())
1117
            std::span<const BlockT> span(size_type blocks) const {
1,222,323,835✔
1118
               BOTAN_DEBUG_ASSERT(blocks == 0 || is_memory_aligned_to<BlockT>());
1,222,323,835✔
1119
               BOTAN_DEBUG_ASSERT(read_bytepos() % sizeof(BlockT) == 0);
1,222,323,835✔
1120
               // Intermittently casting to void* to avoid a compiler warning
1121
               const void* ptr = reinterpret_cast<const void*>(m_source.as_byte_span().data() + read_bytepos());
1,222,323,835✔
1122
               return {reinterpret_cast<const BlockT*>(ptr), blocks};
1,222,323,835✔
1123
            }
1124

1125
            void advance(size_type bytes)
1,222,323,868✔
1126
               requires(is_byte_aligned())
1127
            {
1128
               m_read_bitpos += bytes * 8;
1,222,323,868✔
1129
               m_write_bitpos += bytes * 8;
1,222,323,868✔
1130
            }
1131

1132
            template <std::unsigned_integral BlockT>
1133
               requires(is_byte_aligned())
1134
            size_t is_memory_aligned_to() const {
814,803,380✔
1135
               const void* cptr = m_source.as_byte_span().data() + read_bytepos();
814,803,380✔
1136
               const void* ptr_before = cptr;
814,803,380✔
1137

1138
               // std::align takes `ptr` as a reference (!), i.e. `void*&` and
1139
               // uses it as an out-param. Though, `cptr` is const because this
1140
               // method is const-qualified, hence the const_cast<>.
1141
               void* ptr = const_cast<void*>(cptr);  // NOLINT(*-const-correctness)
814,803,380✔
1142
               size_t size = sizeof(BlockT);
814,803,380✔
1143
               return ptr_before != nullptr && std::align(alignof(BlockT), size, ptr, size) == ptr_before;
1,629,606,760✔
1144
            }
1145

1146
         private:
1147
            size_type read_bytepos() const { return m_read_bitpos / 8; }
821,565,981✔
1148

1149
            size_type write_bytepos() const { return m_write_bitpos / 8; }
3,372,005✔
1150

1151
         private:
1152
            BitvectorT& m_source;
1153
            size_type m_start_bitoffset;
1154
            size_type m_bitlength;
1155

1156
            UnalignedDataHelper m_unaligned_helper;
1157

1158
            mutable size_type m_read_bitpos;
1159
            mutable size_type m_write_bitpos;
1160
      };
1161

1162
      /**
1163
       * Helper struct for the low-level handling of blockwise operations
1164
       *
1165
       * This has two main code paths: Optimized for byte-aligned ranges that
1166
       * can simply be taken from memory as-is. And a generic implementation
1167
       * that must assemble blocks from unaligned bits before processing.
1168
       */
1169
      template <typename FnT, typename... ParamTs>
1170
         requires detail::blockwise_processing_callback<FnT, ParamTs...>
1171
      class blockwise_processing_callback_trait {
1172
         public:
1173
            constexpr static bool needs_mask = detail::blockwise_processing_callback_with_mask<FnT, ParamTs...>;
1174
            constexpr static bool is_manipulator = detail::manipulating_blockwise_processing_callback<FnT, ParamTs...>;
1175
            constexpr static bool is_predicate = detail::predicate_blockwise_processing_callback<FnT, ParamTs...>;
1176
            static_assert(!is_manipulator || !is_predicate, "cannot be manipulator and predicate at the same time");
1177

1178
            /**
1179
             * Applies @p fn to the blocks provided in @p blocks by simply reading from
1180
             * memory without re-arranging any bits across byte-boundaries.
1181
             */
1182
            template <std::unsigned_integral... BlockTs>
1183
               requires(all_same_v<std::remove_cv_t<BlockTs>...> && sizeof...(BlockTs) == sizeof...(ParamTs))
1184
            constexpr static bool apply_on_full_blocks(FnT fn, std::span<BlockTs>... blocks) {
1,222,323,868✔
1185
               constexpr size_type bits = sizeof(detail::first_t<BlockTs...>) * 8;
1,222,323,868✔
1186
               const size_type iterations = detail::first(blocks...).size();
1,222,086,588✔
1187
               for(size_type i = 0; i < iterations; ++i) {
2,147,483,647✔
1188
                  if constexpr(is_predicate) {
1189
                     if(!apply(fn, bits, blocks[i]...)) {
33✔
1190
                        return false;
1191
                     }
1192
                  } else if constexpr(is_manipulator) {
1193
                     detail::first(blocks...)[i] = apply(fn, bits, blocks[i]...);
2,147,483,647✔
1194
                  } else {
1195
                     apply(fn, bits, blocks[i]...);
6,156,777✔
1196
                  }
1197
               }
1198
               return true;
339✔
1199
            }
1200

1201
            /**
1202
             * Applies @p fn to as many blocks as @p ops provide for the given type.
1203
             */
1204
            template <std::unsigned_integral BlockT, typename... BitRangeOperatorTs>
1205
               requires(sizeof...(BitRangeOperatorTs) == sizeof...(ParamTs))
1206
            constexpr static bool apply_on_unaligned_blocks(FnT fn, BitRangeOperatorTs&... ops) {
407,806,543✔
1207
               constexpr size_type block_bits = sizeof(BlockT) * 8;
407,806,543✔
1208
               auto bits = detail::first(ops...).bits_to_read();
407,806,543✔
1209
               if(bits == 0) {
407,806,543✔
1210
                  return true;
1211
               }
1212

1213
               bits += block_bits;  // avoid unsigned integer underflow in the following loop
244,633✔
1214
               while(bits > block_bits * 2 - 8) {
3,661,553✔
1215
                  bits -= block_bits;
3,416,923✔
1216
                  if constexpr(is_predicate) {
1217
                     if(!apply(fn, bits, ops.template load_next<BlockT>()...)) {
29✔
1218
                        return false;
1219
                     }
1220
                  } else if constexpr(is_manipulator) {
1221
                     detail::first(ops...).store_next(apply(fn, bits, ops.template load_next<BlockT>()...));
3,410,695✔
1222
                  } else {
1223
                     apply(fn, bits, ops.template load_next<BlockT>()...);
44,902✔
1224
                  }
1225
               }
1226
               return true;
1227
            }
1228

1229
         private:
1230
            template <std::unsigned_integral... BlockTs>
1231
               requires(all_same_v<std::remove_cv_t<BlockTs>...>)
1232
            constexpr static auto apply(FnT fn, size_type bits, BlockTs... blocks) {
2,147,483,647✔
1233
               if constexpr(needs_mask) {
1234
                  return fn(blocks..., make_mask<detail::first_t<BlockTs...>>(bits));
3✔
1235
               } else {
1236
                  return fn(blocks...);
2,147,483,647✔
1237
               }
1238
            }
1239
      };
1240

1241
      /**
1242
       * Helper function of `full_range_operation` and `range_operation` that
1243
       * calls @p fn on a given aligned unsigned integer block as long as the
1244
       * underlying bit range contains enough bits to fill the block fully.
1245
       *
1246
       * This uses bare memory access to gain a speed up for aligned data.
1247
       */
1248
      template <std::unsigned_integral BlockT, typename FnT, typename... BitRangeOperatorTs>
1249
         requires(detail::blockwise_processing_callback<FnT, BitRangeOperatorTs...> &&
1250
                  sizeof...(BitRangeOperatorTs) > 0)
1251
      static bool _process_in_fully_aligned_blocks_of(FnT fn, BitRangeOperatorTs&... ops) {
1,222,323,868✔
1252
         constexpr size_type block_bytes = sizeof(BlockT);
1,222,323,868✔
1253
         constexpr size_type block_bits = block_bytes * 8;
1,222,323,868✔
1254
         const size_type blocks = detail::first(ops...).bits_to_read() / block_bits;
1,222,323,868✔
1255

1256
         using callback_trait = blockwise_processing_callback_trait<FnT, BitRangeOperatorTs...>;
1257
         const auto result = callback_trait::apply_on_full_blocks(fn, ops.template span<BlockT>(blocks)...);
2,147,483,647✔
1258
         (ops.advance(block_bytes * blocks), ...);
1,222,323,868✔
1259
         return result;
1,222,323,868✔
1260
      }
1261

1262
      /**
1263
       * Helper function of `full_range_operation` and `range_operation` that
1264
       * calls @p fn on a given unsigned integer block size as long as the
1265
       * underlying bit range contains enough bits to fill the block.
1266
       */
1267
      template <std::unsigned_integral BlockT, typename FnT, typename... BitRangeOperatorTs>
1268
         requires(detail::blockwise_processing_callback<FnT, BitRangeOperatorTs...>)
1269
      static bool _process_in_unaligned_blocks_of(FnT fn, BitRangeOperatorTs&... ops) {
407,806,543✔
1270
         using callback_trait = blockwise_processing_callback_trait<FnT, BitRangeOperatorTs...>;
1271
         return callback_trait::template apply_on_unaligned_blocks<BlockT>(fn, ops...);
407,715,228✔
1272
      }
1273

1274
      /**
1275
       * Apply @p fn to all bits in the ranges defined by @p ops. If more than
1276
       * one range operator is passed to @p ops, @p fn receives corresponding
1277
       * blocks of bits from each operator. Therefore, all @p ops have to define
1278
       * the exact same length of their underlying ranges.
1279
       *
1280
       * @p fn may return a bit block that will be stored into the _first_ bit
1281
       * range passed into @p ops. If @p fn returns a boolean, and its value is
1282
       * `false`, the range operation is cancelled and `false` is returned.
1283
       *
1284
       * The implementation ensures to pull bits in the largest bit blocks
1285
       * possible and reverts to smaller bit blocks only when needed.
1286
       */
1287
      template <typename FnT, typename... BitRangeOperatorTs>
1288
         requires(detail::blockwise_processing_callback<FnT, BitRangeOperatorTs...> &&
1289
                  sizeof...(BitRangeOperatorTs) > 0)
1290
      static bool range_operation(FnT fn, BitRangeOperatorTs... ops) {
407,532,612✔
1291
         BOTAN_ASSERT(has_equal_lengths(ops...), "all BitRangeOperators have the same length");
170,509✔
1292

1293
         if constexpr((BitRangeOperatorTs::is_byte_aligned() && ...)) {
1294
            // Note: At the moment we can assume that this will always be used
1295
            //       on the _entire_ bitvector. Therefore, we can safely assume
1296
            //       that the bitvectors' underlying buffers are properly aligned.
1297
            //       If this assumption changes, we need to add further handling
1298
            //       to process a byte padding at the beginning of the bitvector
1299
            //       until a memory alignment boundary is reached.
1300
            //
1301
            // An empty range has no blocks to process and a possibly-null
1302
            // underlying buffer, so the alignment check does not apply.
1303
            if(detail::first(ops...).size() != 0) {
407,441,297✔
1304
               const bool alignment = (ops.template is_memory_aligned_to<uint64_t>() && ...);
814,803,394✔
1305
               BOTAN_ASSERT_NOMSG(alignment);
×
1306
            }
1307

1308
            return _process_in_fully_aligned_blocks_of<uint64_t>(fn, ops...) &&
814,882,584✔
1309
                   _process_in_fully_aligned_blocks_of<uint32_t>(fn, ops...) &&
814,882,571✔
1310
                   _process_in_fully_aligned_blocks_of<uint16_t>(fn, ops...) &&
814,882,581✔
1311
                   _process_in_unaligned_blocks_of<uint8_t>(fn, ops...);
407,441,283✔
1312
         } else {
1313
            return _process_in_unaligned_blocks_of<uint64_t>(fn, ops...) &&
182,630✔
1314
                   _process_in_unaligned_blocks_of<uint32_t>(fn, ops...) &&
91,315✔
1315
                   _process_in_unaligned_blocks_of<uint16_t>(fn, ops...) &&
182,630✔
1316
                   _process_in_unaligned_blocks_of<uint8_t>(fn, ops...);
91,315✔
1317
         }
1318
      }
1319

1320
      /**
1321
       * Apply @p fn to all bit blocks in the bitvector(s).
1322
       */
1323
      template <typename FnT, typename... BitvectorTs>
1324
         requires(detail::blockwise_processing_callback<FnT, BitvectorTs...> &&
1325
                  (is_bitvector_v<std::remove_cvref_t<BitvectorTs>> && ... && true))
1326
      static bool full_range_operation(FnT&& fn, BitvectorTs&... bitvecs) {
407,441,297✔
1327
         BOTAN_ASSERT(has_equal_lengths(bitvecs...), "all bitvectors have the same length");
407,441,297✔
1328
         return range_operation(std::forward<FnT>(fn), BitRangeOperator<BitvectorTs>(bitvecs)...);
407,441,297✔
1329
      }
1330

1331
      template <typename SomeT, typename... SomeTs>
1332
      static bool has_equal_lengths(const SomeT& v, const SomeTs&... vs) {
814,762,900✔
1333
         return ((v.size() == vs.size()) && ... && true);
814,762,900✔
1334
      }
1335

1336
      template <std::unsigned_integral T>
1337
      static constexpr T make_mask(size_type bits) {
3✔
1338
         const bool max = bits >= sizeof(T) * 8;
3✔
1339
         bits &= T(max - 1);
3✔
1340
         return (T(!max) << bits) - 1;
3✔
1341
      }
1342

1343
      auto as_byte_span() { return std::span{m_blocks.data(), m_blocks.size() * sizeof(block_type)}; }
1,636,192,197✔
1344

1345
      auto as_byte_span() const { return std::span{m_blocks.data(), m_blocks.size() * sizeof(block_type)}; }
1,633,155,771✔
1346

1347
   private:
1348
      size_type m_bits;
1349
      std::vector<block_type, allocator_type> m_blocks;
1350
};
1351

1352
using secure_bitvector = bitvector_base<secure_allocator>;
1353
using bitvector = bitvector_base<std::allocator>;
1354

1355
namespace detail {
1356

1357
/**
1358
 * If one of the allocators is a Botan::secure_allocator, this will always
1359
 * prefer it. Otherwise, the allocator of @p lhs will be used as a default.
1360
 */
1361
template <bitvectorish T1, bitvectorish T2>
1362
constexpr auto copy_lhs_allocator_aware(const T1& lhs, const T2& /*rhs*/) {
16✔
1363
   constexpr bool needs_secure_allocator =
16✔
1364
      strong_type_wrapped_type<T1>::uses_secure_allocator || strong_type_wrapped_type<T2>::uses_secure_allocator;
1365

1366
   if constexpr(needs_secure_allocator) {
1367
      return lhs.template as<secure_bitvector>();
11✔
1368
   } else {
1369
      return lhs.template as<bitvector>();
6✔
1370
   }
1371
}
1372

1373
}  // namespace detail
1374

1375
template <bitvectorish T1, bitvectorish T2>
1376
auto operator|(const T1& lhs, const T2& rhs) {
5✔
1377
   auto res = detail::copy_lhs_allocator_aware(lhs, rhs);
5✔
1378
   res |= rhs;
5✔
1379
   return res;
5✔
1380
}
×
1381

1382
template <bitvectorish T1, bitvectorish T2>
1383
auto operator&(const T1& lhs, const T2& rhs) {
4✔
1384
   auto res = detail::copy_lhs_allocator_aware(lhs, rhs);
4✔
1385
   res &= rhs;
4✔
1386
   return res;
4✔
1387
}
×
1388

1389
template <bitvectorish T1, bitvectorish T2>
1390
auto operator^(const T1& lhs, const T2& rhs) {
7✔
1391
   auto res = detail::copy_lhs_allocator_aware(lhs, rhs);
7✔
1392
   res ^= rhs;
7✔
1393
   return res;
7✔
1394
}
×
1395

1396
template <bitvectorish T1, bitvectorish T2>
1397
bool operator==(const T1& lhs, const T2& rhs) {
14✔
1398
   return lhs.equals_vartime(rhs);
14✔
1399
}
1400

1401
namespace detail {
1402

1403
/**
1404
 * A Strong<> adapter for arbitrarily large bitvectors
1405
 */
1406
template <concepts::container T>
1407
   requires is_bitvector_v<T>
1408
class Strong_Adapter<T> : public Container_Strong_Adapter_Base<T> {
559,296✔
1409
   public:
1410
      using size_type = typename T::size_type;
1411

1412
   public:
1413
      using Container_Strong_Adapter_Base<T>::Container_Strong_Adapter_Base;
571✔
1414

1415
      auto at(size_type i) const { return this->get().at(i); }
96,256✔
1416

1417
      auto at(size_type i) { return this->get().at(i); }
403,434,725✔
1418

1419
      auto set(size_type i) { return this->get().set(i); }
1✔
1420

1421
      auto unset(size_type i) { return this->get().unset(i); }
1✔
1422

1423
      auto flip(size_type i) { return this->get().flip(i); }
1✔
1424

1425
      auto flip() { return this->get().flip(); }
1✔
1426

1427
      template <typename OutT>
1428
      auto as() const {
73✔
1429
         return this->get().template as<OutT>();
73✔
1430
      }
1431

1432
      template <bitvectorish OutT = T>
1433
      auto subvector(size_type pos, std::optional<size_type> length = std::nullopt) const {
128,266✔
1434
         return this->get().template subvector<OutT>(pos, length);
128,207✔
1435
      }
1436

1437
      template <typename OutT>
1438
         requires(std::unsigned_integral<strong_type_wrapped_type<OutT>> &&
1439
                  !std::same_as<bool, strong_type_wrapped_type<OutT>>)
1440
      auto subvector(size_type pos) const {
74,940✔
1441
         return this->get().template subvector<OutT>(pos);
74,940✔
1442
      }
1443

1444
      template <typename InT>
1445
         requires(std::unsigned_integral<strong_type_wrapped_type<InT>> && !std::same_as<bool, InT>)
1446
      void subvector_replace(size_type pos, InT value) {
74,939✔
1447
         return this->get().subvector_replace(pos, value);
74,939✔
1448
      }
1449

1450
      template <bitvectorish OtherT>
1451
      auto equals(const OtherT& other) const {
45✔
1452
         return this->get().equals(other);
45✔
1453
      }
1454

1455
      auto push_back(bool b) { return this->get().push_back(b); }
1✔
1456

1457
      auto push_back(CT::Choice b) { return this->get().push_back(b); }
397,280✔
1458

1459
      auto pop_back() { return this->get().pop_back(); }
2✔
1460

1461
      auto front() const { return this->get().front(); }
1462

1463
      auto front() { return this->get().front(); }
1✔
1464

1465
      auto back() const { return this->get().back(); }
1466

1467
      auto back() { return this->get().back(); }
1✔
1468

1469
      auto any_vartime() const { return this->get().any_vartime(); }
1✔
1470

1471
      auto all_vartime() const { return this->get().all_vartime(); }
1✔
1472

1473
      auto none_vartime() const { return this->get().none_vartime(); }
1✔
1474

1475
      auto has_odd_hamming_weight() const { return this->get().has_odd_hamming_weight(); }
1✔
1476

1477
      auto hamming_weight() const { return this->get().hamming_weight(); }
83✔
1478

1479
      auto from_bytes(std::span<const uint8_t> bytes, std::optional<size_type> bits = std::nullopt) {
1480
         return this->get().from_bytes(bytes, bits);
1481
      }
1482

1483
      template <typename OutT = T>
1484
      auto to_bytes() const {
1485
         return this->get().template to_bytes<OutT>();
1486
      }
1487

1488
      auto to_bytes(std::span<uint8_t> out) const { return this->get().to_bytes(out); }
59✔
1489

1490
      auto to_string() const { return this->get().to_string(); }
1✔
1491

1492
      auto capacity() const { return this->get().capacity(); }
1493

1494
      auto reserve(size_type n) { return this->get().reserve(n); }
1495

1496
      constexpr void _const_time_poison() const { this->get()._const_time_poison(); }
71✔
1497

1498
      constexpr void _const_time_unpoison() const { this->get()._const_time_unpoison(); }
98✔
1499
};
1500

1501
}  // namespace detail
1502

1503
}  // namespace Botan
1504

1505
#endif
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